Kubernetes In-Place Pod Resource Updates And Vertical Pod Resize

View Markdown

Listen to this article
AI-narrated article - 19 min 42 sec. Prose is preserved; code and diagram cues refer to the examples below.

Read the narration transcript - Open audio

Kubernetes In-Place Pod Resource Updates And Vertical Pod Resize

Kubernetes can now resize a running Pod's CPU and memory without replacing it. This deep dive explains the resize API, kubelet behavior, failure states, VPA integration, and production limits.

TL;DR

Kubernetes in-place Pod resource updates make container CPU and memory requests and limits mutable through the Pod resize subresource. Kubernetes records the requested values as desired state, while kubelet reports the resources actually configured on the existing node. CPU changes can usually avoid restarts; memory changes need deliberate resize policies and remain riskier, especially when decreasing limits. The feature is stable from Kubernetes 1.35, but it does not reschedule an undersized Pod, change its QoS class, update its controller template, or guarantee that application runtimes adapt automatically.

Kubernetes Pod changing CPU and memory resources in place while retaining its identity, network, and storage.
In-place Pod resize separates resource changes from Pod replacement: Kubernetes can change CPU and memory on the existing node while preserving Pod identity.

Vertical Scaling No Longer Has to Mean Pod Replacement 

For most of Kubernetes' history, changing a container's CPU or memory meant replacing its Pod. A Deployment or StatefulSet controller created a new Pod from an updated template; the scheduler placed it; kubelet rebuilt the sandbox; and the application started again. That model is simple, but it is expensive for stateful services, long-running jobs, game servers, pre-warmed workers, and latency-sensitive processes.

In-place Pod resize breaks that coupling. CPU and memory requests and limits become mutable on the existing Pod. The Pod stays on its node and retains its UID, IP address, volumes, and sandbox. Depending on the container's policy and runtime behavior, even the container can continue running.

The feature, also known as In-Place Pod Vertical Scaling, entered alpha in Kubernetes 1.27, became beta and default-enabled in 1.33, and graduated to stable in Kubernetes 1.35. Stable does not mean every resize is immediate or restart-free. It means the API and core behavior are production-grade; capacity, runtime, QoS, and workload-controller constraints still matter.

Version and client boundaries:

ComponentMinimum or feature stateWhy it matters
Kubernetes server1.33 or later for the current workflowThe feature was beta in 1.33 and stable in 1.35.
Kubernetes 1.35+Stable, enabled by defaultContainer-level CPU and memory resize is GA.
kubectl1.32 or laterOlder clients do not support --subresource=resize.
Kubernetes 1.36 Pod-level resizeSeparate beta featureIt resizes a Pod-level resource budget rather than only container fields.

Start with the official resize task and confirm the versions you actually operate:

kubectl version
kubectl get nodes \
  -o custom-columns='NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion'

The Core Contract: Desired Resources Are Not Actual Resources

The most important architectural change is not that resources became mutable. It is that Kubernetes now exposes a deliberate gap between what you requested and what kubelet has applied.

  • spec.containers[*].resources is the desired CPU and memory state.
  • status.containerStatuses[*].resources is the resource state currently configured for the container.
  • status.containerStatuses[*].allocatedResources is kubelet-confirmed allocation data used mainly for internal scheduling decisions.
  • status.conditions explains whether a resize is pending or in progress.

An accepted API patch only proves that Kubernetes stored the desired state. It does not prove that the node had capacity, the runtime changed its cgroups, or the application adapted.

Execution path from kubectl resize request through the API server, scheduler accounting, kubelet, container runtime, and cgroups.
Resize is asynchronous. The API server stores intent; kubelet on the existing node actuates it; container status reports what actually changed.

The request follows this path:

  1. A user or autoscaler writes the Pod's /resize subresource.
  2. Admission and API validation check the new values and preserve the Pod's QoS invariants.
  3. Kubernetes stores the desired resources in the Pod specification.
  4. Kubelet sees that desired and actual resources differ.
  5. Kubelet checks node capacity and coordinates the change with the container runtime.
  6. The runtime updates CPU and memory controls, typically Linux cgroups.
  7. Kubelet updates container status and Pod resize conditions.

During an incomplete resize, the scheduler accounts for the maximum of desired, allocated, and actual requests. That conservative rule prevents Kubernetes from treating capacity as free while different components still observe different generations of the Pod.

Create a Pod with explicit CPU and memory policies:

apiVersion: v1
kind: Pod
metadata:
  name: resize-demo
  namespace: qos-example
spec:
  containers:
    - name: application
      image: registry.k8s.io/pause:3.10
      resizePolicy:
        - resourceName: cpu
          restartPolicy: NotRequired
        - resourceName: memory
          restartPolicy: RestartContainer
      resources:
        requests:
          cpu: 500m
          memory: 256Mi
        limits:
          cpu: 500m
          memory: 256Mi

This is a Guaranteed-QoS Pod because request equals limit for both resources. Apply it and inspect the initial state:

kubectl create namespace qos-example
kubectl apply -f resize-demo.yaml
kubectl get pod resize-demo -n qos-example -o yaml

