Skip to content
Hogin Hogin
Go back

In-Place Pod Resize reaches GA in Kubernetes 1.35: resizing CPU/memory without restarting pods

11 мин чтения

Until now, the only way to change CPU or memory on a running pod was to edit requests/limits in the controller’s spec, wait for Kubernetes to delete the old pod and create a new one, and hope it landed on the same node with the same data locality. Kubernetes 1.35 closes that chapter: In-Place Pod Resize (KEP-1287) has gone stable, and kubectl patch pod --subresource resize changes a container’s resources while it keeps running. Let’s break down what actually became stable, what still forces a restart, and how it ties into VPA, the scheduler, and the cluster autoscaler.

Table of contents

Open Table of contents

Why this used to hurt

A pod in Kubernetes isn’t just a set of containers — it’s also requests/limits, fixed at creation time and nailed to the Pod object for good. The spec.containers[].resources field was immutable: trying to change it via kubectl edit or kubectl apply was rejected by the API server outright. The only legal path was recreating the whole pod.

For a stateless service behind a Deployment, that’s just an extra rolling restart. For everything else, it was a real cost:

The Kubernetes API worked around this the crude way: people over-provisioned requests “just in case,” padded limits “to be safe,” and accepted that VPA couldn’t be turned on for anything restart-sensitive. In-place resize removes the reason for that trade-off entirely — the pod stays the same pod, same IP, same PID 1 in the container, just with a different cgroup limit around it.

Recreating a pod the old way vs. the new resize subresource

What exactly went stable in 1.35

GA in 1.35 “Timbernetes” locks in three things that previously lived behind the InPlacePodVerticalScaling feature gate (alpha in 1.27, beta in 1.33):

What still forces a container restart

In-place resize isn’t “any resource change, free of charge.” A container restart (not the whole pod — the pod and its IP survive this restart) is still required in a few cases:

Which resource changes apply without a restart, and which need RestartContainer

A live example: resizing a running pod

Patching memory on a container that’s already running, with zero restarts — on cgroups v2 with resizePolicy: NotRequired:

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# 0

kubectl patch pod worker-0 --subresource resize --patch \
  '{"spec":{"containers":[{"name":"worker","resources":{"requests":{"memory":"512Mi","cpu":"250m"},"limits":{"memory":"1Gi","cpu":"1"}}}]}}'
# pod/worker-0 patched

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# 0

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].resources}{"\n"}'
# {"limits":{"cpu":"1","memory":"1Gi"},"requests":{"cpu":"250m","memory":"512Mi"}}

restartCount didn’t move — meaning this really was an in-place resize, not a fast restart that’s easy to mistake for one when eyeballing logs. If the node physically didn’t have room for the new request, status.conditions would show PodResizePending with reason: Deferred instead of applying instantly — the kubelet holds the desired value in spec but doesn’t touch the cgroup until room frees up (or until the resize is reversed with another patch).

Integrating with the Vertical Pod Autoscaler

VPA got a third update mode — updateMode: InPlaceOrRecreate — which tries the resize subresource first and only falls back to the old behavior on failure:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: worker-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  updatePolicy:
    updateMode: InPlaceOrRecreate
  resourcePolicy:
    containerPolicies:
      - containerName: worker
        minAllowed:
          cpu: 100m
          memory: 256Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi

The sequence VPA follows under this mode:

  1. The recommender computes a new target — it tries pods/resize on the current pod first.
  2. The kubelet accepts it: applied in place, restartCount doesn’t grow, the pod never leaves the node.
  3. The kubelet rejects it as Infeasible (the new request physically doesn’t fit on the current node) or the resizePolicy requires a container restart and the gap is too large — VPA falls back to the old behavior: evict the pod and recreate it with the new values, exactly like updateMode: Recreate used to.

For an operator, that means InPlaceOrRecreate is almost always safer than plain Recreate — but it doesn’t remove the occasional need to accept a recreation on an overcommitted node.

What this changes for the scheduler and the cluster autoscaler

The subtlest effect of GA isn’t the resize itself — it’s what happens to an already-made scheduling decision once requests change after the fact:

Comparison: before / after

DimensionBefore in-place resize (≤1.26, no feature gate)With GA in-place resize (1.35+)
Changing CPU on a running podfull pod recreationresize subresource, no container restart
Raising the memory limit (cgroups v2)full pod recreationresize subresource, no container restart
Lowering the memory limit (cgroups v1)full pod recreationstill requires RestartContainer
Pod IP / process-local cachelost on recreationpreserved — the pod never leaves the node
VPA update modeOff / Initial / Recreate+ InPlaceOrRecreate with a recreate fallback
Resize changing the QoS classimpossible (the whole pod is recreated with a new class)still impossible — the API server rejects the patch
Effect on the schedulera new scheduling decision on every changenone — the kubelet checks feasibility locally

What it takes to turn this on

How to verify a resize actually happened in place

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# unchanged restartCount means the resize applied without recreating the container

kubectl get pod worker-0 -o jsonpath='{.status.conditions[?(@.type=="PodResizePending")]}{"\n"}'
# empty if the resize applied immediately; {"reason":"Deferred",...} if the pod is waiting for room

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].resources}{"\n"}'
# the actual cpu/memory limits and requests currently in effect in the cgroup

If restartCount jumps right after the patch, the resize went through RestartContainer rather than applying silently in place — check the container’s resizePolicy and which cgroup driver that node is running.

Bottom line

In-Place Pod Resize closes off a whole class of unnecessary pod churn — but only where you’ve already confirmed cgroups v2 and a current CRI are actually underneath it, not a legacy node that will quietly fall back to a restart exactly when it goes unnoticed. If your stack leans on a container restart as a side-effect way to reclaim leaked memory, VPA’s InPlaceOrRecreate won’t respect that habit — it’ll simply stop triggering it unnecessarily. Before turning resize on everywhere, decide explicitly where resizePolicy should stay RestartContainer rather than silently inheriting the default.


Share this post:

Next Post
Helm, grown up: OCI registries, dependencies, and charts without the pain