An HPA That Cannot See, and Should Not Be Looking at CPU
An order-processing worker consumes from a queue. Each pod is single-threaded and spends most of its time waiting on the database, so CPU sits near 20%. During a sale the queue depth climbed from 200 to 45,000 messages and the backlog took 90 minutes to clear at 4 replicas.
The pod spec sets resources.limits.cpu: 500m but defines no resources.requests. The HPA is configured with minReplicas: 4, maxReplicas: 20, and averageUtilization: 70 on cpu. kubectl get hpa reports TARGETS: <unknown>/70%, MINPODS 4, MAXPODS 20, REPLICAS 4, and the Deployment never scales in either direction. The cluster has metrics-server installed and healthy.
1. Explain the `<unknown>` target and what must change before any HPA on this Deployment can function.
Read the worked answer
HPA computes utilisation as total usage / total requests, expressed as a percentage of the request. With no requests.cpu set, the denominator is undefined, so every pod reports % as <unknown> and the controller cannot compute a ratio. It leaves the replica count untouched rather than guessing - which is exactly what is observed.
Metrics-server being healthy is necessary but not sufficient; the missing piece is the request. Fix:
resources:
requests: { cpu: 300m, memory: 512Mi }
limits: { cpu: 500m, memory: 768Mi }
Size the request from observed steady-state usage (CPU here is ~100m at 20% of a 500m limit), not from the limit. A request that is far too low makes utilisation look enormous and causes runaway scaling; far too high and it never triggers.
Two related traps worth naming: changing a pod's resources triggers a rollout, so do it deliberately; and the HPA must not fight a spec.replicas managed by another controller - with a GitOps tool reconciling replicas, the HPA's scale is reverted each sync unless the field is excluded from the manifest.
2. Suppose the request is fixed. Would the HPA now solve the sale-day backlog? Reason about the workload's shape.
Read the worked answer
No. Fixing the request makes the HPA functional, not correct for this workload.
- CPU is the wrong signal. The worker is I/O-bound on the database. CPU stays near 20% whether the queue holds 200 messages or 45,000. A 70% CPU target would require four times the current CPU usage per pod before scaling - usage that never materialises while workers wait on I/O. The HPA would sit at
minReplicasthrough the entire incident. - The symptom is queue depth, not resource saturation. The right signal is the thing you actually care about: messages pending, or oldest-message age. That requires a custom or external metric via the prometheus-adapter, or a KEDA
ScaledObjectreading the broker directly. - Scale-out is the right dimension. Pods are single-threaded, so vertical scaling helps less than adding replicas; but the ceiling is the database's connection pool, not the HPA's
maxReplicas.
Reasonable configuration: scale on queue depth with a target such as 100 messages per replica and maxReplicas: 20; scale down on oldest-message-age returning to normal rather than on the queue emptying, and use a stabilisation window (e.g. 300 s) so a transient dip does not thrash replicas.
3. If the HPA does start scaling from 4 to 20 replicas, what else must be verified before that is safe?
Read the worked answer
Autoscaling exposes every fixed assumption downstream. Check, in roughly this order:
- Database connection pool. 20 pods x pool size (say 10) = 200 connections. If the database allows 100, the new pods fail or, worse, starve the existing ones. Cap the per-pod pool so
maxReplicas x pool <= dB limit, or introduce a proxy/pooler. - Downstream rate limits. The queue broker, an external API, or a payment provider may have per-account quotas. More workers can violate them and cause a different outage.
- Schedulability. 16 additional pods require real node capacity and, with requests now set, the scheduler needs that capacity reserved. Without the autoscaler, new pods go
Pendingand the backlog does not clear - the same failure mode as the drain scenario. - Workload ordering semantics. If order processing must be strictly serialized per order, adding consumers can introduce out-of-order or duplicate handling; confirm the queue's delivery guarantees and idempotency.
- Disruption and rollout settings. A PDB sized for 4 replicas (
minAvailable: 3) becomes trivially permissive at 20; a surge percentage that was fine at 4 may be far too large at 20. - Scale-down behaviour. Without a stabilisation window, replicas will oscillate on a sawtooth queue, which itself stresses the database. Set
behavior.scaleDown.stabilizationWindowSecondsdeliberately.