CPU and Memory Need Different Resize Policies

The per-container resizePolicy separates two decisions: which resource changes and whether that change requires a container restart.

resizePolicy:
  - resourceName: cpu
    restartPolicy: NotRequired
  - resourceName: memory
    restartPolicy: RestartContainer

NotRequired is the default. It tells kubelet to update the running container. RestartContainer restarts that container within the same Pod before applying the new value. It does not replace the Pod.

Decision diagram for CPU and memory resize policies followed by completed, deferred, infeasible, and in-progress outcomes.
Policy determines whether a container restart is allowed; node capacity and runtime actuation determine the resulting resize state.

CPU is usually the safer no-restart case. The runtime can adjust CPU shares, weight, or quota while the process continues. A larger CPU envelope still does not force the application to create more workers, and a smaller limit can increase throttling.

Memory is harder. Raising a cgroup memory limit does not guarantee that a managed runtime expands its heap. Lowering a limit can place current usage above the new boundary and trigger an OOM kill. Since Kubernetes 1.35, kubelet allows memory-limit decreases and performs a best-effort usage check when the policy is NotRequired, but the GA announcement explicitly warns that the check is not race-free.

Increase CPU without restarting the container:

kubectl patch pod resize-demo -n qos-example \
  --subresource resize \
  --patch '{"spec":{"containers":[{"name":"application","resources":{"requests":{"cpu":"1"},"limits":{"cpu":"1"}}}]}}'

Increase memory with the policy above:

kubectl patch pod resize-demo -n qos-example \
  --subresource resize \
  --patch '{"spec":{"containers":[{"name":"application","resources":{"requests":{"memory":"384Mi"},"limits":{"memory":"384Mi"}}}]}}'

The CPU change should leave restartCount unchanged. The memory change should increment it because RestartContainer applies to memory. If both resources change in one request, the container restarts when either policy requires it.

A Pod with restartPolicy: Never cannot declare a resize policy that requires a restart. Every resizable resource in that Pod must use NotRequired.

A Resize Is an Asynchronous State Machine

Production automation must treat resize like reconciliation, not like a synchronous setter. Read the desired values, actual values, conditions, and generation together:

kubectl get pod resize-demo -n qos-example -o json | jq '{
  generation: .metadata.generation,
  observedGeneration: .status.observedGeneration,
  desired: [.spec.containers[] | {name, resources}],
  actual: [.status.containerStatuses[] | {name, resources, restartCount}],
  resizeConditions: [
    .status.conditions[]?
    | select(.type == "PodResizePending" or .type == "PodResizeInProgress")
  ]
}'

The main outcomes are:

Completed

Desired and actual resources match, the relevant resize condition has cleared, and status.observedGeneration shows that kubelet has acknowledged the current Pod generation.

PodResizePending with Deferred

The node cannot grant the request now, but the request might become feasible after another Pod terminates or shrinks. Kubelet retries deferred resizes. It prioritizes higher PriorityClass, then Guaranteed over Burstable at equal priority, then older deferred requests.

PodResizePending with Infeasible

The resize cannot be satisfied on the current node—for example, the requested CPU exceeds node capacity. Kubernetes does not automatically reschedule the Pod onto a larger node. Submit a feasible value or use higher-level automation that can replace the Pod or change cluster capacity.

PodResizeInProgress

Kubelet accepted the allocation, but runtime actuation is incomplete. The condition can include reason: Error and a message when the runtime cannot apply the change.

This distinction changes alerting. Do not page because spec and status differ for a few seconds. Alert when the gap persists beyond an operational threshold, when Infeasible appears, or when an in-progress resize reports an error.

The Limits That Matter in Kubernetes 1.36

The current Kubernetes documentation lists boundaries that determine whether in-place resize fits a workload:

  • Only CPU and memory are resizable.
  • Resize cannot change the Pod's original Guaranteed, Burstable, or BestEffort QoS class.
  • Guaranteed Pods must keep CPU and memory requests equal to limits.
  • BestEffort Pods cannot gain requests or limits through resize.
  • Non-restartable init containers and ephemeral containers cannot be resized; sidecar containers can.
  • A request or limit cannot be removed entirely after it is set; it can only change value.
  • Windows Pods do not support in-place resize.
  • Pods managed by static CPU Manager or static Memory Manager policies cannot be resized.
  • Pods using swap cannot resize memory requests unless memory uses RestartContainer.
  • Memory-limit reduction without restart remains best-effort and can still race with allocation.

There is also a critical controller boundary: resizing a Pod does not update its owning Deployment or StatefulSet template.

Deployment template: 500m CPU
Running Pod after resize: 1000m CPU
Replacement Pod from unchanged template: 500m CPU

If the new size must survive Pod replacement, update the controller template as well or use a system that reconciles both lifecycle stages. Be deliberate when a GitOps controller owns the manifest; otherwise, live and declared state can diverge in ways your next rollout silently reverses.

Kubernetes 1.36 also exposes a separate beta workflow for in-place Pod-level resource resize. Do not confuse that Pod-wide resource budget with the stable container-level mechanism explained here.

