Nearly every cluster we inherit runs on padded resource requests, and the padding is rational. Until recently, correcting a request meant evicting the pod. Teams sized for a p99 they rarely hit, shipped it, and left the number alone for a year, because being right about resources cost a restart. Kubernetes 1.35 removed that price: in-place pod resize is now stable, and CPU and memory on a running container can change while the process keeps serving traffic.
What the padding actually costs
Requests are what the scheduler reserves. A container asking for 2 CPU and using 300m holds 2 CPU worth of node capacity away from everything else, whether or not it burns it. Multiply that across a few hundred pods and the cluster provisions nodes for demand that never arrives. On EKS with Karpenter this is very visible: node count follows requests, and the bill follows node count.
Vertical Pod Autoscaler has been able to spot the gap for years. Acting on it was the problem. In Recreate mode VPA evicts the pod so the new numbers land on a fresh one, which means a rolling disruption every time the recommendation moves. For a stateless API that is tolerable. For a JVM service with a slow warm-up, a game server holding sessions, or anything with a long-lived connection, it is not, so most teams left VPA in Off mode and used it as a dashboard.
What changed
In-place pod resize entered as alpha in 1.27, went beta in 1.33, and graduated to stable in Kubernetes 1.35. Amazon EKS has supported 1.35 since January 2026 and 1.36 since June 2026, so on a reasonably current cluster this is available without a feature gate.
The model splits resources in two. spec.containers[*].resources is now the desired state and is mutable for CPU and memory. status.containerStatuses[*].resources is what the container actually has right now. Kubelet moves the second toward the first, and the gap between them is a state your tooling needs to read.
Behaviour per resource is declared up front with resizePolicy, which is immutable after the pod is created. Decide it when you write the manifest, not when you need the resize:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 6
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/api:1.4.2
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: NotRequired
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "1Gi"
NotRequired is the default for CPU and applies the change to the cgroup with no restart. RestartContainer restarts the container to apply the new value, which is what you want for a runtime that reads its heap ceiling once at boot. A JVM without a dynamic max-heap setting will not notice a memory limit increase; the container has to come back for it to matter. That is a per-workload decision, not a cluster default.
Applying a resize
Resources are no longer patched on the pod object directly. They go through the resize subresource, which kubectl has supported since v1.32:
# bash
kubectl patch pod api --subresource resize --patch \
'{"spec":{"containers":[{"name":"api","resources":{
"requests":{"cpu":"1","memory":"768Mi"},
"limits":{"cpu":"2","memory":"1Gi"}}}]}}'
# what the container actually has now
kubectl get pod api \
-o jsonpath='{.status.containerStatuses[0].resources}{"\n"}'
# whether the resize is stuck, and why
kubectl get pod api \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {.message}{"\n"}{end}'
Two conditions carry the outcome. PodResizePending means the API server accepted the request but the node cannot satisfy it yet, usually because the capacity is not free. PodResizeInProgress means kubelet is applying it. Neither is an error, and a resize can sit pending indefinitely. When the node is constrained, deferred resizes are reattempted in order of PriorityClass and then QoS class, so a batch job will wait behind a Guaranteed API pod by design.
A resize is a request, not a command. Anything you automate on top of it has to reconcile the pending state instead of assuming the patch worked.

Wiring it into rightsizing
Manual patching is fine for an incident. The value shows up when a recommender drives it. VPA ships an InPlaceOrRecreate update mode that tries the in-place path first and falls back to eviction when the change cannot be applied that way:
# vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api
updatePolicy:
updateMode: InPlaceOrRecreate
resourcePolicy:
containerPolicies:
- containerName: api
controlledResources: ["cpu", "memory"]
minAllowed:
cpu: "250m"
memory: "256Mi"
maxAllowed:
cpu: "4"
memory: "4Gi"
Before you let anything act automatically, read what the recommender is proposing and compare it against live usage. This is also how you find pods stuck waiting on capacity, which is the state that quietly breaks naive automation:
# bash
# what the recommender proposes, while it is still only proposing
kubectl get vpa api \
-o jsonpath='{.status.recommendation.containerRecommendations[*]}{"\n"}'
# what the workload is actually using right now
kubectl top pods -l app=api --containers
# every pod in the cluster currently waiting on capacity for a resize
kubectl get pods -A -o json | jq -r '
.items[]
| select(.status.conditions[]?
| select(.type == "PodResizePending" and .status == "True"))
| "\(.metadata.namespace)/\(.metadata.name)"'
Check the support level for your VPA version before you lean on this. The mode has been moving through its own maturity track independently of the core Kubernetes feature, and the fallback to eviction is not hypothetical: it will happen, and it will happen on the workloads you most wanted to protect from restarts. Set minAllowed and maxAllowed deliberately. Without bounds, a recommender that reads a quiet weekend will happily shrink a service into the next Monday.
Before and after
| Rightsizing before 1.35 | Rightsizing with in-place resize |
|---|---|
| Correcting a request evicts the pod | CPU and memory change on the running container |
| VPA effectively read-only in production | VPA can act, with eviction as fallback |
| Padding is cheaper than a restart, so it stays | Padding can be trimmed as load is observed |
| Node count tracks worst-case requests | Node count tracks something closer to real demand |
| Success is binary: applied or rejected | Three states: applied, in progress, pending on capacity |
What we see in production
Across the EKS platforms we run, the biggest resource waste is almost never a runaway workload. It is a long tail of services requesting more than they use, each one defensible on its own, and collectively responsible for a node count nobody can justify. In our work with an application security company scaling EKS with Karpenter and KEDA, and with a SaaS platform running EKS across multiple availability zones and a second region, the cost work went the same way every time: measure real usage, cut the padding, and let the node autoscaler consolidate what is left.
In-place resize does not change that method, it removes the excuse for skipping it. Padding survived audits because the fix carried a restart, the restart needed a maintenance window, and the window never came.
Honest trade-offs
The feature is genuinely useful, and the edges are sharp enough that you want to know them before you build on it.
- The change lives on the pod, not the template. A resize patches a running pod. The next rollout recreates it from the Deployment spec and the old numbers come back. Either write the recommendation back to the template or accept that resize is a runtime correction with a short lifetime.
- QoS class cannot change. A Burstable pod cannot be resized into Guaranteed. If you want Guaranteed, set requests equal to limits at creation.
- Static CPU Manager policy is out. Workloads pinned to exclusive cores, which is typically the latency-sensitive tier that would benefit most, are not covered.
- Swap-related adjustments and Windows containers are not supported. Linux only, and swap stays outside the scope.
- Lowering a memory limit is best-effort. The GA release allows the decrease with best-effort protection against an OOM kill, and best-effort is the operative word. Shrinking memory on a live process deserves more caution than growing it.
- Pending is a real state with real duration. On a tight cluster a resize can wait behind higher-priority pods. Any controller you build has to reconcile rather than fire and forget.
- It composes awkwardly with HPA on the same metric. A vertical recommender and a horizontal one both reacting to CPU will fight. Pick one axis per workload, or drive HPA from a custom metric such as queue depth.
Our default: enable it on the clusters that are already on 1.35 or later, start with CPU set to NotRequired and memory reviewed per workload, run VPA in recommendation mode long enough to trust the numbers, and only then let it act. Rightsizing is a measurement problem first. In-place resize is what makes the answer cheap to apply.
Facing this on your stack? Naviteq’s senior platform team does this for SaaS, FinTech, and Enterprise teams across the US, EU, and Israel. Let’s talk.
Naviteq. DevOps, FinOps and AI-driven cloud automation, delivered at scale.