BlogKubernetes

Kubernetes autoscaling with the Horizontal Pod Autoscaler

Scale on CPU, memory, or custom Prometheus metrics, and tune stabilization windows so the HorizontalPodAutoscaler won't flap under spiky, bursty load.

Dec 3, 2024·4 min readIntermediate·By the SecOpsLog team · command-tested

The Horizontal Pod Autoscaler adds and removes replicas to match load. The manifest itself is short; the parts teams get wrong are needing metrics-server installed, setting CPU requests so utilization math works, picking a metric that actually reflects user pain, and tuning behavior so replica counts do not yo-yo under spiky traffic.

HPA v2 supports Resource metrics (CPU, memory), custom metrics from Prometheus or other adapters, and external metrics like queue depth. CPU-target autoscaling is the starting point — but for a queue worker you scale on backlog, not processor idle time. The Kubernetes administration track covers requests, limits, and scheduling before you layer autoscaling on top.

Metrics → HPA → Deployment replicas

Without CPU requests, utilization is undefined and HPA never scales.

Metrics Server CPU / memory HPA controller targetUtilization × requests Deployment replicas N → M ! No requests = stuck utilization = usage / request missing request → 0% HPA sits at minReplicas 1 Scale the Deployment never scale Pods directly min / max + behavior windows stabilization avoids flapping 2 Verify kubectl top + HPA TARGETS load test until scale-up alert when pinned at max HPA reads metrics and patches Deployment.spec.replicas. Requests make utilization real. metrics → HPA → Deployment replicas → Pods
Metrics — serverHPA — decideScale — Deployment
HPA from zero to stable

Without requests on the Deployment, CPU utilization is undefined and HPA sits at minReplicas forever.

1Installmetrics-serverkubectl top works2Set CPU requestson every container3Create HPA v2min/max + target4Load testwatch TARGETS column5Tune behaviorstabilization windows6Custom metricPrometheus adapter7Alert on maxhit ceiling = under-provisioned

Prerequisites: metrics-server and requests

HPA reads current utilization from the metrics API. If kubectl top pods fails, HPA has nothing to scale on. Every container in the target Deployment needs a CPU request — utilization is measured as usage divided by request, not limit. A container with no request reports as 0% and the autoscaler never fires.

bash — confirm metrics are flowinglive
kubectl top pods -n prod -l app=api
NAME CPU(cores) MEMORY(bytes)
api-7d4k2 245m 128Mi
kubectl get deployment api -n prod -o jsonpath="{.spec.template.spec.containers[0].resources.requests.cpu}"
250m <- HPA divides usage by this

The basic CPU autoscaler

Target 70% average CPU utilization across pods. Set minReplicas high enough to survive a single-node loss and maxReplicas low enough to stay within your cloud budget and downstream connection limits. Leave headroom above minReplicas so the scaler has room to react before users notice latency.

hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
bash — watch it scalelive
kubectl get hpa api -n prod -w
NAME TARGETS MINPODS MAXPODS REPLICAS
api 42%/70% 3 20 3
api 88%/70% 3 20 6 (scaled up under load)
CPU is a proxy, not the goal
For a queue worker, scale on queue depth via an external metric. For an API, consider requests-per-second or p99 latency from Prometheus. Hitting maxReplicas means you are under-provisioned — alert on it.

Scale on a custom metric when CPU lies

CPU utilization stays flat when your bottleneck is I/O wait, thread pool exhaustion, or downstream latency. Install the Prometheus Adapter (or your cloud provider's equivalent), expose a metric like http_requests_per_second, and reference it in the HPA metrics array with type: Pods and averageValue. The scaler now reacts to traffic, not processor heat.

hpa-custom.yaml
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "500"

Stop the flapping with behavior policies

By default HPA scales up fast and down immediately — fine for steady load, painful for bursty traffic. A scale-down stabilization window waits before removing replicas, and a percent-based policy caps how many pods disappear per minute. Scale-up can stay aggressive; scale-down should be conservative so a brief lull does not halve your capacity right before the next spike.

hpa-behavior.yaml
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60

Where this goes next

HPA scales pods horizontally. Vertical Pod Autoscaler right-sizes requests over time. Cluster Autoscaler adds nodes when pending pods cannot fit. Together they are the three autoscalers — each solves a different bottleneck. The Kubernetes administration path covers all three with production tuning patterns.

Go deeper in a courseKubernetes administrationAutoscaling, scheduling, and running workloads at scale.View course

Related posts