# HorizontalPodAutoscaler

> HorizontalPodAutoscalers move replicas between minReplicas and maxReplicas, and the minimum is a cost floor that holds capacity at 3 a.m. ZopNight records target, bounds, current and desired replicas, and conditions, flagging saturation only when ScalingLimited reports TooManyReplicas, since desiredReplicas is clamped and can never exceed the max.

Source: https://zop.dev/integrations/kubernetes/hpa
Updated: 2026-08-19

---

A HorizontalPodAutoscaler moves a workload's replica count between a floor and a ceiling based on observed metrics. Both bounds are cost decisions: the floor is capacity you pay for at 3 a.m., and the ceiling is where saturation hides.

## minReplicas is a spending floor

Discovery records the target workload (`targetRef` and kind), `minReplicas`, `maxReplicas`, current and desired replicas, the configured metrics, current metric readings, conditions, and the last scale time. The floor deserves the most scrutiny: an HPA with a generous minimum keeps that many pods, and the node capacity beneath them, running through every quiet hour, which is over-provisioning wearing an autoscaling costume.

## Why at-max detection reads a condition, not a comparison

Kubernetes clamps `desiredReplicas` to `maxReplicas`, so the intuitive check (desired above max) can never be observed on real data. The HPA At Max Capacity rule (RC-1717 / RC-1817 / RC-1917, high severity) instead reads the HPA's own `ScalingLimited` condition and fires only when it is True with reason TooManyReplicas: the controller's first-hand record that it wanted more replicas than the ceiling allowed. A workload that merely happens to run at exactly max is left alone, and missing or unparseable conditions cause the rule to abstain rather than guess.

## Removed and recreated around scheduled downtime

An HPA will fight any external scale-to-zero, because it exists to restore replicas. So when a namespace schedule stops an environment, ZopNight deletes the HPAs along with scaling the workloads down, then recreates them at resume with their original spec. Deleting rather than editing means there is no window where a half-configured autoscaler can act on stale metrics.

```bash
kubectl get hpa -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,TARGET:.spec.scaleTargetRef.name,MIN:.spec.minReplicas,MAX:.spec.maxReplicas,CURRENT:.status.currentReplicas
```

## Reading an HPA in review

Three questions cover most audits: does the floor reflect real overnight demand or just caution; has `ScalingLimited` been true recently, meaning the ceiling is throttling real traffic; and does the target workload still exist? The recorded conditions and last scale time answer all three without touching the cluster.
