HorizontalPodAutoscaler Scaling
Learn to use HorizontalPodAutoscaler for scaling in Kubernetes. A practical, step-by-step tutorial covering core concepts, hands-on exercises, troubleshooting, and next steps.
Focus: use horizontalpodautoscaler for scaling
In the static world of a Kubernetes Deployment, your application runs with a fixed number of replicas — until a traffic spike hits. Suddenly requests are queued, error rates climb, and users complain of slow load times. The problem is not your deployment; it's your capacity plan. You cannot manually adjust replicas every five minutes. HorizontalPodAutoscaler (HPA) solves this: it automatically scales your pods based on observed CPU, memory, or custom metrics, matching supply to demand in real time.
The problem this lesson solves
Without autoscaling, you face two bad options:
- Over-provision: Run 20 pods all day even during quiet hours — wastes money and cluster resources.
- Under-provision: Run 3 pods and hope the spike doesn't come — risks downtime and poor experience.
Manual scaling via kubectl scale works for a demo, but in production your traffic fluctuates unpredictably. You need a controller that watches metrics, compares them to a target, and adjusts replicas automatically without human intervention. This is exactly what HPA does.
Core concept / mental model
Think of HPA as a thermostat for your workload. You set a target temperature (e.g., "keep average CPU at 50%"). The HPA controller continuously reads the current temperature (current metrics via Metrics Server), compares it to your target, and turns the heat up or down — adds or removes pods — to keep the system near the setpoint.
- Target metric: CPU, memory, or custom metric (e.g., requests-per-second, queue length).
- Current value: average across all running pods, collected by the Metrics Server.
- Desired replicas:
current_replicas × (current_metric / target_metric). If current is 80% and target is 50%, you need1 × (80/50) = 1.6 → 2replicas.
Pro tip: HPA rounds up the desired replicas to the nearest integer using a ceiling function. It also prevents wild fluctuations with a cooldown mechanism (
--horizontal-pod-autoscaler-sync-period, default 15 seconds) and optional stabilization window.
How it works step by step
- Metrics Server collects resource usage from each pod (CPU/memory) and aggregates them per pod and per workload.
- HPA controller reads metrics via the
metrics.k8s.ioAPI (for CPU/memory) or the custom metrics API (for application-level metrics). - Controller computes desired replicas using the formula above.
- If desired replicas differ from current and the change exceeds a configurable tolerance (default 10%), the controller updates the target resource's
.spec.replicasfield (e.g., a Deployment, StatefulSet, or ReplicaSet). - The Deployment's ReplicaSet creates or terminates pods to match the new desired count.
- Cooldown avoidance: HPA prevents thrashing by requiring the metric to remain outside the target for a period — usually 2–3 sync cycles.
Required components
- Metrics Server must be installed in the cluster (
kubectl top podsworks). - Target resource must have
requestsset for the metric you want to scale on (e.g.,resources.requests.cpu: 250m). Without requests, HPA cannot compute a meaningful utilization percentage.
Hands-on walkthrough
Let's create a deployment that consumes CPU, then attach an HPA that scales on CPU at 50% target.
Step 1: Deploy a sample app
Save the following as stress-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: stress-generator
spec:
replicas: 1
selector:
matchLabels:
app: stress
template:
metadata:
labels:
app: stress
spec:
containers:
- name: stress
image: polinux/stress
command: ["/bin/sh", "-c"]
args: ["stress --cpu 1 &"; "sleep 3600"]
resources:
requests:
cpu: "200m"
limits:
cpu: "400m"
Apply it:
kubectl apply -f stress-deployment.yaml
Step 2: Create the HPA
Save as stress-hpa.yaml:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: stress-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: stress-generator
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
Apply it:
kubectl apply -f stress-hpa.yaml
Step 3: Watch it scale
kubectl get hpa stress-hpa --watch
Expected output (after ~30 seconds):
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
stress-hpa Deployment/stress-generator <unknown>/50% 1 10 0 10s
stress-hpa Deployment/stress-generator 0%/50% 1 10 1 30s
stress-hpa Deployment/stress-generator 80%/50% 1 10 2 60s
The HPA detected CPU above 50% and scaled from 1 to 2 replicas. Each new pod shares the load, bringing average CPU down closer to target.
Step 4: Simulate load increase (optional)
If you want to trigger more scaling, you can increase the stress:
kubectl exec deploy/stress-generator -- stress --cpu 2 & # adds more CPU load
After a few minutes, you'll see replicas climb.
Compare options / when to choose what
| Metric type | Data source | Use case | Requirement |
|---|---|---|---|
| CPU utilization | metrics.k8s.io |
General-purpose workloads, web servers | Metrics Server + requests.cpu |
| Memory utilization | metrics.k8s.io |
Memory-bound workloads (caches, databases) | Metrics Server + requests.memory |
| Custom metric (e.g., QPS) | Custom Metrics API | Application-level scaling (e.g., queue depth) | Prometheus Adapter or similar |
| External metric | External Metrics API | Scaling based on cloud-provider metrics (e.g., SQS queue) | Cloud-specific adapter (e.g., AWS CloudWatch) |
When to use which: - CPU is the simplest and works for most stateless services. - Memory is useful for workloads that cache data in RAM but must stay within limits. - Custom/external metrics are for advanced scenarios where resource metrics are insufficient — e.g., scale on HTTP requests per second or Kafka lag.
Troubleshooting & edge cases
- HPA shows
<unknown>/50%— Metrics Server is not running or pods missingrequests. Runkubectl top podsto verify metrics. If empty, install Metrics Server. - HPA never scales down — Metrics Server might report no data for a while. Check
kubectl describe hpa <name>for conditions likeFailedGetResourceMetric. - Pod flapping (scales up then down repeatedly) — Enable the stabilization window in
autoscaling/v2:yaml behavior: scaleDown: stabilizationWindowSeconds: 300 - HPA ignored for some pods — If pods have different resource requests, HPA averages across all ready pods. Ensure homogeneous request sizes for predictable scaling.
- Race conditions — When HPA and
kubectl scaleare used simultaneously, the HPA controller overwrites the manual scale. Avoid manual scaling on HPA-managed resources.
What you learned & what's next
You now understand:
- How HPA detects load and computes desired replicas using a simple formula.
- The difference between resource metrics (CPU, memory) and custom/external metrics.
- How to create an HPA, watch it scale, and interpret the output.
- How to avoid common pitfalls like missing Metrics Server or missing resource requests.
HPA is the foundation of elastic Kubernetes. Next, you will combine HPA with a Cluster Autoscaler to also scale your node pool when the cluster runs out of capacity — true cloud-native auto-scaling.
Practice recap
Try creating an HPA that scales on memory instead of CPU. Use a Deployment with requests.memory: 128Mi and set averageUtilization: 70. Run kubectl describe hpa and observe the target. For a real test, generate memory load by running a command inside a pod — watch replicas grow. Then delete the HPA and add a stabilization window to avoid flapping.
Common mistakes
- Forgetting to install the Metrics Server — HPA cannot retrieve CPU/memory metrics without it. Run 'kubectl top pods' to verify.
- Omitting 'resources.requests' on pod containers — HPA uses requests to compute utilization percentage, not limits. Without requests, HPA shows '' and never activates.
- Setting 'minReplicas' too low and 'maxReplicas' too low — during a sudden spike, HPA may hit max before traffic is served. Always allow headroom (e.g., min=2, max=20).
- Scaling on memory without considering garbage collection overhead — memory usage can spike temporarily and then drop, causing flapping. Add a stabilization window or use custom metrics instead.
Variations
- Use 'autoscaling/v2' instead of v1 — v2 supports custom metrics, multiple metrics per HPA, and scaling behavior (stabilization windows, policies).
- Combine HPA with VerticalPodAutoscaler (VPA) — VPA adjusts resource limits, HPA adjusts replicas. They can conflict if both modify the same deployment; use VPA in 'initial' mode or separate workloads.
- For application-level metrics, use a custom metrics adapter like Prometheus Adapter to supply custom metrics (e.g., HTTP requests per second) to HPA.
Real-world use cases
- E-commerce web tier: scale pods based on CPU utilization during Black Friday traffic surges, then automatically scale down overnight.
- Real-time analytics pipeline: scale a consumer deployment based on Kafka consumer lag (custom metric) to process messages faster when queue grows.
- Video transcoding service: scale workers based on job queue depth (custom metric), ensuring fast transcoding without idle capacity.
Key takeaways
- HPA automatically scales the number of pod replicas based on observed CPU, memory, or custom metrics.
- It uses the formula: desired_replicas = current_replicas × (current_metric / target_metric).
- Metrics Server is mandatory for CPU/memory-based HPA; resource 'requests' must be defined on containers.
- Use 'autoscaling/v2' for advanced features like multiple metrics, custom metrics, and scaling behavior (stabilization windows).
- Avoid manual scaling with kubectl on HPA-managed workloads — the controller will override your changes.
- Combine HPA with Cluster Autoscaler to also scale the node pool, enabling full vertical + horizontal elasticity.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.