k8s.io/kubernetes@v1.29.3/test/e2e/autoscaling/horizontal_pod_autoscaling.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package autoscaling
    18  
    19  import (
    20  	"context"
    21  	"time"
    22  
    23  	"github.com/onsi/ginkgo/v2"
    24  	"k8s.io/pod-security-admission/api"
    25  
    26  	autoscalingv2 "k8s.io/api/autoscaling/v2"
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/kubernetes/test/e2e/feature"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2eautoscaling "k8s.io/kubernetes/test/e2e/framework/autoscaling"
    32  )
    33  
    34  const (
    35  	titleUp                 = "Should scale from 1 pod to 3 pods and then from 3 pods to 5 pods"
    36  	titleDown               = "Should scale from 5 pods to 3 pods and then from 3 pods to 1 pod"
    37  	titleAverageUtilization = " using Average Utilization for aggregation"
    38  	titleAverageValue       = " using Average Value for aggregation"
    39  	valueMetricType         = autoscalingv2.AverageValueMetricType
    40  	utilizationMetricType   = autoscalingv2.UtilizationMetricType
    41  	cpuResource             = v1.ResourceCPU
    42  	memResource             = v1.ResourceMemory
    43  )
    44  
    45  // These tests don't seem to be running properly in parallel: issue: #20338.
    46  var _ = SIGDescribe(feature.HPA, "Horizontal pod autoscaling (scale resource: CPU)", func() {
    47  	f := framework.NewDefaultFramework("horizontal-pod-autoscaling")
    48  	f.NamespacePodSecurityLevel = api.LevelBaseline
    49  
    50  	f.Describe(framework.WithSerial(), framework.WithSlow(), "Deployment (Pod Resource)", func() {
    51  		ginkgo.It(titleUp+titleAverageUtilization, func(ctx context.Context) {
    52  			scaleUp(ctx, "test-deployment", e2eautoscaling.KindDeployment, cpuResource, utilizationMetricType, false, f)
    53  		})
    54  		ginkgo.It(titleDown+titleAverageUtilization, func(ctx context.Context) {
    55  			scaleDown(ctx, "test-deployment", e2eautoscaling.KindDeployment, cpuResource, utilizationMetricType, false, f)
    56  		})
    57  		ginkgo.It(titleUp+titleAverageValue, func(ctx context.Context) {
    58  			scaleUp(ctx, "test-deployment", e2eautoscaling.KindDeployment, cpuResource, valueMetricType, false, f)
    59  		})
    60  	})
    61  
    62  	f.Describe(framework.WithSerial(), framework.WithSlow(), "Deployment (Container Resource)", func() {
    63  		ginkgo.It(titleUp+titleAverageUtilization, func(ctx context.Context) {
    64  			scaleUpContainerResource(ctx, "test-deployment", e2eautoscaling.KindDeployment, cpuResource, utilizationMetricType, f)
    65  		})
    66  		ginkgo.It(titleUp+titleAverageValue, func(ctx context.Context) {
    67  			scaleUpContainerResource(ctx, "test-deployment", e2eautoscaling.KindDeployment, cpuResource, valueMetricType, f)
    68  		})
    69  	})
    70  
    71  	f.Describe(framework.WithSerial(), framework.WithSlow(), "ReplicaSet", func() {
    72  		ginkgo.It(titleUp, func(ctx context.Context) {
    73  			scaleUp(ctx, "rs", e2eautoscaling.KindReplicaSet, cpuResource, utilizationMetricType, false, f)
    74  		})
    75  		ginkgo.It(titleDown, func(ctx context.Context) {
    76  			scaleDown(ctx, "rs", e2eautoscaling.KindReplicaSet, cpuResource, utilizationMetricType, false, f)
    77  		})
    78  	})
    79  
    80  	// These tests take ~20 minutes each.
    81  	f.Describe(framework.WithSerial(), framework.WithSlow(), "ReplicationController", func() {
    82  		ginkgo.It(titleUp+" and verify decision stability", func(ctx context.Context) {
    83  			scaleUp(ctx, "rc", e2eautoscaling.KindRC, cpuResource, utilizationMetricType, true, f)
    84  		})
    85  		ginkgo.It(titleDown+" and verify decision stability", func(ctx context.Context) {
    86  			scaleDown(ctx, "rc", e2eautoscaling.KindRC, cpuResource, utilizationMetricType, true, f)
    87  		})
    88  	})
    89  
    90  	f.Describe("ReplicationController light", func() {
    91  		ginkgo.It("Should scale from 1 pod to 2 pods", func(ctx context.Context) {
    92  			st := &HPAScaleTest{
    93  				initPods:         1,
    94  				initCPUTotal:     150,
    95  				perPodCPURequest: 200,
    96  				targetValue:      50,
    97  				minPods:          1,
    98  				maxPods:          2,
    99  				firstScale:       2,
   100  				resourceType:     cpuResource,
   101  				metricTargetType: utilizationMetricType,
   102  			}
   103  			st.run(ctx, "rc-light", e2eautoscaling.KindRC, f)
   104  		})
   105  		f.It(f.WithSlow(), "Should scale from 2 pods to 1 pod", func(ctx context.Context) {
   106  			st := &HPAScaleTest{
   107  				initPods:         2,
   108  				initCPUTotal:     50,
   109  				perPodCPURequest: 200,
   110  				targetValue:      50,
   111  				minPods:          1,
   112  				maxPods:          2,
   113  				firstScale:       1,
   114  				resourceType:     cpuResource,
   115  				metricTargetType: utilizationMetricType,
   116  			}
   117  			st.run(ctx, "rc-light", e2eautoscaling.KindRC, f)
   118  		})
   119  	})
   120  
   121  	f.Describe(framework.WithSerial(), framework.WithSlow(), "ReplicaSet with idle sidecar (ContainerResource use case)", func() {
   122  		// ContainerResource CPU autoscaling on idle sidecar
   123  		ginkgo.It(titleUp+" on a busy application with an idle sidecar container", func(ctx context.Context) {
   124  			scaleOnIdleSideCar(ctx, "rs", e2eautoscaling.KindReplicaSet, cpuResource, utilizationMetricType, false, f)
   125  		})
   126  
   127  		// ContainerResource CPU autoscaling on busy sidecar
   128  		ginkgo.It("Should not scale up on a busy sidecar with an idle application", func(ctx context.Context) {
   129  			doNotScaleOnBusySidecar(ctx, "rs", e2eautoscaling.KindReplicaSet, cpuResource, utilizationMetricType, true, f)
   130  		})
   131  	})
   132  
   133  	f.Describe("CustomResourceDefinition", func() {
   134  		ginkgo.It("Should scale with a CRD targetRef", func(ctx context.Context) {
   135  			scaleTest := &HPAScaleTest{
   136  				initPods:         1,
   137  				initCPUTotal:     150,
   138  				perPodCPURequest: 200,
   139  				targetValue:      50,
   140  				minPods:          1,
   141  				maxPods:          2,
   142  				firstScale:       2,
   143  				resourceType:     cpuResource,
   144  				metricTargetType: utilizationMetricType,
   145  			}
   146  			scaleTest.run(ctx, "foo-crd", e2eautoscaling.KindCRD, f)
   147  		})
   148  	})
   149  })
   150  
   151  var _ = SIGDescribe(feature.HPA, "Horizontal pod autoscaling (scale resource: Memory)", func() {
   152  	f := framework.NewDefaultFramework("horizontal-pod-autoscaling")
   153  	f.NamespacePodSecurityLevel = api.LevelBaseline
   154  
   155  	f.Describe(framework.WithSerial(), framework.WithSlow(), "Deployment (Pod Resource)", func() {
   156  		ginkgo.It(titleUp+titleAverageUtilization, func(ctx context.Context) {
   157  			scaleUp(ctx, "test-deployment", e2eautoscaling.KindDeployment, memResource, utilizationMetricType, false, f)
   158  		})
   159  		ginkgo.It(titleUp+titleAverageValue, func(ctx context.Context) {
   160  			scaleUp(ctx, "test-deployment", e2eautoscaling.KindDeployment, memResource, valueMetricType, false, f)
   161  		})
   162  	})
   163  
   164  	f.Describe(framework.WithSerial(), framework.WithSlow(), "Deployment (Container Resource)", func() {
   165  		ginkgo.It(titleUp+titleAverageUtilization, func(ctx context.Context) {
   166  			scaleUpContainerResource(ctx, "test-deployment", e2eautoscaling.KindDeployment, memResource, utilizationMetricType, f)
   167  		})
   168  		ginkgo.It(titleUp+titleAverageValue, func(ctx context.Context) {
   169  			scaleUpContainerResource(ctx, "test-deployment", e2eautoscaling.KindDeployment, memResource, valueMetricType, f)
   170  		})
   171  	})
   172  })
   173  
   174  // HPAScaleTest struct is used by the scale(...) function.
   175  type HPAScaleTest struct {
   176  	initPods         int
   177  	initCPUTotal     int
   178  	initMemTotal     int
   179  	perPodCPURequest int64
   180  	perPodMemRequest int64
   181  	targetValue      int32
   182  	minPods          int32
   183  	maxPods          int32
   184  	firstScale       int
   185  	firstScaleStasis time.Duration
   186  	cpuBurst         int
   187  	memBurst         int
   188  	secondScale      int32
   189  	resourceType     v1.ResourceName
   190  	metricTargetType autoscalingv2.MetricTargetType
   191  }
   192  
   193  // run is a method which runs an HPA lifecycle, from a starting state, to an expected
   194  // The initial state is defined by the initPods parameter.
   195  // The first state change is due to the CPU being consumed initially, which HPA responds to by changing pod counts.
   196  // The second state change (optional) is due to the CPU burst parameter, which HPA again responds to.
   197  // TODO The use of 3 states is arbitrary, we could eventually make this test handle "n" states once this test stabilizes.
   198  func (st *HPAScaleTest) run(ctx context.Context, name string, kind schema.GroupVersionKind, f *framework.Framework) {
   199  	const timeToWait = 15 * time.Minute
   200  	initCPUTotal, initMemTotal := 0, 0
   201  	if st.resourceType == cpuResource {
   202  		initCPUTotal = st.initCPUTotal
   203  	} else if st.resourceType == memResource {
   204  		initMemTotal = st.initMemTotal
   205  	}
   206  	rc := e2eautoscaling.NewDynamicResourceConsumer(ctx, name, f.Namespace.Name, kind, st.initPods, initCPUTotal, initMemTotal, 0, st.perPodCPURequest, st.perPodMemRequest, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle)
   207  	ginkgo.DeferCleanup(rc.CleanUp)
   208  	hpa := e2eautoscaling.CreateResourceHorizontalPodAutoscaler(ctx, rc, st.resourceType, st.metricTargetType, st.targetValue, st.minPods, st.maxPods)
   209  	ginkgo.DeferCleanup(e2eautoscaling.DeleteHorizontalPodAutoscaler, rc, hpa.Name)
   210  
   211  	rc.WaitForReplicas(ctx, st.firstScale, timeToWait)
   212  	if st.firstScaleStasis > 0 {
   213  		rc.EnsureDesiredReplicasInRange(ctx, st.firstScale, st.firstScale+1, st.firstScaleStasis, hpa.Name)
   214  	}
   215  	if st.resourceType == cpuResource && st.cpuBurst > 0 && st.secondScale > 0 {
   216  		rc.ConsumeCPU(st.cpuBurst)
   217  		rc.WaitForReplicas(ctx, int(st.secondScale), timeToWait)
   218  	}
   219  	if st.resourceType == memResource && st.memBurst > 0 && st.secondScale > 0 {
   220  		rc.ConsumeMem(st.memBurst)
   221  		rc.WaitForReplicas(ctx, int(st.secondScale), timeToWait)
   222  	}
   223  }
   224  
   225  func scaleUp(ctx context.Context, name string, kind schema.GroupVersionKind, resourceType v1.ResourceName, metricTargetType autoscalingv2.MetricTargetType, checkStability bool, f *framework.Framework) {
   226  	stasis := 0 * time.Minute
   227  	if checkStability {
   228  		stasis = 10 * time.Minute
   229  	}
   230  	st := &HPAScaleTest{
   231  		initPods:         1,
   232  		perPodCPURequest: 500,
   233  		perPodMemRequest: 500,
   234  		targetValue:      getTargetValueByType(100, 20, metricTargetType),
   235  		minPods:          1,
   236  		maxPods:          5,
   237  		firstScale:       3,
   238  		firstScaleStasis: stasis,
   239  		secondScale:      5,
   240  		resourceType:     resourceType,
   241  		metricTargetType: metricTargetType,
   242  	}
   243  	if resourceType == cpuResource {
   244  		st.initCPUTotal = 250
   245  		st.cpuBurst = 700
   246  	}
   247  	if resourceType == memResource {
   248  		st.initMemTotal = 250
   249  		st.memBurst = 700
   250  	}
   251  	st.run(ctx, name, kind, f)
   252  }
   253  
   254  func scaleDown(ctx context.Context, name string, kind schema.GroupVersionKind, resourceType v1.ResourceName, metricTargetType autoscalingv2.MetricTargetType, checkStability bool, f *framework.Framework) {
   255  	stasis := 0 * time.Minute
   256  	if checkStability {
   257  		stasis = 10 * time.Minute
   258  	}
   259  	st := &HPAScaleTest{
   260  		initPods:         5,
   261  		perPodCPURequest: 500,
   262  		perPodMemRequest: 500,
   263  		targetValue:      getTargetValueByType(150, 30, metricTargetType),
   264  		minPods:          1,
   265  		maxPods:          5,
   266  		firstScale:       3,
   267  		firstScaleStasis: stasis,
   268  		cpuBurst:         10,
   269  		secondScale:      1,
   270  		resourceType:     resourceType,
   271  		metricTargetType: metricTargetType,
   272  	}
   273  	if resourceType == cpuResource {
   274  		st.initCPUTotal = 325
   275  		st.cpuBurst = 10
   276  	}
   277  	if resourceType == memResource {
   278  		st.initMemTotal = 325
   279  		st.memBurst = 10
   280  	}
   281  	st.run(ctx, name, kind, f)
   282  }
   283  
   284  type HPAContainerResourceScaleTest struct {
   285  	initPods               int
   286  	initCPUTotal           int
   287  	initMemTotal           int
   288  	perContainerCPURequest int64
   289  	perContainerMemRequest int64
   290  	targetValue            int32
   291  	minPods                int32
   292  	maxPods                int32
   293  	noScale                bool
   294  	noScaleStasis          time.Duration
   295  	firstScale             int
   296  	firstScaleStasis       time.Duration
   297  	cpuBurst               int
   298  	memBurst               int
   299  	secondScale            int32
   300  	sidecarStatus          e2eautoscaling.SidecarStatusType
   301  	sidecarType            e2eautoscaling.SidecarWorkloadType
   302  	resourceType           v1.ResourceName
   303  	metricTargetType       autoscalingv2.MetricTargetType
   304  }
   305  
   306  func (st *HPAContainerResourceScaleTest) run(ctx context.Context, name string, kind schema.GroupVersionKind, f *framework.Framework) {
   307  	const timeToWait = 15 * time.Minute
   308  	initCPUTotal, initMemTotal := 0, 0
   309  	if st.resourceType == cpuResource {
   310  		initCPUTotal = st.initCPUTotal
   311  	} else if st.resourceType == memResource {
   312  		initMemTotal = st.initMemTotal
   313  	}
   314  	rc := e2eautoscaling.NewDynamicResourceConsumer(ctx, name, f.Namespace.Name, kind, st.initPods, initCPUTotal, initMemTotal, 0, st.perContainerCPURequest, st.perContainerMemRequest, f.ClientSet, f.ScalesGetter, st.sidecarStatus, st.sidecarType)
   315  	ginkgo.DeferCleanup(rc.CleanUp)
   316  	hpa := e2eautoscaling.CreateContainerResourceHorizontalPodAutoscaler(ctx, rc, st.resourceType, st.metricTargetType, st.targetValue, st.minPods, st.maxPods)
   317  	ginkgo.DeferCleanup(e2eautoscaling.DeleteContainerResourceHPA, rc, hpa.Name)
   318  
   319  	if st.noScale {
   320  		if st.noScaleStasis > 0 {
   321  			rc.EnsureDesiredReplicasInRange(ctx, st.initPods, st.initPods, st.noScaleStasis, hpa.Name)
   322  		}
   323  	} else {
   324  		rc.WaitForReplicas(ctx, st.firstScale, timeToWait)
   325  		if st.firstScaleStasis > 0 {
   326  			rc.EnsureDesiredReplicasInRange(ctx, st.firstScale, st.firstScale+1, st.firstScaleStasis, hpa.Name)
   327  		}
   328  		if st.resourceType == cpuResource && st.cpuBurst > 0 && st.secondScale > 0 {
   329  			rc.ConsumeCPU(st.cpuBurst)
   330  			rc.WaitForReplicas(ctx, int(st.secondScale), timeToWait)
   331  		}
   332  		if st.resourceType == memResource && st.memBurst > 0 && st.secondScale > 0 {
   333  			rc.ConsumeMem(st.memBurst)
   334  			rc.WaitForReplicas(ctx, int(st.secondScale), timeToWait)
   335  		}
   336  	}
   337  }
   338  
   339  func scaleUpContainerResource(ctx context.Context, name string, kind schema.GroupVersionKind, resourceType v1.ResourceName, metricTargetType autoscalingv2.MetricTargetType, f *framework.Framework) {
   340  	st := &HPAContainerResourceScaleTest{
   341  		initPods:               1,
   342  		perContainerCPURequest: 500,
   343  		perContainerMemRequest: 500,
   344  		targetValue:            getTargetValueByType(100, 20, metricTargetType),
   345  		minPods:                1,
   346  		maxPods:                5,
   347  		firstScale:             3,
   348  		firstScaleStasis:       0,
   349  		secondScale:            5,
   350  		resourceType:           resourceType,
   351  		metricTargetType:       metricTargetType,
   352  		sidecarStatus:          e2eautoscaling.Disable,
   353  		sidecarType:            e2eautoscaling.Idle,
   354  	}
   355  	if resourceType == cpuResource {
   356  		st.initCPUTotal = 250
   357  		st.cpuBurst = 700
   358  	}
   359  	if resourceType == memResource {
   360  		st.initMemTotal = 250
   361  		st.memBurst = 700
   362  	}
   363  	st.run(ctx, name, kind, f)
   364  }
   365  
   366  func scaleOnIdleSideCar(ctx context.Context, name string, kind schema.GroupVersionKind, resourceType v1.ResourceName, metricTargetType autoscalingv2.MetricTargetType, checkStability bool, f *framework.Framework) {
   367  	// Scale up on a busy application with an idle sidecar container
   368  	stasis := 0 * time.Minute
   369  	if checkStability {
   370  		stasis = 10 * time.Minute
   371  	}
   372  	st := &HPAContainerResourceScaleTest{
   373  		initPods:               1,
   374  		initCPUTotal:           125,
   375  		perContainerCPURequest: 250,
   376  		targetValue:            20,
   377  		minPods:                1,
   378  		maxPods:                5,
   379  		firstScale:             3,
   380  		firstScaleStasis:       stasis,
   381  		cpuBurst:               500,
   382  		secondScale:            5,
   383  		resourceType:           resourceType,
   384  		metricTargetType:       metricTargetType,
   385  		sidecarStatus:          e2eautoscaling.Enable,
   386  		sidecarType:            e2eautoscaling.Idle,
   387  	}
   388  	st.run(ctx, name, kind, f)
   389  }
   390  
   391  func doNotScaleOnBusySidecar(ctx context.Context, name string, kind schema.GroupVersionKind, resourceType v1.ResourceName, metricTargetType autoscalingv2.MetricTargetType, checkStability bool, f *framework.Framework) {
   392  	// Do not scale up on a busy sidecar with an idle application
   393  	stasis := 0 * time.Minute
   394  	if checkStability {
   395  		stasis = 1 * time.Minute
   396  	}
   397  	st := &HPAContainerResourceScaleTest{
   398  		initPods:               1,
   399  		initCPUTotal:           250,
   400  		perContainerCPURequest: 500,
   401  		targetValue:            20,
   402  		minPods:                1,
   403  		maxPods:                5,
   404  		cpuBurst:               700,
   405  		sidecarStatus:          e2eautoscaling.Enable,
   406  		sidecarType:            e2eautoscaling.Busy,
   407  		resourceType:           resourceType,
   408  		metricTargetType:       metricTargetType,
   409  		noScale:                true,
   410  		noScaleStasis:          stasis,
   411  	}
   412  	st.run(ctx, name, kind, f)
   413  }
   414  
   415  func getTargetValueByType(averageValueTarget, averageUtilizationTarget int, targetType autoscalingv2.MetricTargetType) int32 {
   416  	if targetType == utilizationMetricType {
   417  		return int32(averageUtilizationTarget)
   418  	}
   419  	return int32(averageValueTarget)
   420  }