Kubernetes ships with two native autoscaling mechanisms and one widely-adopted extension. Most teams start with HPA, hit its limits, reach for VPA, find out they conflict, and then wonder why their cluster costs are climbing. The answer is usually KEDA. Here is the full picture.
HPA: Scale Out When Load Spikes
The Horizontal Pod Autoscaler scales the number of pod replicas based on metrics — CPU utilisation by default, but custom metrics from Prometheus or the Metrics Server are equally supported.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5-min cooldown
policies:
- type: Percent
value: 20
periodSeconds: 60
The behavior block is critical and often omitted. Without it, HPA will scale down aggressively and cause brief unavailability during traffic dips. The stabilizationWindowSeconds prevents rapid oscillation.
Use HPA when: Your workload scales predictably with CPU or request rate, and adding more pod replicas is the right response to load.
VPA: Right-Size Your Resource Requests
The Vertical Pod Autoscaler adjusts the CPU and memory requests and limits of individual pods. It does not add pods — it makes existing pods use the right amount of resources.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api
updatePolicy:
updateMode: "Off" # Recommendation only; don't auto-apply
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 4
memory: 4Gi
Start with updateMode: "Off" and read VPA’s recommendations for a week before switching to Auto. VPA in auto mode restarts pods to apply new resource values — this is disruptive and needs testing.
The HPA/VPA conflict: Do not use both on the same deployment with CPU as the target metric. VPA will adjust requests, which changes what HPA sees as “utilisation”, causing them to fight. The safe pattern is VPA for memory and HPA for CPU, or run VPA in Off mode for recommendations only.
KEDA: Scale on Any External Signal
KEDA (Kubernetes Event-Driven Autoscaler) fills the gap HPA leaves open: scaling based on anything that is not a resource metric — a queue length, a Kafka lag, a cron schedule, an HTTP request rate, a Prometheus query.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: worker-scaledobject
spec:
scaleTargetRef:
name: message-worker
minReplicaCount: 0 # Scale to zero when idle
maxReplicaCount: 50
triggers:
- type: rabbitmq
metadata:
host: amqp://rabbitmq.default.svc:5672
queueName: jobs
queueLength: "20" # 1 replica per 20 messages
The minReplicaCount: 0 is KEDA’s killer feature for batch workloads. A deployment that does nothing when the queue is empty should run zero pods, not two. This alone can cut compute costs by 40% for event-driven workloads.
KEDA is a CNCF project at Graduated status (as of early 2026), which means it is production-ready and unlikely to have breaking changes.
Decision Matrix
| Signal driving scale | Right tool |
|---|---|
| CPU utilisation | HPA |
| Memory usage | VPA (recommendation mode) |
| Request rate (HTTP) | HPA with custom metrics or KEDA |
| Queue length (RabbitMQ, Kafka, SQS) | KEDA |
| Cron-based schedule | KEDA |
| Scale to zero | KEDA |
| Right-sizing pod resources | VPA |
Training
The Kubernetes training at Sphinx-Digital covers HPA, VPA, and KEDA in a real EKS cluster environment, alongside RBAC, network policies, and production-grade ingress configuration.