VPA Turns the Mechanism Into an Autoscaling Policy

Core Kubernetes provides the resize actuator but does not decide the correct size. Vertical Pod Autoscaler supplies recommendations and update policy.

Its relevant modes are:

VPA modeExisting Pod behavior
OffProduce recommendations only.
InitialApply recommendations only when a Pod starts.
RecreateEvict the Pod so its controller creates a replacement.
InPlaceOrRecreateAttempt in-place resize, then fall back to eviction when necessary.
InPlaceNever evict; wait and retry pending resize. Alpha in VPA 1.7.0.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: payments-api
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payments-api
  updatePolicy:
    updateMode: InPlaceOrRecreate
  resourcePolicy:
    containerPolicies:
      - containerName: application
        minAllowed:
          cpu: 250m
          memory: 256Mi
        maxAllowed:
          cpu: "4"
          memory: 4Gi
        controlledResources: [cpu, memory]
        controlledValues: RequestsAndLimits

InPlaceOrRecreate is the pragmatic default when availability matters but eventual convergence matters more: it avoids disruption when possible and retains an eviction fallback. InPlace is stricter about continuity, but an infeasible resize can remain pending indefinitely because that mode will not evict the Pod.

Coordinate VPA with Horizontal Pod Autoscaler. HPA commonly evaluates CPU utilization as usage divided by requested CPU. Changing the request changes that denominator, so an in-place vertical resize can alter horizontal scaling decisions even when traffic and raw CPU consumption stay constant. Assign each autoscaler clear ownership of metrics and resources; otherwise, two valid control loops can work against each other.

Why Kubernetes Built It This Way

Mutating the Pod specification directly without an explicit resize contract would hide too much. The /resize subresource creates a narrower authorization and validation surface. Separate desired and actual fields make asynchronous node actuation observable. Per-resource restart policies acknowledge that CPU and memory have different runtime semantics. Resize conditions distinguish a temporary capacity problem from an impossible request.

The design also preserves the scheduler's fundamental safety model. A Pod remains bound to one node, so kubelet cannot promise resources that the node does not have. Conservative accounting protects the cluster while control plane, kubelet, and runtime converge. The trade-off is intentional: in-place resize minimizes disruption, but it is not rescheduling, node autoscaling, preemption, or application-level reconfiguration.

That boundary makes the feature composable. VPA can decide values and fallback behavior. A node autoscaler can add capacity. An application controller can adapt worker counts or heap settings. Kubernetes core provides a reliable actuator and an observable state machine instead of pretending one resize primitive solves the entire vertical-scaling problem.

A Production Rollout Checklist

Before enabling automated in-place resizing across a fleet:

  1. Confirm API server, kubelet, container runtime, and kubectl versions.
  2. Inventory QoS classes, static CPU/Memory Manager use, swap, Windows workloads, init containers, and sidecars.
  3. Define CPU and memory resizePolicy explicitly rather than relying on defaults.
  4. Test scale-up, scale-down, restart-required memory changes, Deferred, and Infeasible behavior.
  5. Compare desired and actual resource fields in monitoring.
  6. Alert on persistent generation gaps and resize error conditions.
  7. Verify that application runtimes use newly available CPU or memory as expected.
  8. Decide whether controller templates, GitOps state, or VPA own durable resource values.
  9. Test HPA and VPA together under stable traffic and changing recommendations.
  10. Keep disruption budgets and a recreate fallback for workloads where convergence outranks zero restart.

Clean up the manual example when finished:

kubectl delete namespace qos-example

Frequently Asked Questions

Q: What are Kubernetes in-place Pod resource updates?

A: They let Kubernetes change a running container's CPU and memory requests or limits through the Pod /resize subresource. The Pod remains on its current node and retains its identity, IP address, sandbox, and mounted volumes.

Q: Does an in-place Pod resize restart the container?

A: Not necessarily. resizePolicy can use NotRequired to update the running container or RestartContainer to restart that container. If no policy is specified for a resource, Kubernetes defaults to NotRequired.

Q: How do I verify that a Pod resize succeeded?

A: Compare desired values in spec.containers[*].resources with actual values in status.containerStatuses[*].resources. Inspect PodResizePending, PodResizeInProgress, status.observedGeneration, and the container restart count before declaring success.

Q: Does resizing a Pod update its Deployment or StatefulSet?

A: No. The resize subresource changes one Pod, not the owning controller's template. Update the template or use a reconciling autoscaler if replacement Pods must inherit the new resources.

Q: Can VPA resize Pods without evicting them?

A: Yes. InPlaceOrRecreate first attempts in-place resize and can fall back to eviction. VPA's alpha InPlace mode never evicts; it keeps waiting and retrying if a resize is deferred, in progress, or infeasible.

Primary Sources and Next Steps

Comments

Popular posts from this blog

Argo CD Auto-Sync and Health Checks: An Operator's Guide to Safe GitOps Reconciliation

Applying SRE Error Budgets to Services Running on EKS: SLIs, Burn Rates, and Release Decisions

Progressive Delivery with Canary and Blue-Green on Kubernetes: Argo Rollouts, Flagger, and Gateway API