Definition
These are four different autoscalers operating at different layers.
- HPA (Horizontal Pod Autoscaler) – changes the number of Pod replicas based on CPU, memory, or custom/external metrics.
- VPA (Vertical Pod Autoscaler) – changes the CPU and memory requests and limits of each Pod based on observed usage.
- Cluster Autoscaler – changes the number of nodes when Pods cannot be scheduled for lack of capacity.
- KEDA (Kubernetes Event-Driven Autoscaling) – scales replicas based on external event sources like Kafka lag or SQS queue depth, and can scale to zero.
- Conflict rule – HPA and VPA must not both act on the same resource metric for the same workload.
At the National Bank of India, HPA is opening more identical counters when the queue of customers grows. VPA is giving each existing teller a bigger desk and more tools because their work got heavier. Cluster Autoscaler is building more floors in the branch when there is no room for new counters. KEDA is watching the overnight batch of pending settlements in a queue and opening counters exactly in proportion to how many files are waiting, even down to zero counters when the queue is empty.
Understanding the Concepts
What is the Horizontal Pod Autoscaler (HPA)?
The HPA watches a metric and compares it to a target. When average CPU utilization climbs above the target, the HPA increases the replica count. When it falls, the HPA scales back down within the configured minimum and maximum. HPA is the workhorse for stateless banking services.
# hpa.yaml - scale the transfer service based on CPU
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: transfer-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: transfer-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
kubectl get hpa
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# transfer-service-hpa Deployment/transfer-svc 45%/70% 3 20 5
What is the Vertical Pod Autoscaler (VPA)?
The VPA observes actual CPU and memory usage over time and recommends or applies right-sized requests and limits for each Pod. In Auto or Recreate mode it evicts and recreates Pods with the new values, causing a restart. Many teams run VPA in Off (recommendation-only) mode and apply the numbers at deploy time to avoid unexpected restarts.
# vpa.yaml - right-size the fraud scoring service
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: fraud-scoring-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: fraud-scoring
updatePolicy:
updateMode: "Off" # recommendation only, no restarts
resourcePolicy:
containerPolicies:
- containerName: fraud-scoring
minAllowed:
cpu: 100m
memory: 256Mi
maxAllowed:
cpu: "4"
memory: 4Gi
# View VPA recommendations
kubectl describe vpa fraud-scoring-vpa
# Recommendation:
# Container: fraud-scoring
# Lower Bound: CPU: 250m, Memory: 512Mi
# Target: CPU: 500m, Memory: 1Gi
# Upper Bound: CPU: 1500m, Memory: 2Gi
What is the Cluster Autoscaler?
The Cluster Autoscaler operates on the infrastructure layer. When the scheduler cannot place a Pod because no node has enough free CPU or memory, the Pod goes Pending. The Cluster Autoscaler sees the unschedulable Pods and adds a node to the node group. When nodes sit underutilized, it drains and removes them to save cost.
# A Pending Pod triggers the Cluster Autoscaler
kubectl get pods
# NAME READY STATUS RESTARTS
# transfer-service-6d4f9c7b8a-new 0/1 Pending 0
# Cluster Autoscaler logs show the scale-up
kubectl logs -n kube-system -l app=cluster-autoscaler --tail=20
# Scale-up: group: eks-banking-workers, size: 5 -> 6
# Pod transfer-service-6d4f9c7b8a-new is schedulable on new node
# After load drops, underutilized nodes are drained and removed
# Scale-down: removing node ip-10-0-1-50, utilization 15%
What is KEDA?
KEDA extends HPA with event-driven scalers. Instead of CPU, KEDA reads a source such as Kafka consumer lag, an SQS queue length, a Prometheus query, or a cron schedule. Crucially, KEDA can scale to zero when there is no work and back up when events arrive.
# keda-scaledobject.yaml - scale settlement consumer on Kafka lag
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: settlement-consumer
spec:
scaleTargetRef:
name: settlement-consumer
minReplicaCount: 0 # scale to zero when queue is empty
maxReplicaCount: 50
triggers:
- type: kafka
metadata:
bootstrapServers: kafka.banking:9092
consumerGroup: settlement-group
topic: settlement-files
lagThreshold: "100"
# Queue is empty - zero replicas
kubectl get deployment settlement-consumer
# NAME READY UP-TO-DATE AVAILABLE
# settlement-consumer 0/0 0 0
# Files land on the Kafka topic - KEDA scales up
kubectl get deployment settlement-consumer
# settlement-consumer 10/10 10 10
How do these autoscalers compare?
| Autoscaler | What it scales | Signal | Can scale to zero? |
|---|---|---|---|
| HPA | Pod replica count | CPU, memory, custom metrics | No (min 1) |
| VPA | Per-Pod CPU/memory requests | Observed usage | N/A (resizes, not scales) |
| Cluster Autoscaler | Node count | Pending Pods | Yes (removes idle nodes) |
| KEDA | Pod replica count | Events: Kafka lag, SQS, Prometheus, cron | Yes |
Can HPA and VPA be used together?
The hard rule is that they must not both act on the same resource metric for the same workload. If HPA adds replicas to reduce per-Pod CPU while VPA simultaneously shrinks per-Pod CPU requests, they oscillate against each other and the workload never stabilizes.
The safe patterns are:
# WRONG - both HPA and VPA manage CPU on the same Deployment
# HPA says: add replicas to reduce CPU
# VPA says: reduce CPU requests because usage is lower now
# Result: oscillation, workload never stabilizes
# CORRECT Pattern 1: VPA in recommendation-only mode
# VPA provides numbers, humans apply them at deploy time
# HPA owns runtime scaling on CPU
vpa:
updateMode: "Off" # recommendation only
hpa:
metrics:
- resource:
name: cpu
target:
averageUtilization: 70
# CORRECT Pattern 2: VPA manages memory, HPA scales on CPU
# Each autoscaler owns a distinct signal
vpa:
containerPolicies:
- containerName: fraud-scoring
controlledResources: ["memory"] # VPA only manages memory
hpa:
metrics:
- resource:
name: cpu # HPA only scales on CPU
How do HPA and Cluster Autoscaler work together?
HPA and Cluster Autoscaler are a natural pair. HPA creates more Pods for load, and if those Pods do not fit on existing nodes, the Cluster Autoscaler provisions more nodes. This is the standard setup for a banking service that handles salary-day spikes.
# Salary day: load increases
# Step 1: HPA scales transfer-service from 5 to 15 replicas
# Step 2: 10 new Pods go Pending because nodes are full
# Step 3: Cluster Autoscaler adds 3 new nodes
# Step 4: Pending Pods schedule on the new nodes
# Step 5: All 15 replicas are Running and serving traffic
kubectl get hpa
# transfer-service-hpa Deployment/transfer-svc 85%/70% 3 20 15
kubectl get nodes
# NAME STATUS ROLES
# ip-10-0-1-10 Ready <none> (existing)
# ip-10-0-1-20 Ready <none> (existing)
# ip-10-0-1-30 Ready <none> (new - added by Cluster Autoscaler)
What are common mistakes?
The most common mistake is combining HPA and VPA on the same CPU metric, which causes oscillation.
# Symptom of oscillation:
# HPA: scaling up from 5 to 8 replicas (CPU above target)
# VPA: reducing CPU request from 500m to 300m (CPU usage lower than request)
# HPA: scaling down from 8 to 4 replicas (CPU below target after VPA shrink)
# VPA: increasing CPU request from 300m to 600m (CPU usage higher now)
# ... endless cycle
Another mistake is setting HPA maxReplicas too low for a banking service that handles seasonal spikes. The HPA hits the ceiling and cannot scale further, causing latency to spike.
What should a senior engineer know?
VPA and Cluster Autoscaler combine naturally because larger Pod requests triggered by VPA simply mean the scheduler needs bigger nodes, which Cluster Autoscaler provides.
KEDA can also drive HPA objects directly using its ScaledObject, which means KEDA respects HPA stabilization windows and cooldown periods.
For cost optimization, KEDA's scale-to-zero capability saves significant compute costs for bursty background workers that only have work during business hours or batch windows.
# Check all autoscaler activity in a namespace
kubectl get hpa,vpa -n banking
kubectl get scaledobjects -n banking # KEDA objects
kubectl logs -n kube-system -l app=cluster-autoscaler --tail=20
Visual Overview
Autoscaler Layers
==================
Load increases on Transfer API
│
▼
┌──────────────────────────────────┐
│ HPA (Horizontal Pod Autoscaler) │
│ CPU > 70% target │
│ Scale: 5 → 15 replicas │
└────────────┬─────────────────────┘
│ 10 Pods go Pending
▼
┌──────────────────────────────────┐
│ Cluster Autoscaler │
│ Unschedulable Pods detected │
│ Scale: 3 → 6 nodes │
└────────────┬─────────────────────┘
│ Pods schedule on new nodes
▼
All 15 replicas Running and serving
KEDA Event-Driven Scaling
==========================
Kafka topic: settlement-files
│
│ lag = 0 → replicas = 0
│ lag = 500 → replicas = 10
│ lag = 2000 → replicas = 50
▼
┌──────────────────────────────────┐
│ KEDA ScaledObject │
│ Reads Kafka consumer lag │
│ Drives Deployment replica count │
│ Can scale to zero │
└──────────────────────────────────┘
VPA Right-Sizing (Recommendation Mode)
========================================
Observed usage over time:
CPU: 150m-400m Memory: 600Mi-900Mi
│
▼
VPA recommends:
CPU request: 500m Memory request: 1Gi
Engineer applies at next deploy
Key Takeaway
Kubernetes autoscaling happens at four distinct layers. The Horizontal Pod Autoscaler changes the number of replicas based on CPU, memory, or custom metrics. The Vertical Pod Autoscaler right-sizes each Pod's requests based on observed usage. The Cluster Autoscaler changes the number of nodes when Pods cannot be scheduled. KEDA reads an external event source like Kafka lag and drives replicas from that, including scaling all the way to zero.
These layers naturally combine in pairs. HPA and Cluster Autoscaler are a classic team: HPA creates more Pods, and if they do not fit, Cluster Autoscaler adds more nodes. KEDA sits on top of HPA machinery but adds event sources and scale-to-zero for bursty background workers.
The sharp answer on combining HPA and VPA is: never let both manage the same resource metric on the same workload. They coexist safely only when each owns a different signal, for example VPA managing memory in recommendation mode while HPA scales on CPU.
Remember one sentence: HPA scales out replicas, VPA scales up each Pod, Cluster Autoscaler scales out nodes, and KEDA scales on events down to zero.
---