A HorizontalPodAutoscaler is a feedback controller, not a capacity planner. Its default synchronization interval is fifteen seconds. Depending on its configuration, it reads resource, custom or external metrics and updates a workload’s scale subresource. The basic calculation is desiredReplicas = ceil(currentReplicas * currentMetricValue / desiredMetricValue), followed by handling for missing data, readiness, tolerances, stabilization and bounds.
A plausible HPA object does not prove that capacity will arrive. Metrics availability, requests, replica limits, scheduling and application startup all influence the result. Inspect the recommendation and the actual ready capacity separately; increasing desired replicas cannot manufacture nodes or make a blocked rollout succeed.
The ratio and the dead band
For a CPU utilization target, observed CPU use is compared with the relevant CPU requests, producing a percentage. The requested target is also a percentage. With identical single-container Pods requesting 200m and targeting 60%, 120m per Pod is a useful simplified equivalent. It is not the general denominator for heterogeneous Pods or every metric type.
The cluster default tolerance is 0.1 around a ratio of one. In Kubernetes 1.34, per-direction tolerance is alpha and disabled by default; check the feature gate and controller configuration before assuming an HPA can override it. Treat that as a control dead band, not a promise that 132m never scales and 133m always does: aggregation, integer utilization, readiness and implementation arithmetic affect boundary cases. In the unadjusted formula, three replicas at 160m against a 120m target recommend four. Rounding, policies and actual scheduling still need separate inspection.
Missing metrics and the veto
Missing Pod metrics are handled conservatively. In Kubernetes 1.34.0, GetResourceReplicas fills a missing downscale sample with that Pod’s resource request multiplied by max(100%, target utilization). The plain per-Pod metric branch, calcPlainMetricReplicas, instead uses the target amount. Both substitute zero for scale-up. A 200m request and 60% utilization target therefore give a 200m downscale substitute, not 120m. Recalculation can cancel the action; displayed utilization still uses the original available samples. For CPU, not-yet-ready Pods receive additional treatment, with configurable readiness-delay and initialization windows. These are rules about startup samples and readiness transitions, not a blanket statement that every Pod is ignored for exactly five minutes.
A CPU utilization calculation needs the relevant resource requests. A missing request can make that metric unusable, even when kubectl top reports usage. With multiple configured metrics, HPA takes the largest proposed replica count; an unusable metric can block a proposed scale-down while another valid metric can still justify scale-up. Missing one source therefore does not mean the entire controller always stops.
Stabilisation and rate limits
By default, scale-down considers a five-minute recommendation window and selects the highest recommendation still relevant there. This reduces oscillation; it is not an exact five-minute delay measured from an external load graph. Recommendations, the current replica count, bounds and configured behavior determine when reduction occurs. Default scale-up stabilization is zero, but users can configure different behavior.
Default scale-up policies allow the larger of four Pods or a doubling over fifteen seconds; default scale-down policies permit a full reduction within their period, subject to stabilization and the minimum replica count. These are permitted rates, not an instruction to grow from one to five to ten automatically. The metric must request that growth and maxReplicas still caps it.
When it breaks
Unknown utilization can come from missing requests, unavailable metrics, authorization or other fetch and calculation errors. Read ScalingActive and its reason rather than treating unknown as proof that no CPU samples exist. Check all relevant containers; a first-container query cannot establish the whole Pod’s denominator.
A workload at maxReplicas can remain overloaded even while HPA works as configured. ScalingLimited explains clipping, but capacity planning also needs node headroom, startup delay and downstream limits. Raising the cap may create Pending Pods or overwhelm a shared dependency. Compare desired replicas with available replicas and observed service latency before declaring autoscaling successful.
Check it quickly
These retained read-only diagnostics were not rerun in this review. Select an authorized test cluster and namespace; inspect the complete HPA and every relevant container alongside this abbreviated view.
Confirm that a metric exists, that a denominator exists, and what the controller decided:
kubectl get hpa web -o wide
kubectl top pods -l app=web
kubectl get deploy web -o jsonpath='{.spec.template.spec.containers[0].resources.requests.cpu}{"\n"}'
kubectl get hpa web -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
kubectl describe hpa web | sed -n '/Events/,$p'
The first line shows current against target utilization; <unknown> means the requested value could not be established, not necessarily that raw usage is absent. The third prints nothing when no CPU request is set, which is the usual reason the second command works while the HPA still refuses to scale. The last two name the condition reason and any FailedGetResourceMetric event, which is where the real explanation lives.
Sources
- Horizontal Pod Autoscaling, the algorithm, tolerance, missing-metric handling, stabilisation window and default policies, checked 2026-10-15.
- HPA walkthrough, the metrics-server dependency and the requests prerequisite, checked 2026-10-15.
- metrics-server, the resource metrics API implementation the HPA reads, checked 2026-10-15.
- Kubernetes 1.34 HPA, version-scoped tolerance and conservative metric handling, checked 2026-10-18.
- Kubernetes v1.34.0 replica calculator, distinguishes resource-utilization and plain per-Pod missing-sample substitutions, checked 2026-10-18.