What is HPA (HorizontalPodAutoscaler)?
HPA is a controller that automatically increases or decreases the replica count of a Deployment, ReplicaSet, or StatefulSet based on measured metrics (CPU, memory, custom metrics). When traffic spikes, it adds Pods to keep throughput up; when idle, it scales down to save cost.
- Horizontal scaling: Adjusts the number of Pods (vs. VPA, which adjusts a Pod's resource limits)
- The default sync period is 15 seconds; desired replicas are calculated from the ratio of current value to target value
- A metrics-server (or custom metrics API) that supplies measurements is required
For HPA to work, Pods must have resources.requests set. CPU utilization is calculated as usage / requests, so if requests are missing you get <unknown> and scaling stops.
Installing metrics-server
CPU/memory-based HPA scaling depends on the metrics.k8s.io API provided by metrics-server.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlOn test or private clusters, kubelet certificates are often self-signed, so metrics-server cannot connect. Only in that case, temporarily disable TLS verification.
kubectl patch deployment metrics-server -n kube-system --type=json \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'Verify the installation:
kubectl top nodes
kubectl top pods
# If values appear, metrics-server is healthyPrepare the target workload
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
replicas: 1
selector: { matchLabels: { app: php-apache } }
template:
metadata: { labels: { app: php-apache } }
spec:
containers:
- name: php-apache
image: registry.k8s.io/hpa-example
ports: [{ containerPort: 80 }]
resources:
requests: # Baseline for HPA calculation — required!
cpu: 200m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
spec:
selector: { app: php-apache }
ports: [{ port: 80 }]Create an HPA — CPU-based
Imperative (simple)
kubectl autoscale deployment php-apache \
--cpu-percent=50 --min=1 --max=10Declarative (autoscaling/v2, recommended)
The v2 API supports multiple metrics and scaling behavior tuning.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization # Percentage relative to requests
averageUtilization: 50
- type: Resource
resource:
name: memory
target:
type: AverageValue # Absolute value
averageValue: 200MiWhen multiple metrics are present, each is calculated and the largest replica count is chosen (conservative scale-up).
How scaling is calculated
Desired replicas are determined by the following formula:
desiredReplicas = ceil( currentReplicas * (currentMetric / targetMetric) )Example: 3 current Pods, average CPU utilization 90%, target 50% → ceil(3 * 90/50) = ceil(5.4) = 6 replicas.
Near the target (default ±10% tolerance), scaling is skipped to prevent flapping.
Verify with a load test
# Load generator
kubectl run -it --rm load --image=busybox:1.36 --restart=Never -- \
/bin/sh -c "while true; do wget -q -O- http://php-apache; done"
# Watch in another terminal in real time
kubectl get hpa php-apache --watch
# NAME TARGETS MINPODS MAXPODS REPLICAS
# php-apache 250%/50% 1 10 1 → 5 → ...After the load stops, it waits the default 5 minutes (stabilizationWindowSeconds) and then scales down gradually.
Tuning scaling behavior
To scale up quickly under sudden traffic and scale down cautiously, adjust behavior.
spec:
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # Scale up immediately
policies:
- type: Percent
value: 100 # Increase by at most 100% at a time
periodSeconds: 30
- type: Pods
value: 4 # Or at most 4 Pods
periodSeconds: 30
selectPolicy: Max # Choose the more aggressive of the two policies
scaleDown:
stabilizationWindowSeconds: 300 # Scale down after 5 minutes of stabilization
policies:
- type: Percent
value: 10 # Decrease by at most 10% at a time
periodSeconds: 60| Parameter | Meaning |
|---|---|
stabilizationWindowSeconds | Historical window used for the decision (prevents flapping) |
policy Percent | Ratio-based change limit |
policy Pods | Absolute count change limit |
selectPolicy | Max (aggressive) / Min (conservative) / Disabled |
Custom and external metrics (RPS, queue length)
When CPU is not enough, use Prometheus Adapter or KEDA to scale on application metrics.
metrics:
- type: Pods # Per-Pod custom metric
pods:
metric: { name: http_requests_per_second }
target:
type: AverageValue
averageValue: "100"
- type: External # External metric (e.g. SQS queue length)
external:
metric: { name: sqs_queue_length }
target:
type: AverageValue
averageValue: "30"For event/message-driven workloads, KEDA is more convenient than plain HPA: it supports scale-to-zero and many scalers (Kafka, RabbitMQ, Cron, and more).
Troubleshooting
kubectl describe hpa php-apache # Check events and Conditions| Symptom | Cause / Fix |
|---|---|
TARGETS shows <unknown> | metrics-server not running, or requests missing |
| FailedGetResourceMetric | metrics-server certificate or network issue |
| Not scaling up | maxReplicas reached, or within tolerance |
| Slow scale-down | Long scaleDown stabilizationWindow (expected) |
| Flapping | Increase the stabilizationWindow |
HPA and Cluster Autoscaler should be used together. If HPA adds Pods but nodes lack capacity, Pods stay Pending—so Cluster Autoscaler (or Karpenter) must add nodes automatically.
Summary
| Item | Key point |
|---|---|
| Prerequisites | metrics-server + Pod resources.requests |
| API version | autoscaling/v2 (multiple metrics and behavior) |
| Formula | ceil(current * currentValue/targetValue) |
| Metric types | Resource (CPU/Mem) / Pods / Object / External |
| behavior | Aggressive scaleUp + conservative scaleDown recommended |
| Event workloads | Consider KEDA (scale-to-zero) |
| When nodes are insufficient | Pair with Cluster Autoscaler/Karpenter |
The core of HPA is ratio calculation against requests. Set realistic requests, tune scaleUp to be fast and scaleDown to be slow, and also configure a node autoscaler so you can handle real traffic swings reliably.
AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us
Questions & answers
Ask a question about this guide. We'll review and reply.