Implement Autoscaler Trait

Support Cron type and resource usages (cpu) type
scalling

integrate with OAM by getting child deployment resource via OAM util library

support scaling PodSpecWorkload

support deployment scaling

remove unnecessary comment

support KEDA cron + resource metrics with Keda context

fix ownerreference issue

reorg imports

address part of commemts and refactor code

revert ownerRef settings as somehome leaving spec.replicas doesn't work
This commit is contained in:
zzxwill
2020-10-26 13:39:31 +08:00
committed by 天元
parent fe5f4b1680
commit fecf1a710d
31 changed files with 2494 additions and 104 deletions
+151
View File
@@ -0,0 +1,151 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// Protocol defines network protocols supported for things like container ports.
type Protocol string
// TriggerType defines the type of trigger
type TriggerType string
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// Autoscaler is the Schema for the autoscalers API
type Autoscaler struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec AutoscalerSpec `json:"spec"`
Status AutoscalerStatus `json:"status,omitempty"`
}
func (as *Autoscaler) SetConditions(c ...v1alpha1.Condition) {
as.Status.SetConditions(c...)
}
func (as *Autoscaler) GetCondition(conditionType v1alpha1.ConditionType) v1alpha1.Condition {
return as.Status.GetCondition(conditionType)
}
func (as *Autoscaler) GetWorkloadReference() v1alpha1.TypedReference {
return as.Spec.WorkloadReference
}
func (as *Autoscaler) SetWorkloadReference(reference v1alpha1.TypedReference) {
as.Spec.WorkloadReference = reference
}
type DefaultCondition struct {
// Target is the threshold value to the metric
Target *int32 `json:"target,omitempty"`
}
type CronTypeCondition struct {
// StartAt is the time when the scaler starts, in format `"HHMM"` for example, "08:00"
StartAt string `json:"startAt,omitempty"`
// Duration means how long the target scaling will keep, after the time of duration, the scaling will stop
Duration string `json:"duration,omitempty"`
// Days means in which days the condition will take effect
Days []string `json:"days,omitempty"`
// Replicas is the expected replicas
Replicas int `json:"replicas,omitempty"`
// Timezone defines the time zone, default to the timezone of the Kubernetes cluster
Timezone string `json:"timezone,omitempty"`
}
// TriggerCondition set the condition when to trigger scaling
type TriggerCondition struct {
// DefaultCondition is the condition for resource types, like `cpu/memory/storage/ephemeral-storage`
*DefaultCondition `json:",inline,omitempty"`
// CronTypeCondition is the condition for Cron type scaling, `cron`
*CronTypeCondition `json:",inline,omitempty"`
}
// Trigger defines the trigger of Autoscaler
type Trigger struct {
// Name is the trigger name, if not set, it will be automatically generated and make it globally unique
Name string `json:"name,omitempty"`
// Enabled marks whether the trigger immediately. Defaults to `true`
Enabled bool `json:"enabled,omitempty"`
// Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
Type TriggerType `json:"type"`
// Condition set the condition when to trigger scaling
Condition TriggerCondition `json:"condition"`
}
// AutoscalerSpec defines the desired state of Autoscaler
type AutoscalerSpec struct {
// MinReplicas is the minimal replicas
// +optional
MinReplicas *int32 `json:"minReplicas,omitempty"`
// MinReplicas is the maximal replicas
// +optional
MaxReplicas *int32 `json:"maxReplicas,omitempty"`
// Triggers lists all triggers
Triggers []Trigger `json:"triggers"`
// TargetWorkload specify the workload or child workload which is about to be scaled
TargetWorkload TargetWorkload `json:"targetWorkload,omitempty"`
// WorkloadReference marks the owner of the workload
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
}
// TargetWorkload holds the a reference to the scale target Object
type TargetWorkload struct {
Name string `json:"name"`
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// +optional
Kind string `json:"kind,omitempty"`
}
// AutoscalerStatus defines the observed state of Autoscaler
type AutoscalerStatus struct {
runtimev1alpha1.ConditionedStatus `json:",inline"`
}
// +kubebuilder:object:root=true
// AutoscalerList contains a list of Autoscaler
type AutoscalerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Autoscaler `json:"items"`
}
func init() {
SchemeBuilder.Register(&Autoscaler{}, &AutoscalerList{})
}
+205
View File
@@ -25,6 +25,115 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Autoscaler) DeepCopyInto(out *Autoscaler) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Autoscaler.
func (in *Autoscaler) DeepCopy() *Autoscaler {
if in == nil {
return nil
}
out := new(Autoscaler)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Autoscaler) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AutoscalerList) DeepCopyInto(out *AutoscalerList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Autoscaler, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerList.
func (in *AutoscalerList) DeepCopy() *AutoscalerList {
if in == nil {
return nil
}
out := new(AutoscalerList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AutoscalerList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AutoscalerSpec) DeepCopyInto(out *AutoscalerSpec) {
*out = *in
if in.MinReplicas != nil {
in, out := &in.MinReplicas, &out.MinReplicas
*out = new(int32)
**out = **in
}
if in.MaxReplicas != nil {
in, out := &in.MaxReplicas, &out.MaxReplicas
*out = new(int32)
**out = **in
}
if in.Triggers != nil {
in, out := &in.Triggers, &out.Triggers
*out = make([]Trigger, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.TargetWorkload = in.TargetWorkload
out.WorkloadReference = in.WorkloadReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerSpec.
func (in *AutoscalerSpec) DeepCopy() *AutoscalerSpec {
if in == nil {
return nil
}
out := new(AutoscalerSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AutoscalerStatus) DeepCopyInto(out *AutoscalerStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalerStatus.
func (in *AutoscalerStatus) DeepCopy() *AutoscalerStatus {
if in == nil {
return nil
}
out := new(AutoscalerStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
@@ -61,6 +170,46 @@ func (in *BackendServiceRef) DeepCopy() *BackendServiceRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CronTypeCondition) DeepCopyInto(out *CronTypeCondition) {
*out = *in
if in.Days != nil {
in, out := &in.Days, &out.Days
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronTypeCondition.
func (in *CronTypeCondition) DeepCopy() *CronTypeCondition {
if in == nil {
return nil
}
out := new(CronTypeCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DefaultCondition) DeepCopyInto(out *DefaultCondition) {
*out = *in
if in.Target != nil {
in, out := &in.Target, &out.Target
*out = new(int32)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCondition.
func (in *DefaultCondition) DeepCopy() *DefaultCondition {
if in == nil {
return nil
}
out := new(DefaultCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MetricsTrait) DeepCopyInto(out *MetricsTrait) {
*out = *in
@@ -446,3 +595,59 @@ func (in *TLS) DeepCopy() *TLS {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TargetWorkload) DeepCopyInto(out *TargetWorkload) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetWorkload.
func (in *TargetWorkload) DeepCopy() *TargetWorkload {
if in == nil {
return nil
}
out := new(TargetWorkload)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Trigger) DeepCopyInto(out *Trigger) {
*out = *in
in.Condition.DeepCopyInto(&out.Condition)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Trigger.
func (in *Trigger) DeepCopy() *Trigger {
if in == nil {
return nil
}
out := new(Trigger)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TriggerCondition) DeepCopyInto(out *TriggerCondition) {
*out = *in
if in.DefaultCondition != nil {
in, out := &in.DefaultCondition, &out.DefaultCondition
*out = new(DefaultCondition)
(*in).DeepCopyInto(*out)
}
if in.CronTypeCondition != nil {
in, out := &in.CronTypeCondition, &out.CronTypeCondition
*out = new(CronTypeCondition)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerCondition.
func (in *TriggerCondition) DeepCopy() *TriggerCondition {
if in == nil {
return nil
}
out := new(TriggerCondition)
in.DeepCopyInto(out)
return out
}
@@ -0,0 +1,183 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: autoscalers.standard.oam.dev
spec:
group: standard.oam.dev
names:
categories:
- oam
kind: Autoscaler
listKind: AutoscalerList
plural: autoscalers
singular: autoscaler
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Autoscaler is the Schema for the autoscalers API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: AutoscalerSpec defines the desired state of Autoscaler
properties:
maxReplicas:
description: MinReplicas is the maximal replicas
format: int32
type: integer
minReplicas:
description: MinReplicas is the minimal replicas
format: int32
type: integer
targetWorkload:
description: TargetWorkload specify the workload or child workload
which is about to be scaled
properties:
apiVersion:
type: string
kind:
type: string
name:
type: string
required:
- name
type: object
triggers:
description: Triggers lists all triggers
items:
description: Trigger defines the trigger of Autoscaler
properties:
condition:
description: Condition set the condition when to trigger scaling
properties:
days:
description: Days means in which days the condition will
take effect
items:
type: string
type: array
duration:
description: Duration means how long the target scaling
will keep, after the time of duration, the scaling will
stop
type: string
replicas:
description: Replicas is the expected replicas
type: integer
startAt:
description: StartAt is the time when the scaler starts,
in format `"HHMM"` for example, "08:00"
type: string
target:
description: Target is the threshold value to the metric
format: int32
type: integer
timezone:
description: Timezone defines the time zone, default to
the timezone of the Kubernetes cluster
type: string
type: object
enabled:
description: Enabled marks whether the trigger immediately.
Defaults to `true`
type: boolean
name:
description: Name is the trigger name, if not set, it will be
automatically generated and make it globally unique
type: string
type:
description: Type allows value in [cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom]
type: string
required:
- condition
- type
type: object
type: array
workloadRef:
description: WorkloadReference marks the owner of the workload
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- triggers
type: object
status:
description: AutoscalerStatus defines the observed state of Autoscaler
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
@@ -1233,6 +1233,7 @@ spec:
description: Protocol for port. Must be UDP, TCP,
or SCTP. Defaults to "TCP".
type: string
default: "TCP"
required:
- containerPort
type: object
@@ -3503,6 +3504,7 @@ spec:
can be referred to by services.
type: string
protocol:
default: "TCP"
description: Protocol for port. Must be UDP, TCP,
or SCTP. Defaults to "TCP".
type: string
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: autoscalers.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.webservice
- core.oam.dev/v1alpha2.backend
- core.oam.dev/v1alpha2.task
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: autoscalers.standard.oam.dev
+34 -12
View File
@@ -7,24 +7,32 @@ require (
github.com/AlecAivazis/survey/v2 v2.1.1
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
github.com/briandowns/spinner v1.11.1
github.com/bugsnag/bugsnag-go v1.5.3 // indirect
github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/coreos/prometheus-operator v0.41.1
github.com/crossplane/crossplane-runtime v0.9.0
github.com/crossplane/oam-kubernetes-runtime v0.3.0-rc1.0.20201019050404-723f8ecf8444
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fatih/color v1.9.0
github.com/garyburd/redigo v1.6.2 // indirect
github.com/gertd/go-pluralize v0.1.7
github.com/ghodss/yaml v1.0.0
github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1
github.com/gin-gonic/gin v1.6.3
github.com/go-logr/logr v0.1.0
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721
github.com/google/go-cmp v0.5.2
github.com/google/go-github/v32 v32.1.0
github.com/gosuri/uitable v0.0.4
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/kedacore/keda v1.5.1-0.20201009082004-c498970a120e
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/mholt/archiver/v3 v3.3.0
github.com/oam-dev/trait-injector v0.0.0-20200331033130-0a27b176ffc4
github.com/onsi/ginkgo v1.13.0
github.com/onsi/gomega v1.10.1
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/openservicemesh/osm v0.3.0
github.com/pkg/errors v0.9.1
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
@@ -33,26 +41,40 @@ require (
github.com/stretchr/testify v1.6.1
github.com/wercker/stern v0.0.0-20190705090245-4fa46dd6987f
github.com/wonderflow/cert-manager-api v1.0.3
go.uber.org/zap v1.13.0
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect
github.com/yvasiyarov/gorelic v0.0.7 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect
go.uber.org/zap v1.15.0
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gotest.tools v2.2.0+incompatible
helm.sh/helm/v3 v3.2.4
k8s.io/api v0.18.6
k8s.io/apiextensions-apiserver v0.18.6
k8s.io/apimachinery v0.18.6
k8s.io/cli-runtime v0.18.6
helm.sh/helm/v3 v3.3.4
k8s.io/api v0.18.8
k8s.io/apiextensions-apiserver v0.18.8
k8s.io/apimachinery v0.18.8
k8s.io/cli-runtime v0.18.8
k8s.io/client-go v12.0.0+incompatible
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 // indirect
k8s.io/kubectl v0.18.6
k8s.io/utils v0.0.0-20200414100711-2df71ebbae66
sigs.k8s.io/controller-runtime v0.6.0
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451
sigs.k8s.io/controller-runtime v0.6.2
)
replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v12.2.0+incompatible // https://github.com/kubernetes/client-go/issues/628
github.com/Azure/go-autorest => github.com/Azure/go-autorest v14.2.0+incompatible // https://github.com/kubernetes/client-go/issues/628
github.com/wercker/stern => github.com/oam-dev/stern v1.13.0-alpha
// clint-go had a buggy release, https://github.com/kubernetes/client-go/issues/749
k8s.io/client-go => k8s.io/client-go v0.18.6
k8s.io/client-go => k8s.io/client-go v0.18.8
)
// fix `make` issue
replace github.com/Sirupsen/logrus v1.7.0 => github.com/sirupsen/logrus v1.7.0
replace (
// fix build issue https://github.com/docker/distribution/issues/2406
github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
github.com/docker/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
// fix build issue https://github.com/ory/dockertest/issues/208
golang.org/x/sys => golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6
)
+822 -92
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -20,6 +20,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/applicationdeployment"
autoscalers "github.com/oam-dev/kubevela/pkg/controller/v1alpha1/autoscaler"
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/metrics"
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/podspecworkload"
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/routes"
@@ -29,6 +30,7 @@ import (
func Setup(mgr ctrl.Manager) error {
for _, setup := range []func(ctrl.Manager) error{
metrics.Setup, podspecworkload.Setup, routes.Setup, applicationdeployment.Setup,
metrics.Setup, podspecworkload.Setup, routes.Setup, autoscalers.Setup,
} {
if err := setup(mgr); err != nil {
return err
@@ -0,0 +1,208 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package autoscalers
import (
"context"
"flag"
"fmt"
"path/filepath"
"reflect"
"time"
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
oamutil "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
"github.com/go-logr/logr"
kedav1alpha1 "github.com/kedacore/keda/api/v1alpha1"
"github.com/oam-dev/kubevela/api/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/common"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set"
SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`"
SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value"
SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value"
SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value"
SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format"
SpecWarningSumOfStartAndDurationMoreThan24Hour = "the sum of the start hour and the duration hour has to be less than 24 hours."
)
var (
scaledObjectKind = reflect.TypeOf(kedav1alpha1.ScaledObject{}).Name()
scaledObjectAPIVersion = "keda.k8s.io/v1alpha1"
)
// ReconcileWaitResult is the time to wait between reconciliation.
var ReconcileWaitResult = reconcile.Result{RequeueAfter: 30 * time.Second}
// AutoscalerReconciler reconciles a Autoscaler object
type AutoscalerReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
record event.Recorder
config *restclient.Config
ctx context.Context
}
// +kubebuilder:rbac:groups=standard.oam.dev,resources=autoscalers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=standard.oam.dev,resources=autoscalers/status,verbs=get;update;patch
func (r *AutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("autoscaler", req.NamespacedName)
log.Info("Reconciling Autoscaler...")
var scaler v1alpha1.Autoscaler
if err := r.Get(r.ctx, req.NamespacedName, &scaler); err != nil {
return ReconcileWaitResult, client.IgnoreNotFound(err)
}
log.Info("Retrieved trait Autoscaler", "APIVersion", scaler.APIVersion, "Kind", scaler.Kind)
// find the resource object to record the event to, default is the parent appConfig.
eventObj, err := util.LocateParentAppConfig(r.ctx, r.Client, &scaler)
if err != nil {
log.Error(err, "Failed to find the parent resource", "Autoscaler", scaler.Name)
return util.ReconcileWaitResult, util.PatchCondition(r.ctx, r, &scaler,
cpv1alpha1.ReconcileError(fmt.Errorf(util.ErrLocateAppConfig)))
}
if eventObj == nil {
// fallback to workload itself
log.Info("There is no parent resource", "Autoscaler", scaler.Name)
eventObj = &scaler
}
// Fetch the instance to which the trait refers to
workload, err := oamutil.FetchWorkload(r.ctx, r, log, &scaler)
if err != nil {
log.Error(err, "Error while fetching the workload", "workload reference",
scaler.GetWorkloadReference())
r.record.Event(&scaler, event.Warning(common.ErrLocatingWorkload, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(r.ctx, r, &scaler,
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
}
ownerReference := metav1.OwnerReference{
APIVersion: scaler.APIVersion,
Kind: scaler.Kind,
UID: scaler.GetUID(),
Name: scaler.Name,
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
}
// Fetch the child resources list from the corresponding workload
resources, err := util.FetchWorkloadChildResources(r.ctx, log, r, workload)
if err != nil {
log.Error(err, "Error while fetching the workload child resources", "workload", workload.UnstructuredContent())
r.record.Event(eventObj, event.Warning(util.ErrFetchChildResources, err))
return util.ReconcileWaitResult, util.PatchCondition(r.ctx, r, &scaler,
cpv1alpha1.ReconcileError(fmt.Errorf(util.ErrFetchChildResources)))
}
resources = append(resources, workload)
targetWorkloadSetFlag := false
for _, res := range resources {
resPatch := client.MergeFrom(res.DeepCopyObject())
refs := res.GetOwnerReferences()
for i, r := range refs {
if *r.Controller {
refs[i].Controller = pointer.BoolPtr(false)
}
}
refs = append(refs, ownerReference)
res.SetOwnerReferences(refs)
if err := r.Patch(r.ctx, res, resPatch, client.FieldOwner(scaler.GetUID())); err != nil {
log.Error(err, "Failed to set ownerReference for child resource")
return util.ReconcileWaitResult,
util.PatchCondition(r.ctx, r, &scaler, cpv1alpha1.ReconcileError(
errors.Wrap(err, "Failed to set ownerReference for child resource")))
}
if !targetWorkloadSetFlag && (res.GetKind() == "Deployment" || res.GetKind() == "StatefulSet") {
scaler.Spec.TargetWorkload = v1alpha1.TargetWorkload{
APIVersion: res.GetAPIVersion(),
Kind: res.GetKind(),
Name: res.GetName(),
}
targetWorkloadSetFlag = true
}
}
// if there is no child resource or no child resource kind is deployment or statefuset, set the workload as target workload
if len(resources) == 1 && !targetWorkloadSetFlag {
scaler.Spec.TargetWorkload = v1alpha1.TargetWorkload{
APIVersion: workload.GetAPIVersion(),
Kind: workload.GetKind(),
Name: workload.GetName(),
}
}
namespace := req.NamespacedName.Namespace
if err := r.scaleByKEDA(scaler, namespace, log); err != nil {
return ReconcileWaitResult, err
}
return ctrl.Result{}, nil
}
func (r *AutoscalerReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := r.buildConfig(); err != nil {
return err
}
r.ctx = context.Background()
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("Autoscaler")).
WithAnnotations("controller", "Autoscaler")
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.Autoscaler{}).
Complete(r)
}
func (r *AutoscalerReconciler) buildConfig() error {
var kubeConfig *string
if home := homedir.HomeDir(); home != "" {
kubeConfig = flag.String("kubeConfig", filepath.Join(home, ".kube", "config"), "kubeConfig file")
}
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig)
if err != nil {
return err
}
r.config = config
return nil
}
// Setup adds a controller that reconciles MetricsTrait.
func Setup(mgr ctrl.Manager) error {
r := AutoscalerReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("Autoscaler"),
Scheme: mgr.GetScheme(),
}
return r.SetupWithManager(mgr)
}
+193
View File
@@ -0,0 +1,193 @@
package autoscalers
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/go-logr/logr"
kedav1alpha1 "github.com/kedacore/keda/api/v1alpha1"
kedaclient "github.com/kedacore/keda/pkg/generated/clientset/versioned/typed/keda/v1alpha1"
"github.com/oam-dev/kubevela/api/v1alpha1"
"github.com/pkg/errors"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
apicorev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
)
func (r *AutoscalerReconciler) scaleByKEDA(scaler v1alpha1.Autoscaler, namespace string, log logr.Logger) error {
minReplicas := scaler.Spec.MinReplicas
maxReplicas := scaler.Spec.MaxReplicas
triggers := scaler.Spec.Triggers
scalerName := scaler.Name
targetWorkload := scaler.Spec.TargetWorkload
var kedaTriggers []kedav1alpha1.ScaleTriggers
var err error
var reason string
var resourceMetrics []*autoscalingv2beta2.ResourceMetricSource
for _, t := range triggers {
if t.Type == CronType {
if kedaTriggers, reason, err = r.prepareKEDACronScalerTriggerSpec(scaler, t); err != nil {
log.Error(err, reason)
r.record.Event(&scaler, event.Warning(event.Reason(reason), err))
}
} else if t.Type == CPUType || t.Type == MemoryType || t.Type == StorageType || t.Type == EphemeralStorageType {
resourceMetric := r.prepareKEDAResourceScalerMetrics(t)
resourceMetrics = append(resourceMetrics, resourceMetric)
}
}
scaleObj := kedav1alpha1.ScaledObject{
TypeMeta: metav1.TypeMeta{
Kind: scaledObjectKind,
APIVersion: scaledObjectAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: scalerName,
Namespace: namespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: scaler.APIVersion,
Kind: scaler.Kind,
UID: scaler.GetUID(),
Name: scalerName,
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
Spec: kedav1alpha1.ScaledObjectSpec{
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
APIVersion: targetWorkload.APIVersion,
Kind: targetWorkload.Kind,
Name: targetWorkload.Name,
},
MinReplicaCount: minReplicas,
MaxReplicaCount: maxReplicas,
Advanced: &kedav1alpha1.AdvancedConfig{
HorizontalPodAutoscalerConfig: &kedav1alpha1.HorizontalPodAutoscalerConfig{
ResourceMetrics: resourceMetrics,
},
},
Triggers: kedaTriggers,
},
}
config := r.config
kedaClient, err := kedaclient.NewForConfig(config)
if err != nil {
log.Error(err, "failed to initiate a KEDA client", "config", config)
return err
}
obj, err := kedaClient.ScaledObjects(namespace).Get(r.ctx, scalerName, metav1.GetOptions{})
if err != nil {
log.Info("KEDA ScaledObj doesn't exist", "ScaledObjectName", scalerName)
if _, err := kedaClient.ScaledObjects(namespace).Create(r.ctx, &scaleObj, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {
log.Error(err, "failed to create KEDA ScaledObj", "ScaledObject", scaleObj)
return err
}
} else {
obj.Spec = scaleObj.Spec
if _, err := kedaClient.ScaledObjects(namespace).Update(r.ctx, obj, metav1.UpdateOptions{}); err != nil {
log.Error(err, "failed to update KEDA ScaledObj", "ScaledObject", scaleObj)
return err
}
}
return nil
}
// prepareKEDACronScalerTriggerSpec converts Autoscaler spec into KEDA Cron scaler spec
func (r *AutoscalerReconciler) prepareKEDACronScalerTriggerSpec(scaler v1alpha1.Autoscaler, t v1alpha1.Trigger) ([]kedav1alpha1.ScaleTriggers, string, error) {
var kedaTriggers []kedav1alpha1.ScaleTriggers
targetWorkload := scaler.Spec.TargetWorkload
if targetWorkload.Name == "" {
err := errors.New(SpecWarningTargetWorkloadNotSet)
return kedaTriggers, SpecWarningTargetWorkloadNotSet, err
}
triggerCondition := t.Condition.CronTypeCondition
startAt := triggerCondition.StartAt
if startAt == "" {
return kedaTriggers, SpecWarningStartAtTimeRequired, errors.New(SpecWarningStartAtTimeRequired)
}
duration := triggerCondition.Duration
if duration == "" {
return kedaTriggers, SpecWarningDurationTimeRequired, errors.New(SpecWarningDurationTimeRequired)
}
var err error
startTime, err := time.Parse("15:04", startAt)
if err != nil {
return kedaTriggers, SpecWarningStartAtTimeFormat, err
}
var startHour, startMinute int
startHour = startTime.Hour()
startMinute = startTime.Minute()
durationTime, err := time.ParseDuration(duration)
if err != nil {
return kedaTriggers, SpecWarningDurationTimeNotInRightFormat, err
}
durationHour := durationTime.Hours()
endHour := int(durationHour) + startHour
if endHour >= 24 {
return kedaTriggers, SpecWarningSumOfStartAndDurationMoreThan24Hour, errors.New(SpecWarningSumOfStartAndDurationMoreThan24Hour)
}
replicas := triggerCondition.Replicas
if replicas == 0 {
return kedaTriggers, SpecWarningReplicasRequired, errors.New(SpecWarningReplicasRequired)
}
timezone := triggerCondition.Timezone
//if timezone == "" {
// timezone = "Asia/Shanghai"
//}
days := triggerCondition.Days
var dayNo []int
var i = 0
// TODO(@zzxwill) On Mac, it's Sunday when i == 0, need check on Linux
for _, d := range days {
for i < 7 {
if strings.EqualFold(time.Weekday(i).String(), d) {
dayNo = append(dayNo, i)
break
}
i++
}
}
for _, n := range dayNo {
kedaTrigger := kedav1alpha1.ScaleTriggers{
Type: string(t.Type),
Name: t.Name,
Metadata: map[string]string{
"timezone": timezone,
"start": fmt.Sprintf("%d %d * * %d", startMinute, startHour, n),
"end": fmt.Sprintf("%d %d * * %d", startMinute, endHour, n),
"desiredReplicas": strconv.Itoa(replicas),
},
}
kedaTriggers = append(kedaTriggers, kedaTrigger)
}
return kedaTriggers, "", nil
}
func (r *AutoscalerReconciler) prepareKEDAResourceScalerMetrics(t v1alpha1.Trigger) *autoscalingv2beta2.ResourceMetricSource {
resourceMetric := &autoscalingv2beta2.ResourceMetricSource{
Name: apicorev1.ResourceName(t.Type),
Target: autoscalingv2beta2.MetricTarget{
// Currently only CPU `Utilization` is supported
Type: CPUUtilization,
AverageUtilization: t.Condition.Target,
},
}
return resourceMetric
}
@@ -0,0 +1,81 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package autoscalers
import (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
standardv1alpha1 "github.com/oam-dev/kubevela/api/v1alpha1"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
err = standardv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
@@ -0,0 +1,16 @@
package autoscalers
import (
"github.com/oam-dev/kubevela/api/v1alpha1"
"k8s.io/api/autoscaling/v2beta2"
)
const (
CPUType v1alpha1.TriggerType = "cpu"
MemoryType v1alpha1.TriggerType = "memory"
StorageType v1alpha1.TriggerType = "storage"
EphemeralStorageType v1alpha1.TriggerType = "ephemeral-storage"
CronType v1alpha1.TriggerType = "cron"
CPUUtilization v2beta2.MetricTargetType = "Utilization"
)
@@ -0,0 +1,106 @@
# Scaling PodSpecWorkload with cron and cpu utilization metrics
## Prerequisites
- [ ] HPA with metrics-server enabled
- [ ] [KEDA](https://keda.sh/docs/2.0/deploy/) v2.0 Beta
## Deploy deployment and KEDA
- Apply these manifests to deploy PodSpecWorkload with Trait Autoscaler
```
$ kubectl apply -f .
```
- Check the replicas of the Deployment generated by PodSpecWorkload
```
$ kubectl get deployment --watch
NAME READY UP-TO-DATE AVAILABLE AGE
component-scaler 0/1 0 0 0s
component-scaler 0/1 0 0 0s
component-scaler 0/1 0 0 0s
component-scaler 0/1 0 0 0s
component-scaler 0/1 1 0 0s
component-scaler 1/1 1 1 1s
component-scaler 1/1 1 1 28s
component-scaler 1/2 1 1 40s
component-scaler 1/2 1 1 40s
component-scaler 1/2 1 1 40s
component-scaler 1/2 2 1 40s
component-scaler 2/2 2 2 41s
```
- Wait `ScaledObject` to take effect
```
$ kubectl get scaledobject.keda.sh --watch
trait-scaler component-scaler cron Unknown Unknown 0s
trait-scaler component-scaler cron Unknown Unknown 0s
trait-scaler apps/v1.Deployment component-scaler cron Unknown Unknown 0s
trait-scaler apps/v1.Deployment component-scaler cron Unknown Unknown 0s
trait-scaler apps/v1.Deployment component-scaler cron True Unknown 0s
trait-scaler apps/v1.Deployment component-scaler cron True Unknown 0s
trait-scaler apps/v1.Deployment component-scaler cron True True 1s
trait-scaler apps/v1.Deployment component-scaler cron True True 31s
```
The replicas of Deployment will change to 4.
```shell
$ kubectl get deployment --watch
component-scaler 2/4 2 2 5m47s
component-scaler 2/4 2 2 5m47s
component-scaler 2/4 2 2 5m47s
component-scaler 2/4 4 2 5m47s
component-scaler 3/4 4 3 5m59s
component-scaler 4/4 4 4 5m59s
```
- Visit the Deployment with a heavy load.
```
$ sudo k port-forward deploy/component-scaler 80
$ ab -n 10000 -c 100 http://127.0.0.1/
```
- Monitor the Deployment
The replicas of the Deployment changes from 4 to 8, and to 10 at last. And after stopping ab, it will be scaled down
to 4.
```shell
component-scaler 4/8 4 4 7m5s
component-scaler 4/8 4 4 7m5s
component-scaler 4/8 4 4 7m5s
component-scaler 4/8 8 4 7m6s
component-scaler 5/8 8 5 7m16s
component-scaler 6/8 8 6 7m16s
component-scaler 7/8 8 7 7m16s
component-scaler 8/8 8 8 7m16s
component-scaler 8/10 8 8 7m20s
component-scaler 8/10 8 8 7m20s
component-scaler 8/10 8 8 7m20s
component-scaler 8/10 10 8 7m20s
component-scaler 9/10 10 9 7m22s
component-scaler 10/10 10 10 7m22s
component-scaler 10/4 10 10 12m
component-scaler 10/4 10 10 12m
component-scaler 8/4 8 8 12m
component-scaler 4/4 4 4 12m
```
# Debug
- KEDA ScaledObject won't be ready
```
$ kubectl get scaledobject.keda.sh
NAME SCALETARGETKIND SCALETARGETNAME TRIGGERS AUTHENTICATION READY ACTIVE AGE
trait-scaler component-scaler cron 3m49s
```
Please check those Pods of Keda.
```
$ kubectl get pods -n keda
NAME READY STATUS RESTARTS AGE
keda-operator-6d89f67964-smksp 0/1 CrashLoopBackOff 8 22m
keda-operator-metrics-apiserver-77598644dd-w7th5 1/1 Running 0 22m
```
Try to fix the issue, and those Pods will become `READY`.
```
k get pods -n keda
NAME READY STATUS RESTARTS AGE
keda-operator-695d978ddb-w5qct 1/1 Running 0 7m53s
keda-operator-metrics-apiserver-77598644dd-f4fjq 1/1 Running 0 7m53s
```
@@ -0,0 +1,31 @@
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: appconfig-scaler
spec:
components:
- componentName: component-scaler
traits:
- trait:
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: trait-scaler
spec:
minReplicas: 2
maxReplicas: 10
triggers:
- name: weekday-cron
enabled: true
type: cron
condition:
startAt: "19:01"
duration: 2h
days: ["Monday", "Tuesday"]
replicas: 3
timezone: Asia/Shanghai
- name: cpu-resource-utilization
enabled: true
type: cpu
condition:
target: 5
@@ -0,0 +1,22 @@
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: component-scaler
spec:
workload:
apiVersion: standard.oam.dev/v1alpha1
kind: PodSpecWorkload
spec:
# replicas: 1
podSpec:
containers:
- name: nginx-scaler
image: nginx:1.9.4
ports:
- containerPort: 80
name: nginx
resources:
limits:
cpu: "1"
requests:
cpu: "0.1"
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: autoscalers.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
spec:
appliesToWorkloads:
- webservice
- backend
- task
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: autoscalers.standard.oam.dev
@@ -0,0 +1,26 @@
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: appconfig-scaler
spec:
components:
- componentName: component-scaler
traits:
- trait:
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: trait-scaler
spec:
minReplicas: 3
maxReplicas: 6
triggers:
- name: weekend-cron
enabled: true
type: cron
condition:
startAt: "00:01"
duration: 2h
days: ["Friday", "Saturday"]
replicas: 5
timezone: Asia/Shanghai
@@ -0,0 +1,17 @@
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: component-scaler
spec:
workload:
apiVersion: standard.oam.dev/v1alpha1
kind: PodSpecWorkload
spec:
replicas: 1 # deployment &int32
podSpec:
containers:
- name: nginx-scaler
image: nginx:1.9.4
ports:
- containerPort: 80
name: nginx
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: autoscalers.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.webservice
- core.oam.dev/v1alpha2.backend
- core.oam.dev/v1alpha2.task
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: autoscalers.standard.oam.dev
@@ -0,0 +1,97 @@
# cron type Autoscaler
- Apply manifest
```
$ kubectl apply -f standard_v1alpha2_autoscaler.yaml
$ kubectl describe scaledobjects.keda.sh example-scaler
Name: example-scaler
Namespace: default
Labels: scaledObjectName=example-scaler
Annotations: <none>
API Version: keda.sh/v1alpha1
Kind: ScaledObject
Metadata:
Creation Timestamp: 2020-09-28T09:47:11Z
Finalizers:
finalizer.keda.sh
Generation: 1
Owner References:
API Version: standard.oam.dev/v1alpha1
Block Owner Deletion: true
Controller: true
Kind: Autoscaler
Name: example-scaler
UID: 8ae85eb2-6f1c-4d9e-892c-6af22fa2fac5
Resource Version: 478397
Self Link: /apis/keda.sh/v1alpha1/namespaces/default/scaledobjects/example-scaler
UID: 6c02a685-92e4-4667-84f5-c9d781385cbf
Spec:
Max Replica Count: 4
Min Replica Count: 2
Scale Target Ref:
Name: php-apache
Triggers:
Metadata:
Desired Replicas: 4
End: 48 19 * * 1
Start: 48 17 * * 1
Timezone: Asia/Shanghai
Name: weekend-cron
Type: cron
Metadata:
Desired Replicas: 4
End: 48 19 * * 6
Start: 48 17 * * 6
Timezone: Asia/Shanghai
Name: weekend-cron
Type: cron
Status:
Conditions:
Message: ScaledObject is defined correctly and is ready for scaling
Reason: ScaledObjectReady
Status: True
Type: Ready
Message: Scaling is not performed because triggers are not active
Reason: ScalerNotActive
Status: False
Type: Active
External Metric Names:
cron-Asia-Shanghai-4817xx1-4819xx1
cron-Asia-Shanghai-4817xx6-4819xx6
Original Replica Count: 3
Scale Target GVKR:
Group: apps
Kind: Deployment
Resource: deployments
Version: v1
Scale Target Kind: apps/v1.Deployment
Events: <none>
```
- Monitor KEDA ScaledObject and target deployment
```
$ kubectl get scaledobjects.keda.sh --watch
NAME SCALETARGETKIND SCALETARGETNAME TRIGGERS AUTHENTICATION READY ACTIVE AGE
example-scaler php-apache cron 0s
example-scaler php-apache cron 0s
example-scaler php-apache cron Unknown Unknown 0s
example-scaler php-apache cron Unknown Unknown 0s
example-scaler apps/v1.Deployment php-apache cron Unknown Unknown 0s
example-scaler apps/v1.Deployment php-apache cron Unknown Unknown 0s
example-scaler apps/v1.Deployment php-apache cron True Unknown 0s
example-scaler apps/v1.Deployment php-apache cron True False 0s
example-scaler apps/v1.Deployment php-apache cron True False 60s
example-scaler apps/v1.Deployment php-apache cron True True 60s
```
```
$ kubectl get deploy php-apache --watch
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 3/3 3 3 4m41s
php-apache 3/4 3 3 7m11s
php-apache 3/4 3 3 7m11s
php-apache 3/4 3 3 7m11s
php-apache 3/4 4 3 7m11s
php-apache 4/4 4 4 7m12s
```
@@ -0,0 +1,22 @@
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: example-scaler
spec:
minReplicas: 2 # optional, Defaults: 1
maxReplicas: 6 # optional, cannot be less that minReplicas
triggers:
- name: weekend-cron
enabled: true
type: cron # cron scaler. 表明 scaler 是 KEDA
condition:
startAt: "10:14" # required. "HHMM"
duration: 2h # required. 持续时长;start hour + duration < 24
days: ["Monday", "Saturday"] # optional. In which days the condition will take effect
replicas: 5 # optional.
timezone: Asia/Shanghai # optional. time zone
targetWorkload: # workloadRef # set by users or be auto-patched by outsiders, like OAM
name: "php-apache"
apiVersion: "extensions/v1beta1" # optional
kind: "Deployment" # optional
@@ -0,0 +1,26 @@
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: appconfig-scaler
spec:
components:
- componentName: component-scaler
traits:
- trait:
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: trait-scaler
spec:
minReplicas: 2
maxReplicas: 8
triggers:
- name: weekend-cron
enabled: true
type: cron
condition:
startAt: "16:30"
duration: 2h
days: ["Friday", "Saturday"]
replicas: 4
timezone: Asia/Shanghai
@@ -0,0 +1,24 @@
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: component-scaler
spec:
workload:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-scaler
image: nginx:1.9.4
ports:
- containerPort: 80
name: nginx
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: autoscalers.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.webservice
- core.oam.dev/v1alpha2.backend
- core.oam.dev/v1alpha2.task
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: autoscalers.standard.oam.dev
@@ -0,0 +1,7 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: deployments.apps
spec:
definitionRef:
name: deployments.apps
@@ -0,0 +1,26 @@
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: appconfig-scaler
spec:
components:
- componentName: component-scaler
traits:
- trait:
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: trait-scaler
spec:
minReplicas: 2
maxReplicas: 8
triggers:
- name: weekend-cron
enabled: true
type: cron
condition:
startAt: "16:30"
duration: 2h
days: ["Friday", "Saturday"]
replicas: 4
timezone: Asia/Shanghai
@@ -0,0 +1,24 @@
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: component-scaler
spec:
workload:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-scaler
image: nginx:1.9.4
ports:
- containerPort: 80
name: nginx
@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: autoscalers.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Autoscaler
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.webservice
- core.oam.dev/v1alpha2.backend
- core.oam.dev/v1alpha2.task
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: autoscalers.standard.oam.dev
@@ -0,0 +1,7 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: deployments.apps
spec:
definitionRef:
name: deployments.apps
@@ -0,0 +1,56 @@
# cron type Autoscaler
- Apply manifest
```
$ kubectl apply -f standard_v1alpha2_autoscaler.yaml
$ kubectl describe autoscaler example-scaler
Name: example-scaler
Namespace: default
Labels: <none>
Annotations: API Version: standard.oam.dev/v1alpha1
Kind: Autoscaler
Metadata:
Creation Timestamp: 2020-09-29T03:02:53Z
Generation: 4
Resource Version: 835591
Self Link: /apis/standard.oam.dev/v1alpha1/namespaces/default/autoscalers/example-scaler
UID: 531d875f-4f0d-4b6f-94fe-41a7473d666d
Spec:
Max Replicas: 8
Min Replicas: 4
Target Workload:
API Version: extensions/v1beta1
Kind: Deployment
Name: php-apache
Triggers:
Condition:
Target: 85
Enabled: true
Name: resource-example
Type: cpu
Events: <none>
```
- Monitor HPA instance and target deployment
```
$ kubectl get hpa --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
example-scaler Deployment/php-apache <unknown>/85% 2 4 2 10m
example-scaler Deployment/php-apache <unknown>/85% 1 2 2 10m
example-scaler Deployment/php-apache <unknown>/85% 4 8 2 11m
example-scaler Deployment/php-apache <unknown>/85% 4 8 2 11m
example-scaler Deployment/php-apache <unknown>/85% 4 8 4 11m
```
```
$ kubectl get deploy php-apache --watch
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 2/2 2 2 17h
php-apache 2/4 2 2 17h
php-apache 2/4 2 2 17h
php-apache 2/4 2 2 17h
php-apache 2/4 4 2 17h
php-apache 3/4 4 3 17h
php-apache 4/4 4 4 17h
```
@@ -0,0 +1,26 @@
apiVersion: standard.oam.dev/v1alpha1
kind: Autoscaler
metadata:
name: example-scaler
spec:
minReplicas: 1 # optional, Defaults: 1
maxReplicas: 4 # optional, cannot be less that minReplicas
triggers:
- name: resource-example # optional. 如果为空,自动生成,需要 mutation/validation
enabled: true # optional. whether to take effect. default is true
type: cpu # required. cpu/memory/storage/ephemeral-storage、cron、pps、qps/rps、custom
condition:
target: 85 # required. 阈值,扩容临界值 resource --> 85%
- name: weekend-cron
enabled: true
type: cron # cron scaler. 表明 scaler 是 KEDA
condition:
startAt: "10:14" # required. "HHMM"
duration: 2h # required. 持续时长;start hour + duration < 24
days: ["Monday", "Saturday"] # optional. In which days the condition will take effect
replicas: 5 # optional.
timezone: Asia/Shanghai # optional. time zone
targetWorkload: # set by users or be auto-patched by outsiders, like OAM
name: "php-apache"
apiVersion: "extensions/v1beta1" # optional
kind: "Deployment" # optional