Skip to content

Horizontal Pod Autoscaling

A HorizontalPodAutoscaler watches a metric against a target and adjusts a Deployment’s replicas between a minimum and maximum to keep that metric near the target — nothing more.

Two hard prerequisites before HPA does anything

Section titled “Two hard prerequisites before HPA does anything”

The HorizontalPodAutoscaler (autoscaling/v2) sounds simple, but it silently does nothing at all unless two things are true first.

  1. The workload’s Pods must have resource requests set. By default HPA scales on CPU or memory utilization, and utilization is calculated as a percentage of what a Pod requested — no requests means there is no denominator to compute a percentage from.
  2. metrics-server (or a custom/external metrics adapter) must be installed in the cluster. HPA does not measure anything itself; it reads metrics that something else collected and exposed through the metrics API. No metrics source, no scaling decision, ever.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: web-app:3.2.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
Terminal window
# metrics-server has to actually be running before HPA can read any utilization
kubectl get deployment metrics-server -n kube-system

With requests in place and metrics-server running, the HPA object itself just names a target and a metric:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
Terminal window
kubectl get hpa web-app
kubectl describe hpa web-app

This says: keep average CPU utilization across all web-app Pods near 60% of their requested CPU, scaling the Deployment anywhere between 3 and 20 replicas to hold that line. HPA also supports scaling on custom, non-resource metrics — queue depth, requests-per-second, and similar — through the custom.metrics.k8s.io and external.metrics.k8s.io APIs, for teams whose bottleneck is not CPU or memory at all.

It is worth being precise about the boundary here, because two other autoscalers sound similar but do completely different jobs:

  • HorizontalPodAutoscaler changes how many replicas a Deployment or StatefulSet has. That is the entire job.
  • VerticalPodAutoscaler (VPA) changes the requests/limits of the Pods that already exist, resizing them instead of adding more of them.
  • Cluster Autoscaler changes how many Nodes the cluster has, adding or removing machines underneath everything else (the next lesson covers this).
flowchart LR
  pods["Deployment Pods"] --> ms["metrics-server"]
  ms --> hpa["HorizontalPodAutoscaler controller"]
  hpa -->|"raise or lower"| replicas["Deployment.spec.replicas"]
metrics-server feeding Pod CPU utilization into the HPA controller, which adjusts a Deployment's replica count
What must be installed in a cluster before HPA can make any scaling decision at all?
Why are resource requests a hard prerequisite for CPU/memory-based HPA?
What does HorizontalPodAutoscaler change, compared to VerticalPodAutoscaler and Cluster Autoscaler?
Besides CPU and memory, what else can an HPA scale on?