63.5 Horizontal Pod Autoscaler: scale pods on CPU, memory and custom metrics
Why: the problem
Traffic is never flat. Hotstar sees a few lakh viewers on a normal evening and crores during an India versus Pakistan match. BookMyShow spikes the minute a big film opens bookings. If you size a Deployment for the peak, you pay for idle pods most of the day. If you size it for the average, the app falls over at the peak. The Horizontal Pod Autoscaler (HPA) fixes this by changing the replica count of a Deployment, StatefulSet or ReplicaSet automatically. How does it decide? Every 15 seconds the HPA controller in kube-controller-manager reads metrics, usually average CPU utilisation from metrics-server, and applies one formula: desiredReplicas = ceil(currentReplicas x currentValue / targetValue). If 4 pods average 90% CPU against a 60% target, it wants ceil(4 x 90 / 60) = 6 pods. It then clamps the answer between minReplicas and maxReplicas and writes it to the scale subresource, just like kubectl scale. Three things must be true for this to work. First, metrics-server must be running so kubectl top returns numbers (you installed it in 59.3; we reuse that file). Second, the pods must have CPU and memory requests, because utilisation is measured as a percentage of the request. No request means the HPA shows unknown and never scales. Third, the app must actually be able to run as many copies, which means stateless pods behind a Service. autoscaling/v2 lets you go further: scale on memory, on several metrics at once (the HPA takes the largest answer), and on custom or external metrics such as requests per second from Prometheus or SQS queue depth through KEDA. The behavior section controls speed: scale up fast when load arrives, scale down slowly so a short dip does not remove pods you need a minute later. The default scale-down stabilisation window is 300 seconds. In this lesson we install metrics-server, run a CPU-hungry app, attach an HPA, generate load, watch replicas climb and fall, add a memory metric, tune behavior, and see how custom metrics plug in.
What: every term in plain English
- HPA — Controller that changes replica count based on metrics.
- metrics-server — Cluster add-on that collects CPU and memory from kubelets for kubectl top and HPA.
- Utilization target — Average usage as a percentage of the pod's request.
- Scaling formula — desired = ceil(current x currentValue / targetValue).
- minReplicas / maxReplicas — Floor and ceiling the HPA never crosses.
- Scale subresource — API endpoint that HPA and kubectl scale both write to.
- behavior — autoscaling/v2 section for scale-up and scale-down policies.
- Stabilization window — Period over which the HPA picks the safest recommendation; 300 seconds for scale-down by default.
- Custom metrics — Per-pod metrics from an adapter such as Prometheus Adapter.
- External metrics — Metrics outside the cluster, such as SQS queue length.
- KEDA — Event-driven autoscaler that feeds external metrics to HPA and can scale to zero.
- TARGETS column — Shows current and target value in kubectl get hpa.
How: step by step
- Install metrics-server. kubectl top works.
- Deploy a CPU app with requests. php-apache style test app.
- Create an HPA at 50% CPU. kubectl autoscale or YAML.
- Generate load. Replicas climb.
- Stop load. Replicas fall after the window.
- Add memory and behavior. autoscaling/v2.
- Read how custom metrics plug in. Prometheus Adapter and KEDA.
- Clean up. Ready for VPA.
Example 1: CPU autoscaling from 1 to 8 pods
Goal. CPU autoscaling from 1 to 8 pods.
search-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: search-api
namespace: hpa
spec:
replicas: 1
selector:
matchLabels:
app: search-api
template:
metadata:
labels:
app: search-api
spec:
containers:
- name: api
image: registry.k8s.io/hpa-example
ports:
- containerPort: 80
resources:
requests:
cpu: 200m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: search-api
namespace: hpa
spec:
selector:
app: search-api
ports:
- port: 80
search-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: search-api
namespace: hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: search-api
minReplicas: 1
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
What each line does
hpa-example— Official test image that burns CPU on every request.requests.cpu 200m— 50% target means about 100m per pod.scaleTargetRef— The Deployment to scale.1 to 8— The HPA stays inside this range.Same as— kubectl autoscale deploy search-api -n hpa --cpu-percent=50 --min=1 --max=8.
Green output
$ kubectl apply -f metrics-server.yaml
$ kubectl top nodes
NAME CPU(cores) MEMORY(bytes)
ch63-worker 61m 412Mi
$ kubectl create ns hpa
$ kubectl apply -f search-api.yaml -f search-hpa.yaml
$ kubectl get hpa -n hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
search-api Deployment/search-api cpu: 1%/50% 1 8 1
Expected result. metrics-server is reporting, the HPA can read CPU (1% against a 50% target) and replicas stay at the minimum. Now we add load.
Example 2: Generate load and watch the HPA react
Goal. Generate load and watch the HPA react.
load.sh
kubectl run load -n hpa --image=busybox:1.36 --restart=Never -- sh -c '
while true; do wget -q -O- http://search-api >/dev/null; done'
kubectl get hpa search-api -n hpa -w
What each line does
Endless loop— One busybox pod sends requests as fast as it can.Scale up— Within a minute CPU passes 50% and replicas jump.Formula— 1 pod at 250% gives ceil(1 x 250 / 50) = 5.Stop load— kubectl delete pod load -n hpa.Scale down— After about 5 minutes of low CPU, replicas return to 1.
Green output
$ bash load.sh
NAME TARGETS REPLICAS
search-api cpu: 1%/50% 1
search-api cpu: 248%/50% 1
search-api cpu: 248%/50% 5
search-api cpu: 71%/50% 7
search-api cpu: 49%/50% 7
$ kubectl delete pod load -n hpa
$ kubectl get hpa search-api -n hpa -w
search-api cpu: 0%/50% 7
search-api cpu: 0%/50% 1
$ kubectl describe hpa search-api -n hpa | tail -3
Normal SuccessfulRescale New size: 5; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale New size: 7; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale New size: 1; reason: All metrics below target
Expected result. Replicas went from 1 to 5 to 7 and settled where CPU was just under 50%. After the load stopped, the HPA waited the stabilisation window and scaled back to 1.
Example 3: Memory, several metrics and tuned behavior
Goal. Memory, several metrics and tuned behavior.
search-hpa-v2.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: search-api
namespace: hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: search-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 100Mi
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 120
policies:
- type: Pods
value: 1
periodSeconds: 60
What each line does
Two metrics— The HPA computes replicas for each and uses the largest.AverageValue memory— Absolute target instead of a percentage.scaleUp 100% per 15s— Can double pods every 15 seconds during a spike.scaleDown 1 pod per 60s— Removes pods slowly, after a 2-minute window.minReplicas 2— Never fewer than two, for fault tolerance from 63.4.
Green output
$ kubectl apply -f search-hpa-v2.yaml
$ kubectl get hpa -n hpa
NAME TARGETS MINPODS MAXPODS REPLICAS
search-api cpu: 2%/60%, memory: 9Mi/100Mi 2 10 2
$ bash load.sh
search-api cpu: 230%/60%, memory: 11Mi/100Mi 4
search-api cpu: 115%/60%, memory: 11Mi/100Mi 8
Expected result. Both metrics show in TARGETS. Scale-up doubled pods quickly; after the load stops the scale-down removes one pod a minute instead of dropping straight to 2.
Every command, step by step (kind lab with kubectl)
-
metrics-server
kubectl apply -f metrics-server.yaml kubectl rollout status deploy/metrics-server -n kube-systemWhat it does: The file from 59.3.
deployment "metrics-server" successfully rolled out -
Top
kubectl top nodesWhat it does: Numbers, not errors.
ch63-worker 61m 412Mi -
App
kubectl create ns hpa kubectl apply -f search-api.yamlWhat it does: With requests.
deployment.apps/search-api created -
HPA
kubectl apply -f search-hpa.yamlWhat it does: 50% CPU.
horizontalpodautoscaler.autoscaling/search-api created -
Status
kubectl get hpa -n hpaWhat it does: Targets known.
search-api cpu: 1%/50% 1 8 1 -
Load
bash load.shWhat it does: Climbs.
search-api cpu: 248%/50% 5 -
Pod CPU
kubectl top pods -n hpaWhat it does: Spread over pods.
search-api-7f9c6d8b5-2xk9m 98m 12Mi -
Stop
kubectl delete pod load -n hpaWhat it does: Load gone.
pod "load" deleted -
Events
kubectl describe hpa search-api -n hpaWhat it does: Rescale reasons.
New size: 1; reason: All metrics below target -
v2
kubectl apply -f search-hpa-v2.yamlWhat it does: Memory and behavior.
horizontalpodautoscaler.autoscaling/search-api configured -
Clean up
kubectl delete ns hpaWhat it does: Keep metrics-server.
namespace "hpa" deleted
When it is red 1
TARGETS shows unknown instead of a CPU percentage
Cause. metrics-server missing or pods have no CPU request.
Fix, step by step
- kubectl top pods.
- Add resources.requests.cpu.
When it is red 2
FailedGetResourceMetric: unable to get metrics
Cause. metrics-server not ready or kubelet TLS problem on kind.
Fix, step by step
- Check its logs; on kind it needs --kubelet-insecure-tls.
When it is red 3
Replicas jump up and down every minute
Cause. Scale-down too fast.
Fix, step by step
- Raise scaleDown stabilizationWindowSeconds.
When it is red 4
HPA stuck at maxReplicas
Cause. Real load above capacity or a target too low.
Fix, step by step
- Raise maxReplicas, profile the app, check node capacity.
When it is red 5
Deployment replicas keep resetting after kubectl apply
Cause. replicas field in Git fights the HPA.
Fix, step by step
- Remove replicas from the Deployment YAML when an HPA owns it.
Practice tasks
Easy Task 1: Install
Why: Metrics.
What to do: Apply metrics-server.yaml.
- Wait 60s.
Solution
kubectl top nodesEasy Task 2: HPA
Why: Create.
What to do: kubectl autoscale the Deployment.
- --cpu-percent=50.
Solution
kubectl get hpa -n hpaEasy Task 3: Load
Why: Scale.
What to do: Run load.sh.
- Watch -w.
Solution
kubectl get hpa -n hpa -wMedium Task 4: Math
Why: Formula.
What to do: Predict replicas from TARGETS and check.
- ceil.
Solution
kubectl get hpa -n hpaMedium Task 5: No request
Why: Break.
What to do: Remove CPU requests and read TARGETS.
- unknown.
Solution
kubectl describe hpa -n hpaMedium Task 6: Memory
Why: v2.
What to do: Add the memory metric.
- AverageValue.
Solution
kubectl get hpa -n hpaMedium Task 7: Behavior
Why: Tune.
What to do: Make scale-down 1 pod per minute.
- policies.
Solution
kubectl get hpa search-api -n hpa -o yamlMedium Task 8: Replicas field
Why: GitOps.
What to do: Show apply resetting replicas, then remove the field.
- kubectl apply.
Solution
kubectl get deploy search-api -n hpaHard Task 9: Hotstar
Why: Main task.
What to do: Build the playback HPA.
- min 3 max 30.
Solution
kubectl describe hpa playback-api -n hpaHard Task 10: KEDA
Why: Read.
What to do: Write a KEDA ScaledObject for an SQS queue on paper.
- triggers aws-sqs-queue.
Solution
kubectl explain hpa.spec.metrics.externalHomework: try at home
For Hotstar's playback-api, write an HPA with min 3, max 30, CPU 60%, a memory guard, fast scale-up and a 5-minute slow scale-down. Prove it with load.sh and capture the SuccessfulRescale events.
Recap
- HPA changes replica count from metrics every 15 seconds.
- It needs metrics-server and CPU or memory requests on pods.
- desired = ceil(current x current value / target), clamped by min and max.
- autoscaling/v2 adds memory, multiple metrics, custom metrics and behavior tuning.
- Next lesson: VPA adjusts requests instead of replicas.
FAQ
Q. Can HPA scale to zero?
Not by default; minReplicas is at least 1. KEDA can scale to zero for event-driven workloads.
Q. HPA on memory for Java apps?
Be careful: JVM memory rarely goes down after load, so memory-based scale-down may never happen. Prefer CPU or request rate.
Q. How do I scale on requests per second?
Expose a metric to Prometheus, install Prometheus Adapter, and use type Pods with that metric name.
Q. Does HPA work with StatefulSets?
Yes, anything with a scale subresource.
Q. Can I use HPA and VPA together?
Not on the same CPU or memory metric; see 63.6.
Q. How fast does it react?
About 15 seconds to read metrics, plus pod start time, plus readiness.
Q. Does EKS include metrics-server?
It is available as an EKS add-on; install it once per cluster.
Q. Why did my HPA not scale even though one pod is at 100% CPU?
The HPA uses the average across all pods. If one pod is hot and four are idle, the average may sit under the target. Look for uneven load, such as sticky sessions from 62.8 or a single long-running request type, and fix the balancing first.
Q. Should minReplicas be 1 in production?
Rarely. One replica has no fault tolerance, so a single pod kill or node drain takes the service down. Use at least 2, and 3 across zones for important services, and raise it before known events like a sale or a match.
Q. Can I see what the HPA will do before it acts?
kubectl describe hpa shows current metrics, conditions such as AbleToScale and ScalingLimited, and recent events. Those conditions explain most surprises.
Interview questions
-
How does the HPA calculate replicas?
Model answer: desired = ceil(currentReplicas x currentMetric / targetMetric), then bounded by min and max and smoothed by behavior policies and stabilisation windows.
-
Why does an HPA show unknown targets?
Model answer: No metrics source, or pods lack resource requests for the metric.
-
What is the behavior section for?
Model answer: Controlling scale-up and scale-down speed with policies and stabilisation windows to avoid flapping.
-
How do you autoscale on queue length?
Model answer: External metrics via KEDA or a metrics adapter, for example SQS ApproximateNumberOfMessages.
-
HPA reached max but latency is still high. What next?
Model answer: Check node capacity and Pending pods (cluster autoscaling, 63.7), downstream bottlenecks like the database, and whether the CPU target reflects the real limit.
Quick quiz
Q1. Where does HPA get CPU numbers?
metrics-server.
Q2. Why are requests required?
Utilisation is a percentage of the request.
Q3. Write the formula.
ceil(current x currentValue / target).
Q4. With two metrics, which wins?
The one asking for more replicas.
Q5. Default scale-down window?
300 seconds.
Samajla ka?