Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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:

  1. 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.

  2. 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.

  3. Create the HPA resource – You define which Deployment to target, which metric to watch (usually CPU), and a target average utilization (like 50%).

  4. Monitor and adjust – Every 15 seconds (by default), the HPA pulls metrics and recalculates the desired replica count. It then updates the spec.replicas of 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/v1 instead of autoscaling/v2v1 only supports CPU; v2 supports 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 AverageValue instead to avoid sudden spikes in pod count.

Variations

  1. Use kubectl autoscale for quick, imperative creation, or write a YAML manifest for reproducible GitOps workflows.
  2. For memory-based scaling, use averageUtilization with resource metric type and memory as the resource name.
  3. Scaling on custom metrics (e.g., requests per second) requires the custom-metrics-apiserver adapter 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: 0 and 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.requests on the target container to compute utilization.
  • Use autoscaling/v2 for 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.