Skip to content
daniel@cosenza:~/blog
SRE & DevOpsHow-To September 6, 2025 3 min read

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.