Repository: kubernetes/autoscaler
Pull request #8062: Read container resources from containerStatus in the spec client
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-25T19:23:08Z | Merged: 2025-04-29T09:19:56Z

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

Read container resources from `containerStatus` and `initContainerStatus` in the spec client in VPA, if available. If not, VPA will fallback to the container resources defined in `Pod.Spec.Containers[i].Resources` or the equivalent for `initContainers`.  

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 (init)containerStatus.resources when handling container resources in VPA's spec client:  first try to read the resource requests and limits from the (init)container status field (`Pod.Status.(init)ContainerStatuses[i].Resources`). Otherwise, fallback to the resource requests defined in the pod spec (`Pod.Spec.(init)Containers[i].Resources`). 

```




Diff:
diff --git a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client.go b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client.go
index 81de7307358e..70e0ed417549 100644
--- a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client.go
+++ b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client.go
@@ -22,6 +22,7 @@ import (
 	v1lister "k8s.io/client-go/listers/core/v1"
 
 	"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model"
+	resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
 )
 
 // BasicPodSpec contains basic information defining a pod and its containers.
@@ -79,16 +80,13 @@ func (client *specClient) GetPodSpecs() ([]*BasicPodSpec, error) {
 	}
 	return podSpecs, nil
 }
+
 func newBasicPodSpec(pod *v1.Pod) *BasicPodSpec {
-	podId := model.PodID{
-		PodName:   pod.Name,
-		Namespace: pod.Namespace,
-	}
-	containerSpecs := newContainerSpecs(podId, pod.Spec.Containers)
-	initContainerSpecs := newContainerSpecs(podId, pod.Spec.InitContainers)
+	containerSpecs := newContainerSpecs(pod, pod.Spec.Containers, false /* isInitContainer */)
+	initContainerSpecs := newContainerSpecs(pod, pod.Spec.InitContainers, true /* isInitContainer */)
 
 	basicPodSpec := &BasicPodSpec{
-		ID:             podId,
+		ID:             podID(pod),
 		PodLabels:      pod.Labels,
 		Containers:     containerSpecs,
 		InitContainers: initContainerSpecs,
@@ -97,34 +95,38 @@ func newBasicPodSpec(pod *v1.Pod) *BasicPodSpec {
 	return basicPodSpec
 }
 
-func newContainerSpecs(podID model.PodID, containers []v1.Container) []BasicContainerSpec {
+func newContainerSpecs(pod *v1.Pod, containers []v1.Container, isInitContainer bool) []BasicContainerSpec {
 	var containerSpecs []BasicContainerSpec
-
 	for _, container := range containers {
-		containerSpec := newContainerSpec(podID, container)
+		containerSpec := newContainerSpec(pod, container, isInitContainer)
 		containerSpecs = append(containerSpecs, containerSpec)
 	}
-
 	return containerSpecs
 }
 
-func newContainerSpec(podID model.PodID, container v1.Container) BasicContainerSpec {
+func newContainerSpec(pod *v1.Pod, container v1.Container, isInitContainer bool) BasicContainerSpec {
 	containerSpec := BasicContainerSpec{
 		ID: model.ContainerID{
-			PodID:         podID,
+			PodID:         podID(pod),
 			ContainerName: container.Name,
 		},
 		Image:   container.Image,
-		Request: calculateRequestedResources(container),
+		Request: calculateRequestedResources(pod, container, isInitContainer),
 	}
 	return containerSpec
 }
 
-func calculateRequestedResources(container v1.Container) model.Resources {
-	cpuQuantity := container.Resources.Requests[v1.ResourceCPU]
+func calculateRequestedResources(pod *v1.Pod, container v1.Container, isInitContainer bool) model.Resources {
+	requestsAndLimitsFn := resourcehelpers.ContainerRequestsAndLimits
+	if isInitContainer {
+		requestsAndLimitsFn = resourcehelpers.InitContainerRequestsAndLimits
+	}
+	requests, _ := requestsAndLimitsFn(container.Name, pod)
+
+	cpuQuantity := requests[v1.ResourceCPU]
 	cpuMillicores := cpuQuantity.MilliValue()
 
-	memoryQuantity := container.Resources.Requests[v1.ResourceMemory]
+	memoryQuantity := requests[v1.ResourceMemory]
 	memoryBytes := memoryQuantity.Value()
 
 	return model.Resources{
@@ -133,3 +135,10 @@ func calculateRequestedResources(container v1.Container) model.Resources {
 	}
 
 }
+
+func podID(pod *v1.Pod) model.PodID {
+	return model.PodID{
+		PodName:   pod.Name,
+		Namespace: pod.Namespace,
+	}
+}
diff --git a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test.go b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test.go
index cdbda3ba929b..3c9f6db29278 100644
--- a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test.go
+++ b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test.go
@@ -47,6 +47,6 @@ func TestGetPodSpecsReturnsSpecs(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, len(tc.podSpecs), len(podSpecs), "SpecClient returned different number of results then expected")
 	for _, podSpec := range podSpecs {
-		assert.Contains(t, tc.podSpecs, podSpec, "One of returned BasicPodSpcec is different than expected")
+		assert.Contains(t, tc.podSpecs, podSpec, "One of returned BasicPodSpec is different than expected")
 	}
 }
diff --git a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test_util.go b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test_util.go
index e6cfc606ab18..38106e135c39 100644
--- a/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test_util.go
+++ b/vertical-pod-autoscaler/pkg/recommender/input/spec/spec_client_test_util.go
@@ -68,6 +68,19 @@ metadata:
   name: Pod2
   labels:
     Pod2LabelKey: Pod2LabelValue
+status:
+  containerStatuses:
+  - name: Name23
+    resources:
+      requests:
+        memory: "250Mi"
+        cpu: "30m"
+  initContainerStatuses:
+  - name: Name22-init
+    resources:
+      requests:
+        memory: "350Mi"
+        cpu: "40m"
 spec:
   containers:
   - name: Name21
@@ -82,6 +95,14 @@ spec:
       requests:
         memory: "4096Mi"
         cpu: "4000m"
+  - name: Name23
+    image: Name23Image
+    resources:
+      # Requests below will be ignored because
+      # requests are also defined in containerStatus.
+      requests:
+        memory: "1Mi"
+        cpu: "1m"
   initContainers:
   - name: Name21-init
     image: Name21-initImage
@@ -89,6 +110,14 @@ spec:
       requests:
         memory: "128Mi"
         cpu: "40m"
+  - name: Name22-init
+    image: Name22-initImage
+    resources:
+      requests:
+        # Requests below will be ignored because
+        # requests are also defined in initContainerStatus.
+        memory: "1Mi"
+        cpu: "1m"
 `
 
 type podListerMock struct {
@@ -122,11 +151,13 @@ func newSpecClientTestCase() *specClientTestCase {
 	containerSpec12 := newTestContainerSpec(podID1, "Name12", 1000, 1024*1024*1024)
 	containerSpec21 := newTestContainerSpec(podID2, "Name21", 2000, 2048*1024*1024)
 	containerSpec22 := newTestContainerSpec(podID2, "Name22", 4000, 4096*1024*1024)
+	containerSpec23 := newTestContainerSpec(podID2, "Name23", 30, 250*1024*1024)
 
 	initContainerSpec21 := newTestContainerSpec(podID2, "Name21-init", 40, 128*1024*1024)
+	initContainerSpec22 := newTestContainerSpec(podID2, "Name22-init", 40, 350*1024*1024)
 
 	podSpec1 := newTestPodSpec(podID1, []BasicContainerSpec{containerSpec11, containerSpec12}, nil)
-	podSpec2 := newTestPodSpec(podID2, []BasicContainerSpec{containerSpec21, containerSpec22}, []BasicContainerSpec{initContainerSpec21})
+	podSpec2 := newTestPodSpec(podID2, []BasicContainerSpec{containerSpec21, containerSpec22, containerSpec23}, []BasicContainerSpec{initContainerSpec21, initContainerSpec22})
 
 	return &specClientTestCase{
 		podSpecs: []*BasicPodSpec{podSpec1, podSpec2},
diff --git a/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go b/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
index 420f2da25e2c..3e49ffdb4935 100644
--- a/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
+++ b/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
@@ -30,7 +30,7 @@ import (
 //
 // [1] https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources
 func ContainerRequestsAndLimits(containerName string, pod *v1.Pod) (v1.ResourceList, v1.ResourceList) {
-	cs := containerStatusForContainer(containerName, pod)
+	cs := containerStatusFor(containerName, pod)
 	if cs != nil && cs.Resources != nil {
 		return cs.Resources.Requests.DeepCopy(), cs.Resources.Limits.DeepCopy()
 	}
@@ -44,6 +44,29 @@ func ContainerRequestsAndLimits(containerName string, pod *v1.Pod) (v1.ResourceL
 	return nil, nil
 }
 
+// InitContainerRequestsAndLimits returns a copy of the actual resource requests
+// and limits of a given initContainer:
+//
+//   - If in-place pod updates feature [1] is enabled, the actual resource requests
+//     are stored in the initContainer status field.
+//   - Otherwise, fallback to the resource requests defined in the pod spec.
+//
+// [1] https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources
+func InitContainerRequestsAndLimits(initContainerName string, pod *v1.Pod) (v1.ResourceList, v1.ResourceList) {
+	cs := initContainerStatusFor(initContainerName, pod)
+	if cs != nil && cs.Resources != nil {
+		return cs.Resources.Requests.DeepCopy(), cs.Resources.Limits.DeepCopy()
+	}
+
+	klog.V(6).InfoS("initContainer resources not found in initContainerStatus for initContainer. Falling back to resources defined in the pod spec. This is expected for clusters with in-place pod updates feature disabled.", "initContainer", initContainerName, "initContainerStatus", cs)
+	initContainer := findInitContainer(initContainerName, pod)
+	if initContainer != nil {
+		return initContainer.Resources.Requests.DeepCopy(), initContainer.Resources.Limits.DeepCopy()
+	}
+
+	return nil, nil
+}
+
 func findContainer(containerName string, pod *v1.Pod) *v1.Container {
 	for i, container := range pod.Spec.Containers {
 		if container.Name == containerName {
@@ -53,7 +76,16 @@ func findContainer(containerName string, pod *v1.Pod) *v1.Container {
 	return nil
 }
 
-func containerStatusForContainer(containerName string, pod *v1.Pod) *v1.ContainerStatus {
+func findInitContainer(initContainerName string, pod *v1.Pod) *v1.Container {
+	for i, initContainer := range pod.Spec.InitContainers {
+		if initContainer.Name == initContainerName {
+			return &pod.Spec.InitContainers[i]
+		}
+	}
+	return nil
+}
+
+func containerStatusFor(containerName string, pod *v1.Pod) *v1.ContainerStatus {
 	for i, containerStatus := range pod.Status.ContainerStatuses {
 		if containerStatus.Name == containerName {
 			return &pod.Status.ContainerStatuses[i]
@@ -61,3 +93,12 @@ func containerStatusForContainer(containerName string, pod *v1.Pod) *v1.Containe
 	}
 	return nil
 }
+
+func initContainerStatusFor(initContainerName string, pod *v1.Pod) *v1.ContainerStatus {
+	for i, initContainerStatus := range pod.Status.InitContainerStatuses {
+		if initContainerStatus.Name == initContainerName {
+			return &pod.Status.InitContainerStatuses[i]
+		}
+	}
+	return nil
+}
diff --git a/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers_test.go b/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers_test.go
index 70cc8bc6f11d..857785f90b7e 100644
--- a/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers_test.go
+++ b/vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers_test.go
@@ -107,11 +107,36 @@ func TestContainerRequestsAndLimits(t *testing.T) {
 			wantLimits:   nil,
 		},
 		{
-			desc:          "Container with no requests or limits",
-			containerName: "inexistent-container",
+			desc:          "Init container with the same name as the container is ignored",
+			containerName: "container-1",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("container-1").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).
+				AddContainer(
+					test.Container().WithName("container-1").
+						WithCPURequest(resource.MustParse("4")).
+						WithMemRequest(resource.MustParse("40Mi")).
+						WithCPULimit(resource.MustParse("5")).
+						WithMemLimit(resource.MustParse("50Mi")).Get()).
+				Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("4"),
+				apiv1.ResourceMemory: resource.MustParse("40Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("5"),
+				apiv1.ResourceMemory: resource.MustParse("50Mi"),
+			},
+		},
+		{
+			desc:          "Container with no requests or limits returns non-nil resources",
+			containerName: "container",
 			pod:           test.Pod().AddContainer(test.Container().WithName("container").Get()).Get(),
-			wantRequests:  nil,
-			wantLimits:    nil,
+			wantRequests:  apiv1.ResourceList{},
+			wantLimits:    apiv1.ResourceList{},
 		},
 		{
 			desc:          "2 containers",
@@ -159,3 +184,162 @@ func TestContainerRequestsAndLimits(t *testing.T) {
 		})
 	}
 }
+
+func TestInitContainerRequestsAndLimits(t *testing.T) {
+	testCases := []struct {
+		desc              string
+		initContainerName string
+		pod               *apiv1.Pod
+		wantRequests      apiv1.ResourceList
+		wantLimits        apiv1.ResourceList
+	}{
+		{
+			desc:              "Prefer resource requests from initContainer status",
+			initContainerName: "init-container",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("init-container").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).
+				AddInitContainerStatus(
+					test.ContainerStatus().WithName("init-container").
+						WithCPURequest(resource.MustParse("3")).
+						WithMemRequest(resource.MustParse("30Mi")).
+						WithCPULimit(resource.MustParse("4")).
+						WithMemLimit(resource.MustParse("40Mi")).Get()).Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("3"),
+				apiv1.ResourceMemory: resource.MustParse("30Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("4"),
+				apiv1.ResourceMemory: resource.MustParse("40Mi"),
+			},
+		},
+		{
+			desc:              "No initContainer status, get resources from pod spec",
+			initContainerName: "init-container",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("init-container").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("1"),
+				apiv1.ResourceMemory: resource.MustParse("10Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("2"),
+				apiv1.ResourceMemory: resource.MustParse("20Mi"),
+			},
+		},
+		{
+			desc:              "Only initContainerStatus, get resources from initContainerStatus",
+			initContainerName: "init-container",
+			pod: test.Pod().AddInitContainerStatus(
+				test.ContainerStatus().WithName("init-container").
+					WithCPURequest(resource.MustParse("0")).
+					WithMemRequest(resource.MustParse("30Mi")).
+					WithCPULimit(resource.MustParse("4")).
+					WithMemLimit(resource.MustParse("40Mi")).Get()).Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("0"),
+				apiv1.ResourceMemory: resource.MustParse("30Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("4"),
+				apiv1.ResourceMemory: resource.MustParse("40Mi"),
+			},
+		},
+		{
+			desc:              "Inexistent initContainer",
+			initContainerName: "inexistent-init-container",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("init-container").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).Get(),
+			wantRequests: nil,
+			wantLimits:   nil,
+		},
+		{
+			desc:              "Container with the same name as the initContainer is ignored",
+			initContainerName: "container-1",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("container-1").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).
+				AddContainer(
+					test.Container().WithName("container-1").
+						WithCPURequest(resource.MustParse("4")).
+						WithMemRequest(resource.MustParse("40Mi")).
+						WithCPULimit(resource.MustParse("5")).
+						WithMemLimit(resource.MustParse("50Mi")).Get()).
+				Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("1"),
+				apiv1.ResourceMemory: resource.MustParse("10Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("2"),
+				apiv1.ResourceMemory: resource.MustParse("20Mi"),
+			},
+		},
+		{
+			desc:              "InitContainer with no requests or limits returns non-nil resources",
+			initContainerName: "init-container",
+			pod:               test.Pod().AddInitContainer(test.Container().WithName("init-container").Get()).Get(),
+			wantRequests:      apiv1.ResourceList{},
+			wantLimits:        apiv1.ResourceList{},
+		},
+		{
+			desc:              "2 init containers",
+			initContainerName: "init-container-1",
+			pod: test.Pod().AddInitContainer(
+				test.Container().WithName("init-container-1").
+					WithCPURequest(resource.MustParse("1")).
+					WithMemRequest(resource.MustParse("10Mi")).
+					WithCPULimit(resource.MustParse("2")).
+					WithMemLimit(resource.MustParse("20Mi")).Get()).
+				AddInitContainerStatus(
+					test.ContainerStatus().WithName("init-container-1").
+						WithCPURequest(resource.MustParse("3")).
+						WithMemRequest(resource.MustParse("30Mi")).
+						WithCPULimit(resource.MustParse("4")).
+						WithMemLimit(resource.MustParse("40Mi")).Get()).
+				AddInitContainer(
+					test.Container().WithName("init-container-2").
+						WithCPURequest(resource.MustParse("5")).
+						WithMemRequest(resource.MustParse("5Mi")).
+						WithCPULimit(resource.MustParse("5")).
+						WithMemLimit(resource.MustParse("5Mi")).Get()).
+				AddInitContainerStatus(
+					test.ContainerStatus().WithName("init-container-2").
+						WithCPURequest(resource.MustParse("5")).
+						WithMemRequest(resource.MustParse("5Mi")).
+						WithCPULimit(resource.MustParse("5")).
+						WithMemLimit(resource.MustParse("5Mi")).Get()).
+				Get(),
+			wantRequests: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("3"),
+				apiv1.ResourceMemory: resource.MustParse("30Mi"),
+			},
+			wantLimits: apiv1.ResourceList{
+				apiv1.ResourceCPU:    resource.MustParse("4"),
+				apiv1.ResourceMemory: resource.MustParse("40Mi"),
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			gotRequests, gotLimits := InitContainerRequestsAndLimits(tc.initContainerName, tc.pod)
+			assert.Equal(t, tc.wantRequests, gotRequests, "requests don't match")
+			assert.Equal(t, tc.wantLimits, gotLimits, "limits don't match")
+		})
+	}
+}
diff --git a/vertical-pod-autoscaler/pkg/utils/test/test_pod.go b/vertical-pod-autoscaler/pkg/utils/test/test_pod.go
index 3e152c79bf5a..1f3b5dcc183d 100644
--- a/vertical-pod-autoscaler/pkg/utils/test/test_pod.go
+++ b/vertical-pod-autoscaler/pkg/utils/test/test_pod.go
@@ -25,7 +25,9 @@ import (
 type PodBuilder interface {
 	WithName(name string) PodBuilder
 	AddContainer(container apiv1.Container) PodBuilder
+	AddInitContainer(initContainer apiv1.Container) PodBuilder
 	AddContainerStatus(containerStatus apiv1.ContainerStatus) PodBuilder
+	AddInitContainerStatus(initContainerStatus apiv1.ContainerStatus) PodBuilder
 	WithCreator(creatorObjectMeta *metav1.ObjectMeta, creatorTypeMeta *metav1.TypeMeta) PodBuilder
 	WithLabels(labels map[string]string) PodBuilder
 	WithAnnotations(annotations map[string]string) PodBuilder
@@ -42,14 +44,16 @@ func Pod() PodBuilder {
 }
 
 type podBuilderImpl struct {
-	name              string
-	containers        []apiv1.Container
-	creatorObjectMeta *metav1.ObjectMeta
-	creatorTypeMeta   *metav1.TypeMeta
-	labels            map[string]string
-	annotations       map[string]string
-	phase             apiv1.PodPhase
-	containerStatuses []apiv1.ContainerStatus
+	name                  string
+	containers            []apiv1.Container
+	initContainers        []apiv1.Container
+	creatorObjectMeta     *metav1.ObjectMeta
+	creatorTypeMeta       *metav1.TypeMeta
+	labels                map[string]string
+	annotations           map[string]string
+	phase                 apiv1.PodPhase
+	containerStatuses     []apiv1.ContainerStatus
+	initContainerStatuses []apiv1.ContainerStatus
 }
 
 func (pb *podBuilderImpl) WithLabels(labels map[string]string) PodBuilder {
@@ -76,6 +80,12 @@ func (pb *podBuilderImpl) AddContainer(container apiv1.Container) PodBuilder {
 	return &r
 }
 
+func (pb *podBuilderImpl) AddInitContainer(initContainer apiv1.Container) PodBuilder {
+	r := *pb
+	r.initContainers = append(r.initContainers, initContainer)
+	return &r
+}
+
 func (pb *podBuilderImpl) WithCreator(creatorObjectMeta *metav1.ObjectMeta, creatorTypeMeta *metav1.TypeMeta) PodBuilder {
 	r := *pb
 	r.creatorObjectMeta = creatorObjectMeta
@@ -95,6 +105,12 @@ func (pb *podBuilderImpl) AddContainerStatus(containerStatus apiv1.ContainerStat
 	return &r
 }
 
+func (pb *podBuilderImpl) AddInitContainerStatus(initContainerStatus apiv1.ContainerStatus) PodBuilder {
+	r := *pb
+	r.initContainerStatuses = append(r.initContainerStatuses, initContainerStatus)
+	return &r
+}
+
 func (pb *podBuilderImpl) Get() *apiv1.Pod {
 	startTime := metav1.Time{
 		Time: testTimestamp,
@@ -105,7 +121,8 @@ func (pb *podBuilderImpl) Get() *apiv1.Pod {
 			Name:      pb.name,
 		},
 		Spec: apiv1.PodSpec{
-			Containers: pb.containers,
+			Containers:     pb.containers,
+			InitContainers: pb.initContainers,
 		},
 		Status: apiv1.PodStatus{
 			StartTime: &startTime,
@@ -139,6 +156,9 @@ func (pb *podBuilderImpl) Get() *apiv1.Pod {
 	if pb.containerStatuses != nil {
 		pod.Status.ContainerStatuses = pb.containerStatuses
 	}
+	if pb.initContainerStatuses != nil {
+		pod.Status.InitContainerStatuses = pb.initContainerStatuses
+	}
 
 	return pod
 }


Reviews:
user: login: raywainman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T23:54:09Z
user: login: omerap12
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T16:36:53Z
user: login: laoj2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T18:26:53Z
user: login: omerap12
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T09:18:40Z

Review comments:
diff_hunk: @@ -50,6 +50,12 @@ func findContainer(containerName string, pod *v1.Pod) *v1.Container {
 			return &pod.Spec.Containers[i]
 		}
 	}
+
+	for i, initContainer := range pod.Spec.InitContainers {
path: vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
user: login: raywainman
body: I think one small problem with this approach is that it is perfectly OK to have a container and initContainer with the same name.

Here you will always return the container first (which is probably better for VPA anyway?)

I wonder if we should just create a new `InitContainerRequestsAndLimits` function just to keep it all cleanly separated.

What do you think?
created_at: 2025-04-25T23:53:19Z
author_association: MEMBER
diff_hunk: @@ -50,6 +50,12 @@ func findContainer(containerName string, pod *v1.Pod) *v1.Container {
 			return &pod.Spec.Containers[i]
 		}
 	}
+
+	for i, initContainer := range pod.Spec.InitContainers {
path: vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
user: login: omerap12
body: Agree with Ray — we can also revert this change. Not sure why it's necessary; we can leave it as it was before. Meaning:
```go
containerSpecs := newContainerSpecs(podId, pod.Spec.Containers)
initContainerSpecs := newContainerSpecs(podId, pod.Spec.InitContainers)
```
created_at: 2025-04-26T16:36:52Z
author_association: MEMBER
diff_hunk: @@ -50,6 +50,12 @@ func findContainer(containerName string, pod *v1.Pod) *v1.Container {
 			return &pod.Spec.Containers[i]
 		}
 	}
+
+	for i, initContainer := range pod.Spec.InitContainers {
path: vertical-pod-autoscaler/pkg/utils/resources/resourcehelpers.go
user: login: laoj2
body: ah, that's a good catch :( I was assuming that they couldn't have the same name. Fixed, thanks!
created_at: 2025-04-28T18:26:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: k8s-ci-robot
created_at: 2025-04-25T19:23:17Z
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-25T19:24:22Z
body: /assign @omerap12 @raywainman 
author_association: CONTRIBUTOR
user: login: raywainman
created_at: 2025-04-25T23:30:37Z
body: /ok-to-test
author_association: MEMBER
user: login: omerap12
created_at: 2025-04-29T06:33:29Z
body: Thanks!
Just a thought - do you think it’d be useful to add metrics to track how often the fallback is triggered?
/lgtm
author_association: MEMBER
user: login: k8s-ci-robot
created_at: 2025-04-29T09:18:48Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/autoscaler/pull/8062#" title="Author self-approved">laoj2</a>*, *<a href="https://github.com/kubernetes/autoscaler/pull/8062#pullrequestreview-2802682685" 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
user: login: omerap12
created_at: 2025-04-29T10:41:08Z
body: Oops, I didn’t realize Prow also checks the GitHub status ( thought it required an explicit /approve comment )
@raywainman, could you take another look when you get a chance?
author_association: MEMBER