4.3 Autoscaling Workloads & Clusters (HPA, VPA, Cluster Autoscaler, KEDA)
Key Takeaways
- The Horizontal Pod Autoscaler changes the replica count of a Deployment or StatefulSet based on observed metrics, and requires resource requests to be set to compute CPU utilisation.
- metrics-server supplies the resource metrics the HPA reads; without it, HPA reports unknown metrics and never scales.
- The Vertical Pod Autoscaler adjusts CPU and memory requests rather than replica count, and traditionally must evict a Pod to apply new values.
- The Cluster Autoscaler adds worker nodes when Pods are unschedulable and removes underutilised nodes, operating at the infrastructure layer rather than the workload layer.
- KEDA extends the HPA with event-driven scalers for queue depth, stream lag, and cron schedules, and is the standard way to scale a Kubernetes Deployment to zero.
4.3 Autoscaling Workloads & Clusters (HPA, VPA, Cluster Autoscaler, KEDA)
Quick Answer: Kubernetes autoscales at four distinct layers. The Horizontal Pod Autoscaler (HPA) adds or removes replicas. The Vertical Pod Autoscaler (VPA) raises or lowers a Pod's CPU and memory requests. The Cluster Autoscaler (CA) adds or removes worker nodes. KEDA extends the HPA to external event sources — queue depth, stream lag, cron — and is what lets a Deployment scale all the way to zero.
Elasticity is one of the defining properties in the CNCF cloud native definition, and KCNA reliably tests whether you can name the right autoscaler for a stated symptom. The discriminator is always the same: what is being changed — replicas, request sizes, or machines?
1. The Four Layers at a Glance
┌──────────────────────────────────────────────┐
EVENTS │ KEDA — scales on queue depth, lag, cron, │
│ and to/from zero; drives an HPA underneath │
└──────────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────────┐
REPLICAS │ HPA — more/fewer Pods (scale out / in) │
└──────────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────────┐
POD SIZE │ VPA — bigger/smaller requests (scale up/down)│
└──────────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────────┐
NODES │ Cluster Autoscaler / Karpenter — more/fewer │
│ machines when Pods cannot be scheduled │
└──────────────────────────────────────────────┘
2. Horizontal Pod Autoscaler
The HPA is a control-loop controller that periodically (about every 15 seconds) reads metrics and updates the replicas field of a scale target.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
The Scaling Formula
desiredReplicas = ceil( currentReplicas × ( currentMetric / targetMetric ) )
With 4 replicas averaging 90% CPU against a 70% target: ceil(4 × 90/70) = ceil(5.14) = 6 replicas.
Two Prerequisites Candidates Forget
metrics-servermust be installed. It is not part of a default cluster. It aggregates kubelet resource statistics and serves themetrics.k8s.ioAPI that bothkubectl topand the HPA consume. Without it, the HPA reports<unknown>and never acts.- The Pods must declare CPU/memory
requests. "70% utilisation" is meaningless without a denominator — utilisation is measured as a percentage of the request. A Pod with no CPU request cannot be scaled on CPU utilisation.
Metric Types
| Type | Source | Example |
|---|---|---|
| Resource | metrics-server | CPU or memory utilisation |
| Pods | Custom metrics API | Average requests-per-second per Pod |
| Object | Custom metrics API | Requests-per-second on an Ingress |
| External | External metrics API | Depth of a cloud message queue |
Damping Behaviour
Uncontrolled scaling oscillates. The HPA applies a stabilisation window — 300 seconds for scale-down by default, 0 for scale-up — so it reacts fast to load but retreats slowly. behavior.scaleDown and behavior.scaleUp policies let you tune both the window and the maximum rate of change.
3. Vertical Pod Autoscaler
The VPA answers a different question: are the requests on this workload right at all? It observes real consumption and recommends — or applies — new requests values.
Its three modes:
| Mode | Behaviour |
|---|---|
Off | Produce recommendations only. The safest and most common production use: a right-sizing report. |
Initial | Apply recommendations to newly created Pods only. |
Auto / Recreate | Actively update running Pods — historically by evicting and recreating them, since requests were immutable on a running Pod. |
That eviction behaviour is the VPA's defining limitation and a favourite exam point. It is also why running the HPA and the VPA on the same CPU/memory metric conflicts: one adds replicas while the other resizes and restarts them, and they fight. The supported combination is HPA on CPU with VPA restricted to memory, or VPA in Off mode used purely for recommendations.
Kubernetes has been moving toward in-place Pod resizing so that requests can be changed without recreating the Pod. Until that is universally available, treat "VPA evicts Pods to resize them" as the correct exam answer.
4. Cluster Autoscaler
The HPA and VPA both assume there is somewhere to put the Pods. The Cluster Autoscaler supplies that assumption by talking to the cloud provider's node group or auto-scaling group API.
- Scale up when one or more Pods are
Pendingbecause no node can satisfy them. The trigger is unschedulable Pods, not high CPU on existing nodes — this distinction is tested. - Scale down when a node has been underutilised for a sustained period (about 10 minutes by default) and everything on it can be rescheduled elsewhere. The node is cordoned, drained, and released.
A node will not be removed if it hosts Pods without a controller, Pods with restrictive PodDisruptionBudgets, Pods using local storage, or Pods carrying the cluster-autoscaler.kubernetes.io/safe-to-evict: "false" annotation.
Karpenter is a newer alternative that provisions right-sized instances directly from pending Pod requirements rather than scaling predefined node groups.
5. KEDA — Event-Driven Autoscaling
KEDA (Kubernetes Event-Driven Autoscaling) is a CNCF graduated project that fills the HPA's biggest gaps: external event sources and scale-to-zero.
A queue consumer has no meaningful CPU load while it waits, so CPU-based HPA cannot scale it. KEDA installs a ScaledObject CRD, polls the event source directly through one of its 60-plus scalers (Kafka, RabbitMQ, AWS SQS, Azure Service Bus, Redis, Prometheus, PostgreSQL, cron), and translates that signal into an HPA it manages on your behalf.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-processor
spec:
scaleTargetRef:
name: order-processor
minReplicaCount: 0 # scale to zero when the queue is empty
maxReplicaCount: 50
triggers:
- type: kafka
metadata:
topic: orders
lagThreshold: "100"
The minReplicaCount: 0 line is the headline capability: a plain HPA cannot go below 1, so scale-to-zero on Kubernetes means KEDA or Knative.
6. Diagnostic Table
| Symptom | Correct autoscaler |
|---|---|
| Latency rises at peak; CPU pinned at 95% | HPA on CPU |
| Pods are OOMKilled; requests were guessed | VPA recommendation, then fix the requests |
New Pods sit Pending with Insufficient cpu | Cluster Autoscaler |
| Kafka lag grows while consumer CPU stays flat | KEDA with a Kafka scaler |
| A nightly batch service should cost nothing when idle | KEDA cron scaler, or Knative |
kubectl top pods returns "Metrics API not available" | Install metrics-server |
An HPA targeting 70% CPU utilisation reports <unknown> for the current metric and never scales. What is the most likely cause?
What event causes the Cluster Autoscaler to add a node to the cluster?
A team needs a Kafka consumer Deployment to run zero Pods when the topic is empty and to scale up as consumer lag grows. Which component provides this?