# HPA Has No Metrics

> Flags HPAs whose spec lists no metric targets, leaving scaling to an implicit default or to nothing at all.

Source: https://zop.dev/integrations/kubernetes/recommendations/hpa-has-no-metrics

---

## What an HPA with no named metric scales on

Every scaling decision is `desiredReplicas = ceil(currentReplicas x currentMetricValue /
desiredMetricValue)`, per the
[horizontal pod autoscaling page](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/).
Without a metric there is no ratio to compute.

Kubernetes papers over the gap in one place. The
[HPA v2 API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/horizontal-pod-autoscaler-v2/)
says that if `metrics` is not set, the default metric is 80% average CPU utilization, and the
[API defaulting code](https://github.com/kubernetes/kubernetes/tree/master/pkg/apis/autoscaling/v2)
fills that in when the object is stored. That default is a guess on the owner's behalf. A
memory-bound or queue-driven service scaled on 80% CPU either never scales or scales at the wrong
moment, and CPU utilization can only be computed when the target's containers set CPU requests.

## Checking the stored metric list

```bash
kubectl get hpa -A -o json | jq -r '
  .items[]
  | select((.spec.metrics // []) | length == 0)
  | "\(.metadata.namespace)/\(.metadata.name)"'
```

Also look at one HPA in full with `kubectl get hpa <name> -o yaml`. If the output shows a CPU
target you never wrote, the API default is in effect and the owner should decide whether 80% CPU
is the signal they want.

## An absent or empty list is the trigger

ZopNight reads the HPA's metric list as it collected it and fires when the list is missing or has
no entries. It does not read the HPA's conditions or current metric values, and there is no
window. Once a metric is present in what ZopNight collects, the finding clears on the next
evaluation.

## Why the finding is rare in practice

Because the check reads the collected specification, the result depends on what the API returns.
Read through `autoscaling/v2`, an HPA that omitted `metrics` comes back with the 80% CPU default
filled in, so it has a metric and this rule does not flag it. The rule therefore does not catch
HPAs that silently rely on that default. It also does not verify whether the target's
containers have CPU requests, so an HPA with a CPU target that cannot be computed is not caught
here; the requests side is covered by
[Missing CPU/memory requests](https://zop.dev/integrations/kubernetes/recommendations/missing-cpu-memory-requests).

## Reliability finding with no dollar figure

This medium-severity recommendation has no savings estimate. An HPA without a deliberate signal
either wastes replicas at quiet times or fails to add them at busy ones, and neither shows up
until traffic changes.

## Naming the metric explicitly

1. Decide what load looks like for the service: CPU, memory, requests per second or queue depth.
2. Add the metric to the HPA spec, for example a `Resource` metric for `cpu` with
   `target.type: Utilization` and `averageUtilization: 70`.
3. For custom or external metrics, confirm the metrics adapter serving `custom.metrics.k8s.io`
   or `external.metrics.k8s.io` is installed.
4. Run `kubectl describe hpa <name>` and check `ScalingActive` is True with a valid metric found.
