How to Configure Kubernetes HPA Autoscaling

A HorizontalPodAutoscaler watches your Deployment's CPU (or memory) usage and adjusts the replica count automatically - scale out under load, scale back in when traffic drops. Here's how to write and apply one with autoscaling/v2.

Kubernetes Engineerkuberneteskubectlhpa

What an HPA does

The HorizontalPodAutoscaler controller runs in the cluster control plane and polls the metrics server every 15 seconds. When average CPU utilization across the Deployment's pods rises above your target, it increases the replica count up to maxReplicas. When utilization falls, it scales back down - after a stabilization window - to avoid thrashing.

The autoscaling/v2 API (stable since Kubernetes 1.26) supports multiple metric types in a single object: Resource (CPU, memory), Pods, Object, and External. Use v2 for any new HPA - v1 only supports CPU and is kept for backwards compatibility.

Write the manifest

Create /workspace/hpa.yaml:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

averageUtilization: 70 means the HPA targets 70% of each pod's CPU request. If pods are on average at 140% (double the target), the controller doubles the replica count.

Apply and verify

kubectl apply -f /workspace/hpa.yaml
kubectl get hpa
kubectl describe hpa api

The kubectl get hpa output shows current vs target utilization and the live replica count:

NAME   REFERENCE        TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
api    Deployment/api   18%/70%   2         10        2          30s

<unknown>/70% in TARGETS is normal for the first minute - the metrics server needs a scrape cycle to collect data. Wait 60 seconds and re-run.

Tuning scale-down behavior

By default the HPA won't scale below your target for 5 minutes after a spike. You can tighten or loosen this with behavior:

spec:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 120   # wait 2 min before scaling in
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60              # remove at most 25% of pods per minute

Slower scale-in protects services with long-lived connections (databases, WebSockets) from losing capacity before those connections drain.

Adding memory or custom metrics

Append more entries to metrics: to target multiple signals simultaneously:

metrics:
- type: Resource
  resource:
    name: cpu
    target:
      type: Utilization
      averageUtilization: 70
- type: Resource
  resource:
    name: memory
    target:
      type: Utilization
      averageUtilization: 80

The HPA scales to satisfy the most demanding metric - the replica count is the maximum of what each metric independently requires.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is the difference between autoscaling/v1 and autoscaling/v2?

autoscaling/v1 only supports CPU-based scaling. autoscaling/v2 (stable since Kubernetes 1.26) supports multiple metric types - CPU, memory, custom Pods metrics, Object metrics, and External metrics - all in one HPA object. Use v2 for any new HPA.

Why does kubectl get hpa show <unknown> for TARGETS?

The metrics server needs one or two scrape cycles (about 60 seconds) after the HPA is created before it has data. If <unknown> persists beyond a few minutes, check that metrics-server is running with kubectl get pods -n kube-system and that your pods have CPU requests set.

How do I prevent an HPA from scaling in too aggressively?

Set spec.behavior.scaleDown.stabilizationWindowSeconds to a higher value (default is 300 seconds) and add a scaleDown policy that limits how many pods can be removed per interval. This is important for services with long-lived connections or slow startup.

Keep learning

Fix a Kubernetes CrashLoopBackOffKubernetes projectHeal a Kubernetes Service With No EndpointsKubernetes projectFix a Kubernetes ImagePullBackOffKubernetes projectKubernetes roadmapStep by step to hiredKubernetes interview questionsSTAR answersAll Kubernetes projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →