Repository: kubernetes/autoscaler
Pull request #8041: Read container resources from containerStatus in the capping processor
Author: login: laoj2
Labels: name: cncf-cla: yes
description: Indicates the PR's author has signed the CNCF CLA.
name: lgtm
description: "Looks good to me", indicates that a PR is ready to be merged.
name: area/vertical-pod-autoscaler
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: kind/feature
description: Categorizes issue or PR as related to a new feature.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: ok-to-test
description: Indicates a non-member PR verified by an org member that is safe to test.
Created: 2025-04-15T19:08:58Z | Merged: 2025-04-26T16:13:24Z

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

Read container resources from `containerStatus` in the capping processor in VPA, if available. If not, VPA will fallback to the container resources defined in `Pod.Spec.Containers[i].Resources`. 

This is needed because with [In-Place Update of Pod Resources feature](https://github.com/kubernetes/enhancements/issues/1287) (see [KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources) for details), `Pod.Spec.Containers[i].Resources` may not contain the actual resources held by the pod and its containers (see https://github.com/kubernetes/autoscaler/issues/7971 for details).

#### Does this PR introduce a user-facing change?
Yes 

```release-note

Also consider containerStatus.resources when handling container resources in VPA's Capping processor:  first try to read the resource requests and limits from the container status field (`Pod.Status.ContainerStatuses[i].Resources`). Otherwise, fallback to the resource requests defined in the pod spec (`Pod.Spec.Containers[i].Resources`). 

```




Diff:
diff --git a/vertical-pod-autoscaler/pkg/utils/vpa/capping.go b/vertical-pod-autoscaler/pkg/utils/vpa/capping.go
index 815caa37048c..0f2842329c97 100644
--- a/vertical-pod-autoscaler/pkg/utils/vpa/capping.go
+++ b/vertical-pod-autoscaler/pkg/utils/vpa/capping.go
@@ -25,6 +25,7 @@ import (
 
 	vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
 	"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/limitrange"
+	resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
 )
 
 // NewCappingRecommendationProcessor constructs new RecommendationsProcessor that adjusts recommendation
@@ -94,7 +95,7 @@ func (c *cappingRecommendationProcessor) Apply(
 			klog.V(0).InfoS("Failed to fetch LimitRange for namespace", "namespace", pod.Namespace)
 		}
 		updatedContainerResources, containerAnnotations, err := getCappedRecommendationForContainer(
-			*container, &containerRecommendation, policy, containerLimitRange)
+			pod, *container, &containerRecommendation, policy, containerLimitRange)
 
 		if len(containerAnnotations) != 0 {
 			containerToAnnotationsMap[containerRecommendation.ContainerName] = containerAnnotations
@@ -110,6 +111,7 @@ func (c *cappingRecommendationProcessor) Apply(
 
 // getCappedRecommendationForContainer returns a recommendation for the given container, adjusted to obey policy and limits.
 func getCappedRecommendationForContainer(
+	pod *apiv1.Pod,
 	container apiv1.Container,
 	containerRecommendation *vpa_types.RecommendedContainerResources,
 	policy *vpa_types.PodResourcePolicy, limitRange *apiv1.LimitRangeItem) (*vpa_types.RecommendedContainerResources, []string, error) {
@@ -125,7 +127,8 @@ func getCappedRecommendationForContainer(
 	cappingAnnotations := make([]string, 0)
 
 	process := func(recommendation apiv1.ResourceList, genAnnotations bool) {
-		limitAnnotations := applyContainerLimitRange(recommendation, container, limitRange)
+		containerRequests, containerLimits := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
+		limitAnnotations := applyContainerLimitRange(recommendation, containerRequests, containerLimits, limitRange)
 		annotations := applyVPAPolicy(recommendation, containerPolicy)
 		if genAnnotations {
 			cappingAnnotations = append(cappingAnnotations, limitAnnotations...)
@@ -133,7 +136,7 @@ func getCappedRecommendationForContainer(
 		}
 		// TODO: If limits and policy are conflicting, set some condition on the VPA.
 		if containerControlledValues == vpa_types.ContainerControlledValuesRequestsOnly {
-			annotations = capRecommendationToContainerLimit(recommendation, container)
+			annotations = capRecommendationToContainerLimit(recommendation, containerLimits)
 			if genAnnotations {
 				cappingAnnotations = append(cappingAnnotations, annotations...)
 			}
@@ -149,10 +152,10 @@ func getCappedRecommendationForContainer(
 
 // capRecommendationToContainerLimit makes sure recommendation is not above current limit for the container.
 // If this function makes adjustments appropriate annotations are returned.
-func capRecommendationToContainerLimit(recommendation apiv1.ResourceList, container apiv1.Container) []string {
+func capRecommendationToContainerLimit(recommendation apiv1.ResourceList, containerLimits apiv1.ResourceList) []string {
 	annotations := make([]string, 0)
 	// Iterate over limits set in the container. Unset means Infinite limit.
-	for resourceName, limit := range container.Resources.Limits {
+	for resourceName, limit := range containerLimits {
 		recommendedValue, found := recommendation[resourceName]
 		if found && recommendedValue.MilliValue() > limit.MilliValue() {
 			recommendation[resourceName] = limit
@@ -313,13 +316,15 @@ func getContainer(containerName string, pod *apiv1.Pod) *apiv1.Container {
 }
 
 // applyContainerLimitRange updates recommendation if recommended resources are outside of limits defined in VPA resources policy
-func applyContainerLimitRange(recommendation apiv1.ResourceList, container apiv1.Container, limitRange *apiv1.LimitRangeItem) []string {
+func applyContainerLimitRange(recommendation apiv1.ResourceList,
+	containerRequests apiv1.ResourceList, containerLimits apiv1.ResourceList,
+	limitRange *apiv1.LimitRangeItem) []string {
 	annotations := make([]string, 0)
 	if limitRange == nil {
 		return annotations
 	}
-	maxAllowedRecommendation := getMaxAllowedRecommendation(recommendation, container, limitRange)
-	minAllowedRecommendation := getMinAllowedRecommendation(recommendation, container, limitRange)
+	maxAllowedRecommendation := getMaxAllowedRecommendation(recommendation, containerRequests, containerLimits, limitRange)
+	minAllowedRecommendation := getMinAllowedRecommendation(recommendation, containerRequests, containerLimits, limitRange)
 	for resourceName, recommended := range recommendation {
 		cappedToMin, isCapped := maybeCapToMin(recommended, resourceName, minAllowedRecommendation)
 		recommendation[resourceName] = cappedToMin
@@ -335,22 +340,24 @@ func applyContainerLimitRange(recommendation apiv1.ResourceList, container apiv1
 	return annotations
 }
 
-func getMaxAllowedRecommendation(recommendation apiv1.ResourceList, container apiv1.Container,
+func getMaxAllowedRecommendation(recommendation apiv1.ResourceList,
+	containerRequests apiv1.ResourceList, containerLimits apiv1.ResourceList,
 	podLimitRange *apiv1.LimitRangeItem) apiv1.ResourceList {
 	if podLimitRange == nil {
 		return apiv1.ResourceList{}
 	}
-	return getBoundaryRecommendation(recommendation, container, podLimitRange.Max, podLimitRange.Default)
+	return getBoundaryRecommendation(recommendation, containerRequests, containerLimits, podLimitRange.Max, podLimitRange.Default)
 }
 
-func getMinAllowedRecommendation(recommendation apiv1.ResourceList, container apiv1.Container,
+func getMinAllowedRecommendation(recommendation apiv1.ResourceList,
+	containerRequests apiv1.ResourceList, containerLimits apiv1.ResourceList,
 	podLimitRange *apiv1.LimitRangeItem) apiv1.ResourceList {
 	// Both limit and request must be higher than min set in the limit range:
 	// https://github.com/kubernetes/kubernetes/blob/016e9d5c06089774c6286fd825302cbae661a446/plugin/pkg/admission/limitranger/admission.go#L303
 	if podLimitRange == nil {
 		return apiv1.ResourceList{}
 	}
-	minForLimit := getBoundaryRecommendation(recommendation, container, podLimitRange.Min, podLimitRange.Default)
+	minForLimit := getBoundaryRecommendation(recommendation, containerRequests, containerLimits, podLimitRange.Min, podLimitRange.Default)
 	minForRequest := podLimitRange.Min
 	if minForRequest == nil {
 		return minForLimit
@@ -365,13 +372,14 @@ func getMinAllowedRecommendation(recommendation apiv1.ResourceList, container ap
 	return result
 }
 
-func getBoundaryRecommendation(recommendation apiv1.ResourceList, container apiv1.Container,
+func getBoundaryRecommendation(recommendation apiv1.ResourceList,
+	containerRequests apiv1.ResourceList, containerLimits apiv1.ResourceList,
 	boundaryLimit, defaultLimit apiv1.ResourceList) apiv1.ResourceList {
 	if boundaryLimit == nil {
 		return apiv1.ResourceList{}
 	}
-	boundaryCpu := GetBoundaryRequest(apiv1.ResourceCPU, container.Resources.Requests.Cpu(), container.Resources.Limits.Cpu(), boundaryLimit.Cpu(), defaultLimit.Cpu())
-	boundaryMem := GetBoundaryRequest(apiv1.ResourceMemory, container.Resources.Requests.Memory(), container.Resources.Limits.Memory(), boundaryLimit.Memory(), defaultLimit.Memory())
+	boundaryCpu := GetBoundaryRequest(apiv1.ResourceCPU, containerRequests.Cpu(), containerLimits.Cpu(), boundaryLimit.Cpu(), defaultLimit.Cpu())
+	boundaryMem := GetBoundaryRequest(apiv1.ResourceMemory, containerRequests.Memory(), containerLimits.Memory(), boundaryLimit.Memory(), defaultLimit.Memory())
 	return apiv1.ResourceList{
 		apiv1.ResourceCPU:    *boundaryCpu,
 		apiv1.ResourceMemory: *boundaryMem,
@@ -403,8 +411,9 @@ func applyPodLimitRange(resources []vpa_types.RecommendedContainerResources,
 	var sumLimit, sumRecommendation resource.Quantity
 	for _, containerWithRecommendation := range containersWithRecommendations {
 		container := containerWithRecommendation.container
-		limit := container.Resources.Limits[resourceName]
-		request := container.Resources.Requests[resourceName]
+		requests, limits := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
+		limit := limits[resourceName]
+		request := requests[resourceName]
 		var recommendation resource.Quantity
 		if containerWithRecommendation.recommendation == nil {
 			// No recommendation, don't change the container
@@ -455,7 +464,8 @@ func applyPodLimitRange(resources []vpa_types.RecommendedContainerResources,
 		var limit resource.Quantity
 		if containerWithRecommendation.recommendation == nil {
 			// No recommendation, don't change the container
-			limit = containerWithRecommendation.container.Resources.Limits[resourceName]
+			_, limits := resourcehelpers.ContainerRequestsAndLimits(containerWithRecommendation.container.Name, pod)
+			limit = limits[resourceName]
 		} else {
 			limit = (*fieldGetter(*containerWithRecommendation.recommendation))[resourceName]
 		}
@@ -489,12 +499,13 @@ func insertRequestsForMissingRecommendations(containerRecommendations []vpa_type
 		if recommendationForContainerExists(container.Name, containerRecommendations) {
 			continue
 		}
-		if len(container.Resources.Requests) == 0 {
+		requests, _ := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
+		if len(requests) == 0 {
 			continue
 		}
 		result = append(result, vpa_types.RecommendedContainerResources{
 			ContainerName: container.Name,
-			Target:        container.Resources.Requests.DeepCopy(),
+			Target:        requests,
 		})
 	}
 	return result
diff --git a/vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go b/vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
index 9fd890cb4e5e..63762e23b796 100644
--- a/vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
+++ b/vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
@@ -96,6 +96,7 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 	requestsOnly := vpa_types.ContainerControlledValuesRequestsOnly
 	for _, tc := range []struct {
 		name               string
+		pod                *apiv1.Pod
 		policy             vpa_types.PodResourcePolicy
 		expectedTarget     apiv1.ResourceList
 		expectedUpperBound apiv1.ResourceList
@@ -103,6 +104,7 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 	}{
 		{
 			name:   "no capping for default policy",
+			pod:    pod,
 			policy: vpa_types.PodResourcePolicy{},
 			expectedTarget: apiv1.ResourceList{
 				apiv1.ResourceCPU:    *resource.NewScaledQuantity(2, 1),
@@ -114,6 +116,7 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 			},
 		}, {
 			name: "no capping for RequestsAndLimits policy",
+			pod:  pod,
 			policy: vpa_types.PodResourcePolicy{
 				ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
 					ContainerName:    vpa_types.DefaultContainerResourcePolicy,
@@ -130,6 +133,7 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 			},
 		}, {
 			name: "capping for RequestsOnly policy",
+			pod:  pod,
 			policy: vpa_types.PodResourcePolicy{
 				ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
 					ContainerName:    vpa_types.DefaultContainerResourcePolicy,
@@ -145,11 +149,39 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 				apiv1.ResourceMemory: *resource.NewScaledQuantity(7000, 1),
 			},
 			expectedAnnotation: true,
+		}, {
+			name: "capping for RequestsOnly policy for limits defined in containerStatus",
+			pod: func() *apiv1.Pod {
+				pod := test.Pod().WithName("pod1").AddContainer(
+					test.Container().WithName(containerName).
+						WithCPULimit(*resource.NewScaledQuantity(3, 1)).
+						WithMemLimit(*resource.NewScaledQuantity(7000, 1)).Get()).Get()
+				pod.Status.ContainerStatuses = []apiv1.ContainerStatus{
+					test.ContainerStatus().WithName(containerName).
+						WithCPULimit(resource.MustParse("2.5")).
+						WithMemLimit(*resource.NewScaledQuantity(6000, 1)).Get()}
+				return pod
+			}(),
+			policy: vpa_types.PodResourcePolicy{
+				ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
+					ContainerName:    vpa_types.DefaultContainerResourcePolicy,
+					ControlledValues: &requestsOnly,
+				}},
+			},
+			expectedTarget: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("2.5"),
+				apiv1.ResourceMemory: *resource.NewScaledQuantity(6000, 1),
+			},
+			expectedUpperBound: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("2.5"),
+				apiv1.ResourceMemory: *resource.NewScaledQuantity(6000, 1),
+			},
+			expectedAnnotation: true,
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			vpa.Spec.ResourcePolicy = &tc.policy
-			res, annotations, err := NewCappingRecommendationProcessor(&fakeLimitRangeCalculator{}).Apply(vpa, pod)
+			res, annotations, err := NewCappingRecommendationProcessor(&fakeLimitRangeCalculator{}).Apply(vpa, tc.pod)
 			assert.Nil(t, err)
 			assert.Equal(t, tc.expectedTarget, res.ContainerRecommendations[0].Target)
 
@@ -1253,6 +1285,63 @@ func TestApplyLimitRangeMinToRequest(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "Should use container status limits when lower than both recommendation and pod limit range",
+			resources: vpa_types.RecommendedPodResources{
+				ContainerRecommendations: []vpa_types.RecommendedContainerResources{
+					{
+						ContainerName: "container",
+						Target: apiv1.ResourceList{
+							apiv1.ResourceCPU:    resource.MustParse("1"),
+							apiv1.ResourceMemory: resource.MustParse("200M"),
+						},
+					},
+				},
+			},
+			pod: *test.Pod().
+				AddContainer(test.Container().WithName("container").
+					// Requests and Limits defined in the pod spec are ignored because
+					// the values in container status have higher priority.
+					WithCPURequest(resource.MustParse("10")).
+					WithMemRequest(resource.MustParse("1G")).
+					WithCPULimit(resource.MustParse("10")).
+					WithMemLimit(resource.MustParse("1G")).Get()).
+				AddContainerStatus(test.ContainerStatus().WithName("container").
+					WithCPURequest(resource.MustParse("5m")).
+					WithMemRequest(resource.MustParse("50M")).
+					WithCPULimit(resource.MustParse("5m")).
+					WithMemLimit(resource.MustParse("100M")).Get()).Get(),
+			podLimitRange: apiv1.LimitRangeItem{
+				Type: apiv1.LimitTypePod,
+				Min: apiv1.ResourceList{
+					apiv1.ResourceMemory: resource.MustParse("500M"),
+					apiv1.ResourceCPU:    resource.MustParse("2"),
+				},
+			},
+			policy: &vpa_types.PodResourcePolicy{
+				ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
+					ContainerName:    vpa_types.DefaultContainerResourcePolicy,
+					ControlledValues: &requestsOnly,
+				}},
+			},
+			expect: vpa_types.RecommendedPodResources{
+				ContainerRecommendations: []vpa_types.RecommendedContainerResources{
+					{
+						ContainerName: "container",
+						Target: apiv1.ResourceList{
+							apiv1.ResourceCPU:    resource.MustParse("5m"),
+							apiv1.ResourceMemory: resource.MustParse("100M"),
+						},
+					},
+				},
+			},
+			expectAnnotations: map[string][]string{
+				"container": {
+					"memory capped to container limit",
+					"cpu capped to container limit",
+				},
+			},
+		},
 		{
 			name: "caps to pod limit if below pod limit one container with recommendation and one without",
 			resources: vpa_types.RecommendedPodResources{


Reviews:
user: login: omerap12
body: Thanks, Luiz! Overall, it looks good to me aside from a few small nits. It would be great to add an e2e test to cover the entire process.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T06:16:49Z
user: login: omerap12
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T06:18:05Z
user: login: laoj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:44:39Z
user: login: laoj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:44:50Z
user: login: laoj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:48:42Z
user: login: omerap12
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T16:10:47Z

Review comments:
diff_hunk: @@ -125,15 +127,16 @@ func getCappedRecommendationForContainer(
 	cappingAnnotations := make([]string, 0)
 
 	process := func(recommendation apiv1.ResourceList, genAnnotations bool) {
-		limitAnnotations := applyContainerLimitRange(recommendation, container, limitRange)
+		containerRequests, containerLimits := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
path: vertical-pod-autoscaler/pkg/utils/vpa/capping.go
user: login: omerap12
body: Can we add a comment here mentioning that this is the actual fallback?
created_at: 2025-04-16T05:58:22Z
author_association: MEMBER
diff_hunk: @@ -1253,6 +1283,61 @@ func TestApplyLimitRangeMinToRequest(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "caps to pod limit in containerStatus if below pod limit",
path: vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
user: login: omerap12
body: Can we change it to: `Should use container status limits when lower than both Pod spec and Pod limit range`? I think this would be easier to understand
created_at: 2025-04-16T06:15:07Z
author_association: MEMBER
diff_hunk: @@ -145,11 +149,37 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 				apiv1.ResourceMemory: *resource.NewScaledQuantity(7000, 1),
 			},
 			expectedAnnotation: true,
+		}, {
+			name: "capping for RequestsOnly policy for limits defined in containerStatus",
path: vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
user: login: omerap12
body: Shouldn't this test include `Pod.Spec.Containers[i].Resources` alongside `ContainerStatus` to properly demonstrate the fallback mechanism?
created_at: 2025-04-16T06:17:55Z
author_association: MEMBER
diff_hunk: @@ -145,11 +149,37 @@ func TestRecommendationToLimitCapping(t *testing.T) {
 				apiv1.ResourceMemory: *resource.NewScaledQuantity(7000, 1),
 			},
 			expectedAnnotation: true,
+		}, {
+			name: "capping for RequestsOnly policy for limits defined in containerStatus",
path: vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
user: login: laoj2
body: It actually includes it already (it does a deep copy of the pod declared earlier in the test function), but I agree that it's not that readable, I also prefer to redeclare a pod here,  for better readability.
created_at: 2025-04-25T17:44:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1253,6 +1283,61 @@ func TestApplyLimitRangeMinToRequest(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "caps to pod limit in containerStatus if below pod limit",
path: vertical-pod-autoscaler/pkg/utils/vpa/capping_test.go
user: login: laoj2
body: That's much better, thanks!
created_at: 2025-04-25T17:44:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,15 +127,16 @@ func getCappedRecommendationForContainer(
 	cappingAnnotations := make([]string, 0)
 
 	process := func(recommendation apiv1.ResourceList, genAnnotations bool) {
-		limitAnnotations := applyContainerLimitRange(recommendation, container, limitRange)
+		containerRequests, containerLimits := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
path: vertical-pod-autoscaler/pkg/utils/vpa/capping.go
user: login: laoj2
body: The fallback can happen every time we call `resourcehelpers.ContainerRequestsAndLimits`, which is in a lot of places already, that's why I didn't add comments before the function calls. Was thinking of eventually having this completely abstracted from the callers of this library. 
created_at: 2025-04-25T17:48:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,15 +127,16 @@ func getCappedRecommendationForContainer(
 	cappingAnnotations := make([]string, 0)
 
 	process := func(recommendation apiv1.ResourceList, genAnnotations bool) {
-		limitAnnotations := applyContainerLimitRange(recommendation, container, limitRange)
+		containerRequests, containerLimits := resourcehelpers.ContainerRequestsAndLimits(container.Name, pod)
path: vertical-pod-autoscaler/pkg/utils/vpa/capping.go
user: login: omerap12
body: Understood.
created_at: 2025-04-26T16:10:47Z
author_association: MEMBER

PR comments:
user: login: k8s-ci-robot
created_at: 2025-04-15T19:09:09Z
body: Hi @laoj2. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fautoscaler).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

author_association: CONTRIBUTOR
user: login: laoj2
created_at: 2025-04-15T19:10:12Z
body: /assign @raywainman @omerap12 
author_association: CONTRIBUTOR
user: login: raywainman
created_at: 2025-04-15T19:10:22Z
body: /ok-to-test
author_association: MEMBER
user: login: raywainman
created_at: 2025-04-25T23:44:12Z
body: /lgtm

@omerap12 I'll leave the approval up to you :)
author_association: MEMBER
user: login: omerap12
created_at: 2025-04-26T16:12:08Z
body: Appreciated :) 
Thanks @laoj2 
/approve
author_association: MEMBER
user: login: k8s-ci-robot
created_at: 2025-04-26T16:12:15Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/autoscaler/pull/8041#" title="Author self-approved">laoj2</a>*, *<a href="https://github.com/kubernetes/autoscaler/pull/8041#issuecomment-2832386026" title="Approved">omerap12</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fautoscaler).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[vertical-pod-autoscaler/OWNERS](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/OWNERS)~~ [omerap12]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR