Chapter 41 of 47 · Scheduling and scaling

Autoscaling: HPA, VPA and cluster autoscaling

Traffic is never flat. Your blog is quiet at night and busy when a post goes viral. Paying for peak capacity all day is wasteful; running too little at peak means slow pages. Kubernetes can grow and shrink things automatically, at three levels: more Pods, bigger Pods, or more machines.

Three ways to grow

Think of a restaurant on a busy evening. You can call in more waiters (horizontal), give each waiter a bigger tray (vertical), or open the terrace for more tables (more room).

Horizontal, vertical and cluster scalingThe HPA adds more Pods of the same size. The VPA makes each Pod bigger. The cluster autoscaler adds nodes when Pods cannot fit. HPA: more Pods VPA: bigger Pods Cluster autoscaler: more nodes node-1 node-2
ToolChangesGood for
HPA (Horizontal Pod Autoscaler)Number of replicasStateless apps like posts and comments: the usual choice
VPA (Vertical Pod Autoscaler)CPU/memory requests of each PodApps that cannot run as many copies, and finding the right requests
Cluster autoscaler / KarpenterNumber of nodesMaking room when Pods are Pending, and removing idle machines

Manual scaling first

Before automating, remember the manual way (chapter 10):

$ kubectl scale deployment comments-depl --replicas=5     # imperative
# or change replicas: 5 in the YAML and kubectl apply     # declarative

Autoscaling simply does this for you, all day, based on numbers it measures.

HPA: more Pods when busy

The HPA checks every 15 seconds or so: "how busy are the Pods, compared to what I want?" and changes replicas on the Deployment. Click the parts:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: comments
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: comments-depl
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

averageUtilization: 70

"Keep the average CPU use at about 70% of the requested CPU." That is why the HPA needs resources.requests.cpu set (chapter 22); without requests it cannot compute a percentage.

It needs the metrics-server add-on to read CPU numbers (the same one behind kubectl top). Quick start without YAML:

$ kubectl autoscale deployment comments-depl --cpu-percent=70 --min=2 --max=8
$ kubectl get hpa
NAME       REFERENCE                  TARGETS   MINPODS   MAXPODS   REPLICAS
comments   Deployment/comments-depl   92%/70%   2         8         3

Try the HPA maths

The rule is simple: new replicas = current replicas × (current use ÷ target), rounded up, then kept between min and max. Move the sliders:

Calm, not jumpy

CPU numbers wiggle. An autoscaler that reacts to every wiggle would add and remove Pods all the time ("flapping"). The HPA protects you in two ways:

spec:
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0        # react to spikes at once
      policies:
        - type: Pods
          value: 4                         # but add at most 4 Pods
          periodSeconds: 60                #   per minute
    scaleDown:
      stabilizationWindowSeconds: 300      # wait 5 minutes of calm before removing Pods

Remember: a new Pod needs time to start and become ready (chapter 13). Autoscaling smooths out the day; it cannot absorb a spike that arrives in one second. Keep some headroom with minReplicas.

Scaling on other numbers

CPU is not always the best signal. A comments service waiting on a database barely uses CPU, but its queue grows. autoscaling/v2 can scale on several kinds of metrics:

- type: Resource
  resource:
    name: memory
    target: { type: Utilization, averageUtilization: 80 }

CPU or memory, from metrics-server. Built in.

With several metrics, the HPA computes a replica count for each and takes the highest.

KEDA and scale to zero

The HPA never goes below 1 replica. But a worker that processes moderation events has nothing to do at 3 a.m. KEDA (Kubernetes Event-Driven Autoscaling) is an add-on that watches event sources (queues, Kafka topics, databases, cron schedules…) and can scale a Deployment to zero and back:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: moderation-worker
spec:
  scaleTargetRef:
    name: moderation-depl
  minReplicaCount: 0                 # nothing running when the queue is empty
  maxReplicaCount: 10
  triggers:
    - type: rabbitmq
      metadata:
        queueName: comments-to-moderate
        value: "20"                  # one Pod per 20 waiting messages

Under the hood KEDA feeds an HPA for the 1-to-N part and handles 0-to-1 itself. Knative offers similar scale-to-zero for HTTP services: requests wait briefly while the first Pod starts.

Scale to zero has a price: the first request or message after a quiet period waits for a cold start. Fine for background workers, often not for a page a user is waiting on.

VPA: bigger Pods instead of more

Getting requests right is hard (chapter 22). The Vertical Pod Autoscaler (an add-on) watches real usage over days and recommends, or sets, better requests.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: query
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: query-depl
  updatePolicy:
    updateMode: "Off"          # only recommend, change nothing
updateModeWhat VPA does
OffOnly writes recommendations. Read them with kubectl describe vpa query and copy them into your YAML. The safest start.
InitialSets requests only when a Pod is created; running Pods are left alone.
Recreate / AutoAlso fixes running Pods, by evicting and recreating them with new requests. Expect restarts.
Do not let HPA and VPA fight over the same number. If HPA scales on CPU and VPA changes CPU requests, each move confuses the other. Use VPA in Off mode for advice, or let VPA handle memory while HPA scales on CPU or a custom metric.

Cluster autoscaling: more nodes

The HPA can ask for 8 Pods, but if the nodes are full, the new Pods stay Pending. The Cluster Autoscaler (or Karpenter on AWS and others) watches for exactly that:

  1. comments-depl   3 → 6 replicas   (HPA)
    comments-…-x2k  Pending   0/2 nodes are available: 2 Insufficient cpu.

    The HPA added Pods; there is no room for them (chapter 40).

  2. cluster-autoscaler: 3 unschedulable Pods would fit on a new node
    cloud: creating VM in node group "general"…

    The autoscaler asks the cloud for another machine. This takes a minute or two.

  3. node-3   Ready
    comments-…-x2k  Running   node-3

    The new node joins; the scheduler places the waiting Pods.

  1. node-3   CPU requested: 12%   (quiet night)

    A node has been mostly idle for a while (about 10 minutes by default).

  2. cluster-autoscaler: Pods on node-3 fit elsewhere → draining node-3

    Its Pods are evicted gracefully and rescheduled elsewhere. A PodDisruptionBudget can protect important apps here (chapter 39).

  3. cloud: deleting VM node-3

    The machine is removed and stops costing money.

Cluster autoscaling reacts to Pending Pods and requests, not to CPU usage. Correct requests matter here too.

All three together

  1. Set honest requests, using VPA in Off mode as an advisor.
  2. Let the HPA add and remove Pods as traffic moves, with sensible min/max.
  3. Let the cluster autoscaler add nodes when Pods are Pending and remove idle ones.
  4. Use KEDA for queue workers that can sleep at zero.

Recap

Quick check

kubectl get hpa shows TARGETS <unknown>/70% and nothing scales. Two likely causes?
Show answer

metrics-server is not installed, or the Pods have no resources.requests.cpu, so a percentage cannot be calculated.

The HPA sets 6 replicas, but after every deploy you briefly have 2 again. Why?
Show answer

The Deployment YAML still has replicas: 2, and kubectl apply resets it. Remove replicas from the Deployment and let the HPA own it.

Autoscaling: common questions

Why does my HPA show unknown targets?

Usually metrics-server is missing, or the Pods have no CPU request, so the utilization percentage cannot be calculated.

Can I use HPA and VPA together?

Not on the same metric. Let VPA only recommend, or let VPA manage memory while HPA scales on CPU or a custom metric.