Skip to content
Cloud & Infra Intermediate Tutorial

Right-Size GKE Pods Automatically with the Vertical Pod Autoscaler

Deploy GKE's built-in VPA in recommendation and auto modes to stop over-provisioning CPU and memory.

Ji-ho Choi
Ji-ho Choi
Security & Cloud Editor · Jul 30, 2026 · 5 min read
Right-Size GKE Pods Automatically with the Vertical Pod Autoscaler

What you'll build

A GKE cluster running Google's managed Vertical Pod Autoscaler, first in recommendation-only mode to see how badly a workload is over-provisioned, then in auto mode so VPA rewrites CPU and memory requests for you — the fastest structural fix for a cluster bill inflated by guessed resource requests.

Prerequisites

Verified July 2026 against GKE 1.35 (Regular channel default 1.35.6-gke.1127000) and the autoscaling.k8s.io/v1 VPA API.

  • A Google Cloud project with billing enabled and the GKE API on: gcloud services enable container.googleapis.com
  • The gcloud CLI installed and authenticated (gcloud auth login, gcloud config set project YOUR_PROJECT)
  • kubectl plus the GKE auth plugin: gcloud components install kubectl gke-gcloud-auth-plugin
  • This tutorial creates a small Standard cluster (two e2-medium nodes), which bills while it exists — the delete command is in Next steps

On Autopilot clusters VPA is enabled by default, so you can skip step 1 and go straight to creating VPA objects.

1. Create a cluster with VPA enabled

gcloud container clusters create vpa-demo \
  --location=us-central1-a \
  --num-nodes=2 \
  --enable-vertical-pod-autoscaling

gcloud container clusters get-credentials vpa-demo --location=us-central1-a

For an existing Standard cluster, enable it in place — note this can recreate the control plane, so the API server may be briefly unavailable:

gcloud container clusters update vpa-demo --location=us-central1-a \
  --enable-vertical-pod-autoscaling

Unlike open-source VPA, there's nothing to install: GKE runs the recommender, updater, and admission controller as managed control-plane components (you won't see vpa-* pods in kube-system), and its recommendations account for node sizes and resource quotas.

2. Deploy a deliberately over-provisioned workload

Save as deployment.yaml — 500m CPU and 512Mi for an idle nginx is exactly the kind of "guess big to be safe" request VPA exists to correct:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:stable
        resources:
          requests:
            cpu: 500m
            memory: 512Mi

Two replicas matter: on GKE 1.35.1 and earlier, VPA won't evict pods from a Deployment with fewer than two live replicas by default (1.35.2+ lowers the default to one).

kubectl apply -f deployment.yaml

3. Create a VPA in recommendation mode

Save as vpa.yaml. updateMode: "Off" means VPA computes recommendations but touches nothing — always start here so you can sanity-check the numbers before handing over control. The maxAllowed block is a guardrail capping what VPA may ever set:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
    - containerName: nginx
      maxAllowed:
        cpu: "1"
        memory: 1Gi
kubectl apply -f vpa.yaml

4. Read the recommendation

Give the recommender a few minutes to gather usage metrics, then:

kubectl describe vpa nginx-vpa

The interesting part is Status.Recommendation:

Recommendation:
  Container Recommendations:
    Container Name:  nginx
    Lower Bound:
      Cpu:     25m
      Memory:  262144k
    Target:
      Cpu:     25m
      Memory:  262144k
    Upper Bound:
      Cpu:     429m
      Memory:  1Gi

Target is what VPA would set (25m CPU and ~256Mi is GKE's floor — idle nginx uses almost nothing, so we're paying for 20x the CPU we need). Lower Bound and Upper Bound are the confidence interval; the upper bound starts wide and tightens as history accumulates, which is why recommendations improve over the first day or two.

5. Switch to auto mode

Once the target looks sane, let VPA act on it. Recreate evicts pods and lets the admission controller rewrite their requests on the way back in (Auto currently behaves identically; limits are scaled proportionally if you set them):

kubectl patch vpa nginx-vpa --type merge \
  -p '{"spec":{"updatePolicy":{"updateMode":"Recreate"}}}'

Watch the rollout — the updater runs about once a minute and evicts gradually, respecting PodDisruptionBudgets:

kubectl get pods -l app=nginx -w

Verify it works

Check the requests on a freshly recreated pod:

kubectl get pods -l app=nginx \
  -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].resources.requests}{"\n"}{end}'

Expected output — requests now match the recommendation, not your manifest:

nginx-7f8d6c9b5d-k2xzq	{"cpu":"25m","memory":"262144k"}
nginx-7f8d6c9b5d-p9wlt	{"cpu":"25m","memory":"262144k"}

Each resized pod also carries a vpaUpdates annotation naming the VPA that modified it, and kubectl get vpa nginx-vpa should show a provided recommendation:

NAME        MODE       CPU   MEM       PROVIDED   AGE
nginx-vpa   Recreate   25m   262144k   True       12m

That's the win: this Deployment's CPU reservation dropped from 1000m to 50m across two replicas. Across a real cluster, freed requests let the cluster autoscaler shed nodes — which is where the actual cost reduction lands.

Troubleshooting

no matches for kind "VerticalPodAutoscaler" in version "autoscaling.k8s.io/v1" — the VPA CRDs aren't there because vertical Pod autoscaling isn't enabled on the cluster; run the gcloud container clusters update ... --enable-vertical-pod-autoscaling command from step 1 and wait for it to finish. Also check the apiVersion: it's autoscaling.k8s.io/v1, not autoscaling/v1 (that group is HPA's).

PROVIDED stays False and there's no recommendation — either the recommender simply needs a few more minutes of metrics, or the VPA isn't matching your pods. Run kubectl describe vpa nginx-vpa and look for the NoPodsMatched condition ("No pods match this VPA object"): it means targetRef doesn't exactly match the controller's kind and name. VPA only works with controller-managed workloads (Deployments, StatefulSets, ReplicaSets) — never standalone pods.

Recommendations exist but pods are never evicted in Recreate mode — you're likely running a single replica on GKE 1.35.1 or earlier, where the updater requires two live replicas by default. Scale to two, or explicitly opt in with spec.updatePolicy.minReplicas: 1 (accepting the downtime of the lone pod restarting).

Pods thrash between sizes after enabling VPA — you have an HPA scaling the same workload on CPU or memory; the two controllers fight. Don't combine them on the same metrics — use GKE's multidimensional Pod autoscaling instead, or point the HPA at custom/external metrics only.

Next steps

  • Try InPlaceOrRecreate mode (Preview, GKE 1.34.0-gke.2201000+), which resizes pods without restarting them where possible — it removes the eviction cost that makes Recreate awkward for stateful or slow-starting workloads.
  • Set minAllowed and controlledResources in the container policy for finer control, e.g. autoscale memory only for JVM-adjacent workloads where VPA's memory signal is unreliable.
  • Roll VPA out fleet-wide in Off mode first and export the recommendation-vs-request gap to see your total over-provisioning before flipping anything to auto.
  • Clean up: gcloud container clusters delete vpa-demo --location=us-central1-a

Sources & further reading

  1. Vertical Pod autoscaling — docs.cloud.google.com
  2. Scale container resource requests and limits — docs.cloud.google.com
  3. gcloud container clusters create — docs.cloud.google.com
  4. GKE release notes — docs.cloud.google.com
Ji-ho Choi
Written by
Ji-ho Choi · Security & Cloud Editor

Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading