mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user