Skip to content

Scaling and HPA

Assumes: Pods and Deployments and Resource Requests and Limits - HPA measures utilization against requests, so that page comes first.

Scaling in Kubernetes has three layers:

  • workload scaling: change pod replicas
  • node scaling: add or remove cluster nodes
  • resource sizing: change CPU or memory requests per pod

This page focuses on workload scaling with Horizontal Pod Autoscaler (HPA).

Manual scaling

Manual scaling is still useful for planned events:

kubectl scale deployment web --replicas=8

For live traffic variability, manual scaling does not react quickly enough.

How HPA works

HPA is a closed control loop driven by metrics:

flowchart LR
    MS[Metrics Server\nor custom adapter] -->|current usage| HPA[HPA controller]
    HPA -->|desired replicas| DEP[Deployment / StatefulSet]
    DEP -->|pod metrics| MS

Loop steps:

  1. Read metrics for current pods (via Metrics API). The loop runs every 15 seconds by default.
  2. Compare current utilization to the configured target.
  3. Compute desired replica count: desiredReplicas = ceil(currentReplicas × currentUtil / targetUtil).
  4. Apply stabilization window to avoid oscillation.
  5. Update the target workload replica count.

Details of the algorithm that explain real-world behavior:

  • Tolerance: if the ratio currentUtil / targetUtil is within ±10% of 1.0, the HPA does nothing. This dead band prevents constant micro-adjustments.
  • Utilization is measured against requests, not limits. A pod using 300m CPU with a 200m request is at 150% utilization - even if its limit is 2 cores.
  • Missing metrics are handled conservatively: pods without metrics count as 0% when deciding to scale up and 100% when deciding to scale down, and not-yet-ready pods are ignored for scale-up. The bias is always toward not making things worse.
  • Multiple metrics: when several metrics are configured, the HPA computes a desired count for each and takes the highest. You cannot use a second metric to hold replicas down.
  • Scale to zero isn't supported for CPU/memory targets - minReplicas must be at least 1 there. HPA can go to minReplicas: 0 with an Object or External metric instead, but true scale-to-zero-when-idle behavior is still better served by KEDA or a request-driven activator.

Cluster Requirements

HPA is only as good as metric quality.

Required baseline:

  • metrics pipeline available (metrics-server for CPU or memory)
  • workload has realistic resources.requests
  • readiness probes are configured so new pods enter traffic safely

If requests are missing, percentage-based resource targets become unreliable.

HPA example

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 12
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 20
          periodSeconds: 60

This configuration scales up aggressively and scales down more cautiously to reduce flapping.

HPA troubleshooting

kubectl get hpa
kubectl describe hpa web-hpa
kubectl top pods -l app=web

Common failure patterns:

  • Unknown targets due to missing metrics pipeline
  • very slow response because pods have long startup times
  • oscillation caused by too-tight thresholds and no stabilization

HPA, VPA, and node autoscaling

  • HPA scales pod count horizontally.
  • VPA (Vertical Pod Autoscaler) adjusts pod resource requests over time - do not use HPA and VPA together on the same CPU/memory signal; they will conflict. VPA is safe to combine with HPA when HPA uses custom/external metrics instead.
  • Node autoscaler or Karpenter adds infrastructure capacity when pods cannot be scheduled.

Custom and external metrics

The built-in autoscaling/v2 HPA supports three metric types:

  • Resource: CPU or memory utilization against pod requests.
  • Pods: custom per-pod metric from an adapter (e.g. requests per second).
  • External: metric from an external system (e.g. queue depth from SQS or Kafka lag).

For event-driven scaling needs beyond what HPA covers natively, consider KEDA (Kubernetes Event-driven Autoscaling). KEDA extends HPA with out-of-the-box scalers for message queues, databases, HTTP traffic, and 70+ other sources - scaling to zero when idle is a key advantage.

Practical guidance

  • Start with CPU utilization targets around 50 to 70 percent
  • Tune using real production latency and error metrics, not only CPU
  • Set sensible min and max replica limits to protect cost and stability
  • Validate behavior with load tests before relying on autoscaling in production

Certification notes

  • kubectl autoscale deployment web --min=2 --max=10 --cpu-percent=60 creates a working HPA imperatively - the fastest exam path.
  • Remember the formula and that utilization is relative to requests. Scenario questions often hinge on a missing or wrong resources.requests.
  • kubectl get hpa showing <unknown>/60% means the metrics pipeline is broken or requests are unset - know both causes.

Summary

HPA is a control loop, not a magic switch. It works well when metrics are trustworthy, pod requests are accurate, and rollout health checks are disciplined.

Check Yourself

Your HPA reports <unknown>/70% and never scales. What are the two most likely causes?

Either metrics-server is not installed or not healthy, so no utilization data exists at all, or the target pods have no CPU requests set, leaving nothing to compute a percentage against. Both show up identically in kubectl describe hpa.

A pod uses 300m CPU and requests 200m. What utilization does the HPA see, and is that a problem?

150%. Utilization is measured against the request, not the limit, so under-requesting makes every pod look permanently overloaded and the HPA scales out to compensate. You end up paying for replicas to fix a sizing error.

Traffic spikes hard and the HPA adds replicas, but latency stays bad for two minutes. Why is autoscaling not instant?

The loop has latency at every stage: metrics are scraped on an interval, the HPA evaluates periodically, new pods must be scheduled, images pulled, and readiness probes passed before they receive traffic. HPA absorbs sustained load changes, not sudden bursts - headroom or a queue does that.

Why should you not run an HPA and a VPA in Auto mode against the same Deployment on CPU?

They fight. The VPA changes the CPU request, which is the denominator of the HPA's utilization calculation, so each one's action changes the other's input and the pair oscillates. Use VPA for memory or in recommendation mode, and let HPA own replica count.


Beginner track - step 10 of 12. Next: Kubernetes API. Back to the track overview.