0%
Containers and Kubernetes: From cgroups to Operators
What a Container Actually Is
Working with Docker
Kubernetes Architecture
Workloads
Networking and Storage
Extending Kubernetes
Autoscaling Workloads and Clusters
Horizontal autoscaling is a control loop with a formula at its centre, and almost every surprising behaviour is explained by the formula or by the loop's period rather than by anything subtle.
The loop and its period
The controller runs inside the controller manager on a fixed interval, fifteen seconds by default.
On each pass it lists the pods belonging to the target, fetches the current metric value for each, computes a desired replica count, and writes that count to the target's scale subresource if it differs from the current one. The deployment controller then does the actual work of creating or removing pods.
The period matters because it bounds reaction time. A traffic spike arriving immediately after a pass waits up to fifteen seconds before the controller even looks, and the metrics it then reads are themselves a few tens of seconds stale, so the honest floor on reaction is well under a minute but not instantaneous.
The replica formula
One expression produces the desired count and is worth knowing exactly.
The controller sums the metric across ready pods, divides by the target value to get the number of pods needed, and rounds up:
So four pods averaging eighty percent utilisation against a fifty percent target gives the ceiling of four times eighty over fifty, which is seven. The ratio, not the absolute gap, drives the magnitude, which is why a small target produces large jumps: the same utilisation against a ten percent target would ask for thirty-two.
The tolerance band
One constant prevents the loop from acting on noise.
A ratio within ten percent of one is treated as no change, so a target of fifty percent is effectively a band from forty-five to fifty-five. Inside it the controller does nothing, however long the condition persists.
That band is the reason an autoscaler appears to ignore a metric sitting slightly above target, which is a frequent complaint and is working as designed. It also means the effective utilisation of a scaled workload settles somewhere in the band rather than at the target, so capacity planning should assume the upper edge.
Utilisation is a ratio against the request
One definition is responsible for a large share of confusion.
When the metric is processor or memory utilisation, the denominator is the container's request, not the limit and not the node's capacity. A pod requesting a tenth of a core and using a tenth of a core is at a hundred percent utilisation regardless of how idle the node is.
So the request is simultaneously a scheduling claim, a contended-time weight and the autoscaling denominator, which means changing a request silently changes the scaling behaviour. A team that halves a request to improve packing has also halved the usage at which scaling triggers, and that linkage is rarely noticed until the replica count doubles.
Pods the controller ignores or treats specially
Several pod states are handled differently and the handling matters during an incident.
Pods that are not ready, or whose metrics are unavailable, are excluded from the average but counted when deciding direction conservatively: for a scale-up the controller assumes missing pods are at zero, and for a scale-down it assumes they are at a hundred percent. Pods that are still starting up, within their initialisation window, are ignored entirely so a cold pod's high usage does not drive further scaling.
The practical consequence is that a workload whose pods take a long time to become ready scales sluggishly, because each new pod contributes nothing to the average until it is ready and the controller keeps computing from the old ones.
Multiple metrics
One rule combines several metrics and it is deliberately asymmetric.
When more than one metric is configured, the controller computes a desired count for each and takes the largest. There is no weighting and no averaging: any single metric calling for more replicas wins.
That makes adding a metric a safe operation in one direction only. A second metric can only increase the replica count, never decrease it, so a badly chosen one pins the workload high permanently while a well-chosen one acts as a safety net for a case the first metric misses.
Bounds, and what the minimum really is
Two fields bound the loop and one of them has a subtlety.
The maximum is a hard cap and the controller reports a condition when it is pinned there, which is the signal to look at. The minimum is also a floor, and setting it to zero is possible only with a feature gate, which allows scaling a workload entirely away when its metric is zero.
Scaling to zero is attractive for intermittently used services and introduces a cold start on the first request, which something must absorb: either a queue that buffers the request, or a proxy that holds the connection while a pod starts. Without one of those, scaling to zero converts an idle cost into a user-visible failure.
What the autoscaler cannot do
Three limits are worth stating because people expect otherwise.
It cannot make pods schedule: it writes a replica count, and if the cluster has no capacity the extra pods sit pending, which is a cluster autoscaling problem rather than a workload one. It cannot scale faster than its own period plus the metrics pipeline delay plus pod startup time, which together are usually a minute or more. And it cannot help a workload whose bottleneck is a shared dependency, since adding replicas increases load on the dependency.
That last case is the one that does harm. A service limited by a database connection pool scales up under load, each replica opens connections, and the database degrades further, which raises the metric and triggers more scaling.
Reading the autoscaler's own status
One object explains any disagreement about what the autoscaler is doing.
Its status carries the current and desired replica counts, the last computed metric values, the time of the last scaling event, and conditions saying whether it is able to scale, whether it is able to fetch metrics, and whether it is limited by a bound. Those conditions name the problem directly in almost every case.
The most common findings are that the metrics pipeline is unavailable, so the controller is computing from nothing; that the target's pods have no resource request, so utilisation is undefined; or that the controller is scaling as asked and the pods are pending, which moves the investigation to the scheduler.
The metrics pipeline underneath
One dependency is easy to forget until it fails.
Resource utilisation metrics come from a metrics service that aggregates per-pod samples collected by each node agent, typically refreshed on a fifteen to thirty second cadence and smoothed over a short window. Custom and external metrics come from an adapter backed by a monitoring system, with its own scrape interval and query latency.
So the figure the controller acts on describes the recent past, by anywhere from fifteen seconds to a couple of minutes depending on the pipeline. That lag is additive with the loop period and is the dominant term in how late a scale-up arrives, which is why reducing the loop period alone changes little.
Which object the autoscaler actually writes
One indirection makes the controller generic and occasionally confusing.
The autoscaler does not know about deployments. It writes a replica count to its target's scale subresource, a small uniform interface exposing a replica field and a selector, which deployments, replica sets, stateful sets and many custom resources implement.
That is why an autoscaler can target a custom resource it has never heard of, and why targeting a replica set belonging to a deployment is a mistake: the deployment controller will overwrite the count on its next pass. The target should always be the top-level object a human manages.
A strong answer states the formula, the ceiling of ready replicas times current over target, and then draws the consequences: the ratio rather than the gap sets the magnitude, so a low target produces large jumps; utilisation is measured against the request, so changing a request changes scaling behaviour; and a ten percent tolerance band means the workload settles near the upper edge of the band rather than at the target.