Merge pull request #129373 from googs1025/feature/kubectl/autoscale
feature(kubectl): support --cpu, --memory flag to kubectl autoscale Kubernetes-commit: 3ce7034a7f321bdc8223850cc805f01dc9126621
This commit is contained in:
commit
9892d492ab
|
@ -19,14 +19,16 @@ package autoscale
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
@ -36,6 +38,7 @@ import (
|
|||
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
|
||||
autoscalingv2client "k8s.io/client-go/kubernetes/typed/autoscaling/v2"
|
||||
"k8s.io/client-go/scale"
|
||||
"k8s.io/klog/v2"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
@ -54,10 +57,16 @@ var (
|
|||
|
||||
autoscaleExample = templates.Examples(i18n.T(`
|
||||
# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used
|
||||
kubectl autoscale deployment foo --min=2 --max=10
|
||||
kubectl autoscale deployment foo --min=2 --max=10
|
||||
|
||||
# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%
|
||||
kubectl autoscale rc foo --max=5 --cpu-percent=80`))
|
||||
kubectl autoscale rc foo --max=5 --cpu=80%
|
||||
|
||||
# Auto scale a deployment "bar", with the number of pods between 3 and 6, target average CPU of 500m and memory of 200Mi
|
||||
kubectl autoscale deployment bar --min=3 --max=6 --cpu=500m --memory=200Mi
|
||||
|
||||
# Auto scale a deployment "bar", with the number of pods between 2 and 8, target CPU utilization 60% and memory utilization 70%
|
||||
kubectl autoscale deployment bar --min=3 --max=6 --cpu=60% --memory=70%`))
|
||||
)
|
||||
|
||||
// AutoscaleOptions declares the arguments accepted by the Autoscale command
|
||||
|
@ -74,6 +83,8 @@ type AutoscaleOptions struct {
|
|||
Min int32
|
||||
Max int32
|
||||
CPUPercent int32
|
||||
CPU string
|
||||
Memory string
|
||||
|
||||
createAnnotation bool
|
||||
args []string
|
||||
|
@ -109,7 +120,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu=CPU] [--memory=MEMORY]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
|
||||
Long: autoscaleLong,
|
||||
|
@ -129,7 +140,11 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
|
||||
cmd.MarkFlagRequired("max")
|
||||
cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")
|
||||
cmd.Flags().StringVar(&o.CPU, "cpu", "", `Target CPU utilization over all the pods. When specified as a percentage (e.g."70%" for 70% of requested CPU) it will target average utilization. When specified as quantity (e.g."500m" for 500 milliCPU) it will target average value. Value without units is treated as a quantity with miliCPU being the unit (e.g."500" is "500m").`)
|
||||
cmd.Flags().StringVar(&o.Memory, "memory", "", `Target memory utilization over all the pods. When specified as a percentage (e.g."60%" for 60% of requested memory) it will target average utilization. When specified as quantity (e.g."200Mi" for 200 MiB, "1Gi" for 1 GiB) it will target average value. Value without units is treated as a quantity with mebibytes being the unit (e.g."200" is "200Mi").`)
|
||||
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
|
||||
_ = cmd.Flags().MarkDeprecated("cpu-percent",
|
||||
"Use --cpu with percentage or resource quantity format (e.g., '70%' for utilization or '500m' for milliCPU).")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
|
@ -189,7 +204,22 @@ func (o *AutoscaleOptions) Validate() error {
|
|||
if o.Max < o.Min {
|
||||
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
|
||||
}
|
||||
|
||||
// only one of the CPUPercent or CPU param is allowed
|
||||
if o.CPUPercent > 0 && o.CPU != "" {
|
||||
return fmt.Errorf("--cpu-percent and --cpu are mutually exclusive")
|
||||
}
|
||||
// validate CPU target if specified
|
||||
if o.CPU != "" {
|
||||
if _, _, _, err := parseResourceInput(o.CPU, corev1.ResourceCPU); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// validate Memory target if specified
|
||||
if o.Memory != "" {
|
||||
if _, _, _, err := parseResourceInput(o.Memory, corev1.ResourceMemory); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -214,16 +244,24 @@ func (o *AutoscaleOptions) Run() error {
|
|||
|
||||
mapping := info.ResourceMapping()
|
||||
gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
|
||||
if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
|
||||
if _, err = o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %s: %w", mapping.GroupVersionKind.Kind, err)
|
||||
}
|
||||
|
||||
// handles the creation of HorizontalPodAutoscaler objects for both v2 and v1 APIs.
|
||||
// If v2 API fails, try to create and handle HorizontalPodAutoscaler using v1 API
|
||||
hpaV2 := o.createHorizontalPodAutoscalerV2(info.Name, mapping)
|
||||
if err := o.handleHPA(hpaV2); err != nil {
|
||||
klog.V(1).Infof("Encountered an error with the v2 HorizontalPodAutoscaler: %v. "+
|
||||
"Falling back to try the v1 HorizontalPodAutoscaler", err)
|
||||
// handles the creation of HorizontalPodAutoscaler objects for both autoscaling/v2 and autoscaling/v1 APIs.
|
||||
// If autoscaling/v2 API fails, try to create and handle HorizontalPodAutoscaler using autoscaling/v1 API
|
||||
var hpaV2 runtime.Object
|
||||
hpaV2, err = o.createHorizontalPodAutoscalerV2(info.Name, mapping)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HorizontalPodAutoscaler using autoscaling/v2 API: %w", err)
|
||||
}
|
||||
if err = o.handleHPA(hpaV2); err != nil {
|
||||
klog.V(1).Infof("Encountered an error with the autoscaling/v2 HorizontalPodAutoscaler: %v. "+
|
||||
"Falling back to try the autoscaling/v1 HorizontalPodAutoscaler", err)
|
||||
// check if the HPA can be created using v1 API.
|
||||
if ok, err := o.canCreateHPAV1(); !ok {
|
||||
return fmt.Errorf("failed to create autoscaling/v2 HPA and the configuration is incompatible with autoscaling/v1: %w", err)
|
||||
}
|
||||
hpaV1 := o.createHorizontalPodAutoscalerV1(info.Name, mapping)
|
||||
if err := o.handleHPA(hpaV1); err != nil {
|
||||
return err
|
||||
|
@ -241,6 +279,18 @@ func (o *AutoscaleOptions) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) canCreateHPAV1() (bool, error) {
|
||||
// Allow fallback to v1 HPA only if:
|
||||
// 1. CPUPercent is set and Memory is not set.
|
||||
// 2. Or, Memory is not set and the metric type is UtilizationMetricType.
|
||||
_, _, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return (o.CPUPercent >= 0 && o.Memory == "") ||
|
||||
(o.Memory == "" && metricsType == autoscalingv2.UtilizationMetricType), nil
|
||||
}
|
||||
|
||||
// handleHPA handles the creation and management of a single HPA object.
|
||||
func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
|
||||
if err := o.Recorder.Record(hpa); err != nil {
|
||||
|
@ -288,7 +338,7 @@ func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
|
|||
return printer.PrintObj(actualHPA, o.Out)
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) *autoscalingv2.HorizontalPodAutoscaler {
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) (*autoscalingv2.HorizontalPodAutoscaler, error) {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
|
@ -312,22 +362,83 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mappi
|
|||
scaler.Spec.MinReplicas = &o.Min
|
||||
}
|
||||
|
||||
if o.CPUPercent >= 0 {
|
||||
scaler.Spec.Metrics = []autoscalingv2.MetricSpec{
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: &o.CPUPercent,
|
||||
},
|
||||
},
|
||||
metrics := []autoscalingv2.MetricSpec{}
|
||||
|
||||
// add CPU metric if any of the CPU targets are specified
|
||||
if o.CPUPercent > 0 {
|
||||
cpuMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
cpuMetric.Resource.Target.AverageUtilization = &o.CPUPercent
|
||||
metrics = append(metrics, cpuMetric)
|
||||
}
|
||||
|
||||
return &scaler
|
||||
// add Cpu metric if any of the cpu targets are specified
|
||||
if o.CPU != "" {
|
||||
cpuMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
|
||||
quantity, value, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch metricsType {
|
||||
case autoscalingv2.UtilizationMetricType:
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
cpuMetric.Resource.Target.AverageUtilization = &value
|
||||
case autoscalingv2.AverageValueMetricType:
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
|
||||
cpuMetric.Resource.Target.AverageValue = &quantity
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
|
||||
}
|
||||
metrics = append(metrics, cpuMetric)
|
||||
}
|
||||
|
||||
// add Memory metric if any of the memory targets are specified
|
||||
if o.Memory != "" {
|
||||
memoryMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
quantity, value, metricsType, err := parseResourceInput(o.Memory, corev1.ResourceMemory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch metricsType {
|
||||
case autoscalingv2.UtilizationMetricType:
|
||||
memoryMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
memoryMetric.Resource.Target.AverageUtilization = &value
|
||||
case autoscalingv2.AverageValueMetricType:
|
||||
memoryMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
|
||||
memoryMetric.Resource.Target.AverageValue = &quantity
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
|
||||
}
|
||||
metrics = append(metrics, memoryMetric)
|
||||
}
|
||||
|
||||
// Only set Metrics if there are any defined
|
||||
if len(metrics) > 0 {
|
||||
scaler.Spec.Metrics = metrics
|
||||
} else {
|
||||
scaler.Spec.Metrics = nil
|
||||
}
|
||||
|
||||
return &scaler, nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
|
||||
|
@ -361,3 +472,72 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mappi
|
|||
|
||||
return &scaler
|
||||
}
|
||||
|
||||
// parseResourceInput parses a resource input string into either a utilization percentage or a quantity value.
|
||||
// It supports:
|
||||
// - Percentage values (e.g., "70%") for UtilizationMetricType
|
||||
// - Quantity values with units (e.g., "500m", "2Gi")
|
||||
// - Bare numbers without units, which are interpreted as:
|
||||
// - CPU: milliCPU ("500" → "500m")
|
||||
// - Memory: Mebibytes ("512" → "512Mi")
|
||||
func parseResourceInput(input string, resourceType corev1.ResourceName) (apiresource.Quantity, int32, autoscalingv2.MetricTargetType, error) {
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
return apiresource.Quantity{}, 0, "", fmt.Errorf("empty input")
|
||||
}
|
||||
|
||||
// Case 1: Handle percentage-based metrics like "70%"
|
||||
percentValue, isPercent, err := parsePercentage(input)
|
||||
if isPercent {
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
return apiresource.Quantity{}, percentValue, autoscalingv2.UtilizationMetricType, nil
|
||||
}
|
||||
|
||||
// Case 2: Try to interpret input as a bare number (e.g., "500"), and apply default float
|
||||
valueFloat, err := strconv.ParseFloat(input, 64)
|
||||
if err == nil {
|
||||
unit, err := getDefaultUnitForResource(resourceType)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
|
||||
inputWithUnit := fmt.Sprintf("%g%s", valueFloat, unit)
|
||||
quantity, err := apiresource.ParseQuantity(inputWithUnit)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
|
||||
}
|
||||
|
||||
// Case 3: Parse normally if input has a valid unit (e.g., "500m", "2Gi")
|
||||
quantity, err := apiresource.ParseQuantity(input)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", fmt.Errorf("invalid resource %s value: %s", resourceType, input)
|
||||
}
|
||||
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
|
||||
}
|
||||
|
||||
func getDefaultUnitForResource(resourceType corev1.ResourceName) (string, error) {
|
||||
switch resourceType {
|
||||
case corev1.ResourceCPU:
|
||||
return "m", nil
|
||||
case corev1.ResourceMemory:
|
||||
return "Mi", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported resource type: %v", resourceType)
|
||||
}
|
||||
}
|
||||
|
||||
func parsePercentage(input string) (int32, bool, error) {
|
||||
if !strings.HasSuffix(input, "%") {
|
||||
return 0, false, nil
|
||||
}
|
||||
trimmed := strings.TrimSuffix(input, "%")
|
||||
valueInt64, err := strconv.ParseInt(trimmed, 10, 32)
|
||||
if err != nil || valueInt64 < 0 {
|
||||
return 0, true, fmt.Errorf("invalid percentage value: %s", trimmed)
|
||||
}
|
||||
return int32(valueInt64), true, nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/ptr"
|
||||
|
@ -79,11 +80,113 @@ func TestAutoscaleValidate(t *testing.T) {
|
|||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "CPUPercent appears with CPU",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: 0,
|
||||
CPU: "800",
|
||||
CPUPercent: 20,
|
||||
},
|
||||
expectedError: fmt.Errorf("--cpu-percent and --cpu are mutually exclusive"),
|
||||
},
|
||||
{
|
||||
name: "CPUPercent default (-1) with CPU",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: 0,
|
||||
CPU: "800",
|
||||
CPUPercent: -1,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU percentage",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "70%",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU numeric without unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU with unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500m",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid CPU value (non-numeric)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "abc",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource cpu value: abc"),
|
||||
},
|
||||
{
|
||||
name: "invalid CPU value (malformed unit)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500xyz",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource cpu value: 500xyz"),
|
||||
},
|
||||
{
|
||||
name: "valid memory percentage",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "60%",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid memory numeric without unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid memory with unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512Mi",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid memory value (non-numeric)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "xyz",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource memory value: xyz"),
|
||||
},
|
||||
{
|
||||
name: "invalid memory value (MiB unit)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512MiB",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource memory value: 512MiB"),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errorGot := tc.options.Validate()
|
||||
assert.Equal(t, tc.expectedError, errorGot)
|
||||
if errorGot != nil {
|
||||
assert.Equal(t, tc.expectedError.Error(), errorGot.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +201,10 @@ type createHorizontalPodAutoscalerTestCase struct {
|
|||
}
|
||||
|
||||
func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
|
||||
cpu500m := apiresource.MustParse("500m")
|
||||
mem512Mi := apiresource.MustParse("512Mi")
|
||||
cpu2000m := apiresource.MustParse("2000m")
|
||||
mem3Gi := apiresource.MustParse("3Gi")
|
||||
tests := []createHorizontalPodAutoscalerTestCase{
|
||||
{
|
||||
name: "create with all options",
|
||||
|
@ -360,10 +467,396 @@ func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use %) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "70%",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(70)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use m unit) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500m",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(no use unit) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with memory(no use unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
Memory: "512",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem512Mi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with cpu(no use unit) and memory(no use unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500",
|
||||
Memory: "512",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem512Mi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use m unit) and memory(use Gi unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "2000m",
|
||||
Memory: "3Gi",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu2000m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem3Gi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hpa := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
|
||||
hpa, _ := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
|
||||
assert.Equal(t, tc.expectedHPAV2, hpa)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue