Skip to main content
Kubernetes HPA Not Scaling? The 7 Causes, in Order

Kubernetes HPA Not Scaling? The 7 Causes, in Order

KU
• 6 min read•1 view

The HorizontalPodAutoscaler is up, traffic is spiking, and replicas won't move. Seven causes in the order we check them, each with the command that confirms it.

Key takeaways

  • When a Kubernetes HPA won't scale, run kubectl describe hpa and read its targets and conditions.
  • The usual causes are a missing metrics-server, Pods without CPU requests, the 10% tolerance, the 300-second scale-down window, maxReplicas already reached, or new Pods that are not yet ready.

When a HorizontalPodAutoscaler won't scale, the HPA usually tells you why. Most people look at kubectl get hpa, see <unknown> or a target that looks fine, and start guessing. The answer is in kubectl describe hpa: its targets and its conditions name the cause in most cases.

bash.bash
$ kubectl describe hpa web -n shop
...
Metrics:                  ( current / target )
  resource cpu on pods:   <unknown> / 70%
Min replicas:             2
Max replicas:             10
Conditions:
  Type            Status  Reason                   Message
  AbleToScale     True    SucceededGetScale        the HPA controller was able to get the target's current scale
  ScalingActive   False   FailedGetResourceMetric  the HPA was unable to compute the replica count: failed to get cpu utilization

ScalingActive False means the HPA can't even compute a number. ScalingLimited True means it computed one but a bound stopped it. Those two conditions sort the seven causes into two groups.

#CauseWhat you seeFix
1No metrics API<unknown>, kubectl top failsInstall metrics-server
2No CPU or memory request<unknown> for that resourceSet requests on every container
3Wrong targetAbleToScale FalseFix scaleTargetRef
4Within toleranceTarget shows 75% / 70%, nothing happensExpected; lower the target if needed
5Scale-down windowHigh replicas stay for 5 minutesExpected; tune behavior
6Bound reachedScalingLimited True, TooManyReplicasRaise maxReplicas, check capacity
7Pods not readyScale-up smaller than expectedFix readiness, startup time

When the HPA can't compute anything

1. The metrics API isn't there. Resource metrics come from the metrics.k8s.io API, which the Kubernetes docs note is usually provided by metrics-server, an add-on you launch separately. Many local and some managed clusters don't have it.

bash.bash
$ kubectl top pods -n shop            # fails if the metrics API is missing
$ kubectl get apiservice v1beta1.metrics.k8s.io
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/shop/pods | head -c 300

2. Containers have no requests. Utilization targets are a percentage of the request. From the Kubernetes documentation: if some of a Pod's containers don't have the relevant request set, CPU utilization for the Pod isn't defined and the autoscaler takes no action for that metric. A sidecar without a request counts too, so check every container, not just the app. Getting requests right helps far beyond autoscaling, as we covered in rightsizing resource requests.

3. The target reference is wrong. A typo in scaleTargetRef, the wrong apiVersion, or an HPA pointing at a Deployment that was renamed shows up as AbleToScale False. Two HPAs targeting the same workload also fight each other; keep one per workload.

When it computes a number but doesn't act

4. You're inside the tolerance. The controller computes desiredReplicas = ceil(currentReplicas × currentMetric / desiredMetric) and skips any change when that ratio is within 0.1 of 1.0 by default. At a 70% target, 75% utilization is a ratio of about 1.07, so nothing happens. That's intended, to prevent flapping.

5. You're waiting on the scale-down window. Scale-up reacts within about 15 seconds by default, but scale-down uses a 300-second stabilization window: the HPA picks the highest recommendation from the last five minutes. If replicas "won't come down", wait five minutes before debugging. You can tune both directions:

yaml.yaml
# hpa-web.yaml (autoscaling/v2)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: shop
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 120

6. You hit a bound. ScalingLimited True with reason TooManyReplicas means maxReplicas is reached. If replicas did increase but new Pods sit Pending, the HPA did its job and the cluster is out of capacity; that's a node autoscaler or quota problem, not an HPA problem.

7. New Pods aren't ready yet. For CPU, the controller sets aside Pods that aren't ready or are still initializing and, when scaling up, conservatively assumes they use 0% of the target. Slow-starting apps, such as JVM services, therefore scale up in smaller steps than you'd expect. Fix readiness probes and startup time before tuning the HPA.

Scaling on custom or external metrics

CPU and memory come from metrics-server, but requests per second, queue depth or latency need a metrics adapter that serves the custom.metrics.k8s.io or external.metrics.k8s.io APIs, such as Prometheus Adapter or KEDA. Without one, the HPA reports ScalingActive False with a reason like FailedGetPodsMetric or FailedGetExternalMetric.

bash.bash
$ kubectl get apiservices | grep -E 'custom.metrics|external.metrics'
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | head -c 300

If the API is registered but the metric name returns nothing, the adapter's query or naming rules don't match what your Pods export. Check the metric in Prometheus first, then the adapter configuration, before suspecting the HPA.

Questions people ask

Why does my HPA show unknown for CPU?

<unknown> means the HPA can't read the metric. Either the metrics API is missing (install metrics-server and confirm kubectl top pods works), or a container in the Pod has no CPU request, which leaves utilization undefined. Check every container, including sidecars, for resources.requests.cpu.

Why isn't my HPA scaling down?

By default, scale-down uses a 300-second stabilization window, so the HPA keeps the highest replica count recommended in the last five minutes. Also check minReplicas, and whether utilization is within the default 10% tolerance of the target, in which case the HPA intentionally does nothing.

How fast does a Kubernetes HPA scale up?

With default behavior, it can add up to 100% of current replicas or 4 Pods, whichever is more, every 15 seconds, with no stabilization window for scale-up. In practice, metrics collection and Pod startup time add delay, so slow-starting apps scale more slowly than the policy allows.

The decision, concretely

  • <unknown> target? Check the metrics API first, then requests on every container.
  • Number computed but no change? Compare current and target; within 10% is normal.
  • Replicas stuck high? Wait out the 300-second window, then tune behavior.scaleDown.
  • Replicas went up but Pods are Pending? Look at cluster capacity, not the HPA.

The call we'd make

Make kubectl describe hpa your first move and read the conditions before touching configuration. Then fix the boring prerequisites, metrics-server and requests on every container, which resolve most HPA tickets we see. Tune behavior only after those are right, and only with real traffic graphs in front of you.

Sources

Explore topics:KubernetesDevOps
React
Share this post
KU

Kiril Urbonas

AI Engineer

570 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.