Back to Kubernetes: Core Concepts
Q15Question 15 of 16

HPA vs VPA vs Cluster Autoscaler vs KEDA, and Combining HPA with VPA

Key Concepts
  • HPA scales the NUMBER of Pod replicas based on CPU, memory, or custom metrics.
  • VPA scales each Pod UP by adjusting its CPU and memory requests and limits based on observed usage.
  • Cluster Autoscaler scales the NUMBER of NODES when Pods cannot be scheduled for lack of capacity.
  • KEDA scales replicas based on external event sources like Kafka lag or queue depth, and can scale to zero.
  • HPA and VPA must not both manage the same CPU or memory metric on the same workload, or they oscillate.
  • HPA and Cluster Autoscaler are a natural pair: HPA creates more Pods, Cluster Autoscaler adds more nodes.

Definition

HPA scales replicas out, VPA scales each Pod up, Cluster Autoscaler scales nodes, KEDA scales on events to zero, and HPA plus VPA on the same CPU metric conflict.

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.

YAML
# 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
BASH
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.

YAML
# 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
BASH
# 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.

BASH
# 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.

YAML
# 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"
BASH
# 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:

YAML
# 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.

BASH
# 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.

BASH
# 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.

BASH
# 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate