How to Set Up Horizontal Pod Autoscaling in Kubernetes
A complete, working setup for scaling a deployment automatically based on CPU usage, including the metrics-server prerequisite most tutorials skip over.
This sets up Horizontal Pod Autoscaling (HPA) end to end — including the metrics infrastructure it depends on, which is the step most commonly missing when an HPA configuration silently does nothing.
Step 1: confirm metrics-server is installed
HPA needs resource metrics to make scaling decisions, and those come from metrics-server, which isn’t installed by default on every cluster:
kubectl get deployment metrics-server -n kube-system
If it’s missing, install it:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Step 2: verify metrics are actually flowing
kubectl top nodes
kubectl top pods
Don’t proceed until this returns real numbers. If it errors or returns nothing, HPA has no data to scale on and will silently fail to do anything useful, regardless of how correctly the HPA resource itself is configured.
Step 3: make sure your deployment declares resource requests
HPA calculates CPU utilization as a percentage of the pod’s requested CPU — without a request set, there’s no baseline to calculate a percentage against:
# deployment.yaml
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
resources:
requests:
cpu: "200m"
limits:
cpu: "500m"
Step 4: create the HPA resource
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
This scales myapp between 2 and 10 replicas, aiming to keep average CPU utilization around 70% of the requested 200m per pod.
kubectl apply -f hpa.yaml
Step 5: verify the HPA sees current metrics
kubectl get hpa myapp-hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
myapp-hpa Deployment/myapp 23%/70% 2 10 2
A TARGETS column showing <unknown>/70% instead of an actual percentage means metrics still aren’t reaching the HPA controller — revisit steps 1–3 rather than waiting, since this state won’t resolve on its own.
Step 6: generate load and watch it scale
kubectl run -it --rm load-generator --image=busybox -- /bin/sh -c \
"while true; do wget -q -O- http://myapp; done"
kubectl get hpa myapp-hpa --watch
Watching the REPLICAS column climb as TARGETS exceeds 70% confirms the whole pipeline — metrics, resource requests, and HPA configuration — is genuinely working end to end, not just correctly configured on paper.
Step 7: understand scale-down behavior
HPA deliberately scales down more conservatively than it scales up, to avoid flapping — by default waiting for a stabilization window before reducing replica count, configurable if the defaults don’t fit your workload:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
Step 8: consider scaling on custom metrics, if CPU isn’t the right signal
For workloads where CPU isn’t a good proxy for actual load (a queue-processing service, for instance, where queue depth matters more), HPA supports custom and external metrics via the metrics adapter pattern — a more advanced setup than CPU-based scaling, but built on the exact same HorizontalPodAutoscaler resource type shown here.
Why metrics-server is the step people actually get stuck on
An HPA resource can be configured perfectly and still do absolutely nothing if metrics-server isn’t installed or isn’t reporting correctly — this is, by a wide margin, the most common reason “I set up HPA and it’s not scaling” turns out to have a simple explanation. Checking kubectl top pods returns real numbers before troubleshooting the HPA resource itself saves considerable confusion.