A pod’s CPU and memory requests are almost always set once, when the manifest gets written, from an estimate, then never revisited despite months of drift between real consumption and that initial value. The Vertical Pod Autoscaler exists precisely to close that gap, provided you understand why its most automatic mode isn’t the one to enable first.

A different question than the HPA’s

The HPA answers “how many pods are needed?” by adding or removing replicas based on a load metric. The VPA answers a distinct question: “is each pod the right size?”, adjusting CPU and memory requests from consumption actually observed over time, rather than a fixed estimate made at manifest creation.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: checkout-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout
  updatePolicy:
    updateMode: "Off"

Three modes, only one safe to enable without thinking

Off mode computes a recommendation without ever applying it: the observation mode, letting you see what the VPA proposes before letting it touch anything. Initial mode applies the recommendation only when a new pod is created, never touching a pod already running. Auto mode goes further: it restarts an existing pod to apply a new recommendation, meaning an interruption on every adjustment, potentially frequent under a load that varies a lot.

# The recommendation is visible without ever
# having been applied, even in Off mode
kubectl describe vpa checkout-vpa | grep -A6 "Recommendation"

Enabling Auto without checking that these restarts are tolerable (a service with no buffer in front of it, no multiple replicas to absorb one interruption) turns an optimization tool into a source of self-inflicted instability.

The conflict with HPA, often overlooked

VPA and HPA act on different axes but can step on each other: HPA computes its load target from the pod’s CPU request, while VPA modifies that same request. If both target the same metric (CPU, typically) at once, each invalidates the other’s basis for calculation, a cycle that can oscillate without ever stabilizing.

# Safe combination: VPA on memory only,
# HPA on CPU, each on its own axis
resourcePolicy:
  containerPolicies:
    - containerName: "*"
      controlledResources: ["memory"]

The pattern that avoids the conflict: use VPA in Off mode to size requests once with real data, apply that recommendation manually, then let HPA manage replica count on that stabilized base — or restrict VPA to a resource HPA isn’t watching (controlledResources: ["memory"] when HPA scales on CPU).

What VPA doesn’t fix

VPA adjusts requests from past consumption, which assumes a relatively stable or cyclical load: a sharp, one-off spike (a data import running once a day) skews the recommendation if the algorithm doesn’t have enough history to tell the exceptional spike apart from normal load. VPA-based sizing remains an aid, not a replacement for judgment about the actual nature of an application’s load.

Takeaway

VPA answers a question HPA never asks: is each pod the right requests size, rather than are there enough of them. Off mode (recommendation only) is the systematic starting point before any automatic mode; Auto mode has a real interruption cost on every adjustment, never to be enabled without checking it’s tolerable. Combined with an HPA targeting the same metric, VPA can create a mutual invalidation cycle: separating them by resource, or sizing once and freezing, is the habit that avoids this trap, one of the fine-tuning steps that accompanies a Kubernetes migration once real production load has been observed.