# HPA Pinned

> Flags HPAs whose minReplicas equals maxReplicas above 1, which pin the workload at a fixed size.

Source: https://zop.dev/integrations/kubernetes/recommendations/hpa-pinned

---

## 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](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
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

```bash
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](https://zop.dev/integrations/kubernetes/recommendations/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](https://zop.dev/integrations/kubernetes/recommendations/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

1. Check the manifest history to see whether the pin was a deliberate choice or a leftover from an
   incident.
2. 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.
3. To run a fixed size, delete the HPA and set `replicas` on the Deployment or StatefulSet
   directly, so the manifest states the real behaviour.
