HPAs pinned with minReplicas equal to maxReplicas
What does ZopNight detect here?
ZopNight flags a Kubernetes HorizontalPodAutoscaler on EKS, GKE or AKS whose `minReplicas` equals `maxReplicas` at a value above 1, such as 4 and 4. An HPA with no range between its bounds holds the workload at one fixed size, so it adds no elasticity and hides the fact that the replica count is really hard-coded.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1744 · RC-1844 · RC-1944 |
| Category | reliability |
| Severity | low |
| Metric | spec.minReplicas and spec.maxReplicas |
| Threshold | min equals max, max greater than 1 |
| Source | ZopNight |
| Permissions used | list horizontalpodautoscalers.autoscaling |
Where it applies
A range of one value is not autoscaling
The HPA controller picks a replica count from its metrics and then clamps it between
minReplicas and maxReplicas, as the
horizontal pod autoscaling page
describes. When the two bounds are the same, the clamp always wins. Whatever the metrics say, the
answer is the pinned number.
A pinned HPA costs in two directions. At quiet times the workload runs its full count, paying for
pods that a real range would have removed. At peaks it cannot add capacity. It also misleads: the
HPA manages the target’s replicas field, so someone who edits the Deployment’s replica count
will see it overwritten and conclude the autoscaler is doing its job.
Finding pinned HPAs
kubectl get hpa -A -o json | jq -r ' .items[] | select(.spec.minReplicas == .spec.maxReplicas and .spec.maxReplicas > 1) | "\(.metadata.namespace)/\(.metadata.name) pinned at \(.spec.maxReplicas)"'kubectl get hpa -A also shows MINPODS and MAXPODS side by side for a quick scan.
Equal bounds above one
ZopNight needs both bounds to be reported. It fires when they are equal and the maximum is not 1; for example, 3 and 3 fires, 2 and 5 does not. The case of 1 and 1 is excluded on purpose, because HPA cannot scale already reports any HPA with a maximum of 1. The finding names the pinned count. There is no metric or time window.
When the rule does not apply
An HPA with only a maximum set gets the default minimum of 1, so it can only be pinned at 1 and 1, which this rule leaves to the maximum-of-1 check. The rule cannot tell a temporary pin from a permanent one. HPAs held at the top of a real range are reported by HPA at max capacity instead.
A low-severity reliability finding
The recommendation carries no savings estimate. Any saving from restoring a range depends on how far below the pinned count the workload would scale at quiet times, which ZopNight does not model here.
Restoring a range, or removing the HPA
- Check the manifest history to see whether the pin was a deliberate choice or a leftover from an incident.
- To autoscale, lower the minimum to what the service needs at its quietest, for example
kubectl patch hpa <name> -p '{"spec":{"minReplicas":2}}', keeping the maximum as the peak. - To run a fixed size, delete the HPA and set
replicason the Deployment or StatefulSet directly, so the manifest states the real behaviour.