Set up a HorizontalPodAutoscaler
Set up a HorizontalPodAutoscaler in Kubernetes: step-by-step tutorial covering core concepts, hands-on exercise, troubleshooting, and next steps for progressive mastery.
Focus: set up a horizontalpodautoscaler
Your application is running, but you've noticed that during peak traffic hours, response times spike and pods start restarting due to resource pressure. You could keep guessing and adjust replicas manually, but that's both inefficient and error-prone. The HorizontalPodAutoscaler (HPA) is Kubernetes' built-in answer to this problem—automatically scaling the number of pods based on observed metrics like CPU or memory usage. By the end of this lesson, you'll know exactly how to set up an HPA and why it's a critical tool for maintaining performance without manual intervention.
The problem this lesson solves
When traffic to your application varies throughout the day, you have two unpleasant choices: over-provision resources (which costs more) or risk poor performance during spikes. Manually scaling with kubectl scale deployment is fine for testing, but in production, it's a reactive bottleneck. The real challenge is that you don't know when to scale or by how much. HorizontalPodAutoscaler eliminates this guesswork by continuously monitoring resource usage and adjusting the replica count to stay within a target threshold. It turns scaling from a manual chore into an automated, policy-driven operation.
Core concept / mental model
Think of an HPA like a thermostat for your application's pod count. You set a target temperature (for example, CPU utilization at 50%), and the HPA constantly checks the current "temperature" (actual average CPU usage). If it rises above 50%, the HPA "cools down" by adding more pods. If it drops below, it "warms up" by removing unused pods. The HPA does not deploy new versions or handle application logic—it only manages the number of replicas in a Deployment or StatefulSet.
Definitions you need
- Horizontal scaling: Adding or removing pod replicas (as opposed to vertical scaling, which adds more CPU/memory to existing pods).
- Target metric: The resource or custom metric the HPA watches (CPU, memory, or custom metrics from Prometheus).
- Current utilization: The average value of the metric across all pods.
- Desired replicas: Calculated as
ceil[currentReplicas * (currentUtilization / targetUtilization)].
Pro tip: The HPA works best with resource requests set on your containers. Without requests, the metric server can't compute utilization correctly.
How it works step by step
Setting up an HPA follows a predictable flow:
-
Ensure the metrics server is running – The HPA depends on the Kubernetes Metrics Server to collect CPU and memory usage from nodes and pods. Without it, the HPA has no data.
-
Define resource requests for your containers – The HPA calculates utilization based on the ratio of actual usage to the requested value. If you don't set
requests, the HPA doesn't know the baseline and cannot compute utilization correctly. -
Create the HPA resource – You define which Deployment to target, which metric to watch (usually CPU), and a target average utilization (like 50%).
-
Monitor and adjust – Every 15 seconds (by default), the HPA pulls metrics and recalculates the desired replica count. It then updates the
spec.replicasof the target Deployment. The HPA has a cooldown (default 5 minutes) to avoid flapping.
Hands-on walkthrough
Let's create an end-to-end example. We'll deploy a simple php-apache server, expose metrics, then attach an HPA.
Step 1: Deploy a sample application
First, create a Deployment with explicit CPU requests. Without these, the HPA won't know how to compute utilization.
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: registry.k8s.io/hpa-example
ports:
- containerPort: 80
resources:
requests:
cpu: 200m
Apply it:
kubectl apply -f php-apache-deployment.yaml
Step 2: Verify the Metrics Server is running
kubectl get deployment metrics-server -n kube-system
If it's missing, install it via the official manifest:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Step 3: Create the HorizontalPodAutoscaler
Now create an HPA that targets the php-apache Deployment and sets CPU utilization to 50%.
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
This is the quick CLI way. Alternatively, you can write a YAML manifest:
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
averageUtilization: 50
Apply it:
kubectl apply -f hpa.yaml
Step 4: Generate load and watch the HPA in action
In a separate terminal, start a load generator:
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
Watch the HPA behavior:
kubectl get hpa php-apache --watch
You'll see output similar to:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache 0%/50% 1 10 1 30s
php-apache Deployment/php-apache 250%/50% 1 10 1 35s
php-apache Deployment/php-apache 250%/50% 1 10 4 60s
php-apache Deployment/php-apache 150%/50% 1 10 5 75s
php-apache Deployment/php-apache 45%/50% 1 10 5 90s
The replica count increases under load and stabilizes as usage approaches the 50% target.
Compare options / when to choose what
| Feature | Resource-based HPA (CPU/Memory) | Custom Metrics HPA (Prometheus) | External Metrics HPA (e.g., queue length) |
|---|---|---|---|
| Setup complexity | Low – built-in | Medium – requires adapter | High – requires adapter & external API |
| Data source | Metrics Server | Prometheus & custom-metrics-apiserver | Cloud provider APIs or event sources |
| Use case | General web backends | Applications with custom scaling logic | Queue-based or event-driven workloads |
| Latency | ~15s cycle | Depends on Prometheus scrape interval | Depends on external provider |
| YAML example | autoscaling/v2 with resource |
autoscaling/v2 with pods metric |
autoscaling/v2 with external metric |
For most applications, resource-based HPA is the simplest starting point. Move to custom metrics only when CPU/memory don't correlate with load—like a worker processing jobs from a queue.
Troubleshooting & edge cases
"Failed to get metrics" or "unknown" targets
Check that:
- Metrics Server is running (kubectl get po -n kube-system | grep metrics-server).
- The target Deployment has resources.requests.cpu set.
- Underlying nodes are healthy (kubectl get nodes).
HPA doesn't scale down quickly enough
By default, the HPA has a cooldown of 5 minutes (--horizontal-pod-autoscaler-downscale-stabilization). You can reduce this, but be cautious—lower values can cause flapping.
HPA never scales up under load
Verify:
- The load is actually hitting your service (check pod logs).
- The metric server is returning data (kubectl top pods).
- The HPA's --horizontal-pod-autoscaler-sync-period is set to a reasonable value (default is 15 seconds).
Edge case: Single pod with very high CPU due to startup
If a single pod spikes to 500% CPU, the HPA might overreact. Mitigation: Use average utilization (which averages across pods) or set a higher target type like AverageValue instead of Utilization.
Pro tip: Always combine HPA with PodDisruptionBudget to avoid losing all pods during rolling updates.
What you learned & what's next
You now understand what a HorizontalPodAutoscaler is, how it uses resource requests and the Metrics Server to calculate desired replicas, and how to set one up—via CLI or YAML. You also learned about different metric types and when to use them. The key takeaway: an HPA turns reactive scaling into proactive automation.
Next in the Kubernetes track, you'll explore VerticalPodAutoscaler, which adjusts pod resources (CPU/memory) without changing replica count. Together, these two autoscalers form the backbone of efficient resource management in production.
Practice recap
Try this: Deploy a simple Nginx server with CPU requests set to 100m, and create an HPA with a 50% CPU target. Generate load using ab or busybox wget and observe the scaling behavior with kubectl get hpa --watch. Then, change the target to 80% and see how the scaling curve changes. This hands-on exercise reinforces the relationship between target utilization and pod count.
Common mistakes
- Forgetting to set CPU or memory requests – the HPA cannot compute utilization without a baseline.
- Using
autoscaling/v1instead ofautoscaling/v2–v1only supports CPU;v2supports memory and custom metrics. - Misunderstanding the cooldown period – the HPA doesn't scale down immediately because of the stabilization window, which can frustrate new users.
- Scaling based on average utilization when your application has periodic bursts – consider
AverageValueinstead to avoid sudden spikes in pod count.
Variations
- Use
kubectl autoscalefor quick, imperative creation, or write a YAML manifest for reproducible GitOps workflows. - For memory-based scaling, use
averageUtilizationwithresourcemetric type andmemoryas the resource name. - Scaling on custom metrics (e.g., requests per second) requires the
custom-metrics-apiserveradapter and a source like Prometheus.
Real-world use cases
- An e-commerce site that sees 10x traffic during flash sales – HPA automatically scales web pods from 5 to 50 replicas based on CPU.
- A video transcoding service where the queue length (custom metric) triggers the HPA to add more worker pods.
- A background job worker that scales down to zero when idle using an HPA with
minReplicas: 0and a custom metric from a message queue.
Key takeaways
- HorizontalPodAutoscaler automatically adjusts replica count based on observed metrics, eliminating manual scaling.
- It requires a running Metrics Server and
resources.requestson the target container to compute utilization. - Use
autoscaling/v2for flexible metric types: CPU, memory, or custom metrics. - The HPA calculates desired replicas via a formula that averages utilization across pods.
- Always test with load generation before running in production to tune cooldown and target values.
- Combine HPA with PodDisruptionBudget and resource limits to ensure stable scaling behavior.
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.