# CPU Over-Provisioned

> Reclaimable CPU is measured against the peak of the `CPUUsageMillicores` series over 30 days, never an average. ZopNight only recommends a resize when the reclaimable share falls between 5% and 85%. Outside that band the reading is treated as noise or as broken, and nothing is emitted.

Source: https://zop.dev/integrations/kubernetes/recommendations/cpu-over-provisioned
Updated: 2026-08-19

---

## Sized against the peak, deliberately

The recommended request is the **maximum** observed value in the usage series, not a percentile
and not a mean.

That is the conservative choice and it is intentional. Peak is always at least as large as p95,
so a request resized to the peak still covers the busiest hour the workload actually had. The
rule gives up some theoretical saving in exchange for never proposing a request that the
observed history already exceeded.

The saving is `per-pod cost × (request − peak) / request`, and it is derived from the **full**
metric's peak rather than the 30-day window's. A calm recent window must not be allowed to hide
an older spike and talk the target down.

## The 5%–85% band

Two guards bracket every recommendation.

Below **5%** reclaimable, the number is inside the noise floor, and in practice a sub-5% reading
is usually a stale or mismatched series rather than a genuinely well-sized workload. Nothing is
emitted.

Above **85%**, the request is either wildly mis-set or the usage series is empty or broken. A
"reclaim 97% of your CPU" recommendation is far more likely to be a data fault than a finding,
so that too is suppressed rather than shipped.

## Why CPU and memory are treated differently here

CPU is compressible. A request trimmed below the working set causes throttling: the pod slows
down, and the scheduler keeps it running. Recovery is a config change.

Memory is not. Trim a memory request below the real working set and the kernel OOM-kills the pod.
That asymmetry is why the memory sibling of this rule abstains entirely today while this one
emits, and it is a deliberate position rather than an implementation gap.

## Five conditions that produce silence

Nothing is emitted unless all of these hold: the workload is `running` (not stopped, not
degraded mid-rollout); a per-pod CPU request exists to reclaim against; a per-pod cost basis is
available, so no zero-cost recommendation is ever produced; the `CPUUsageMillicores` series is
present with sufficient coverage, not a point-in-time snapshot; and the peak is genuinely below
the request.

`metrics-server` alone gives snapshots, not history. Clusters running only `metrics-server`
will see this rule abstain across the board until a metrics pipeline retains a series.

## Comparing CPU requests against kubectl top

```bash
kubectl get deploy -A -o json | jq -r '
  .items[] | "\(.metadata.namespace)/\(.metadata.name)\t" +
  ([.spec.template.spec.containers[].resources.requests.cpu // "none"] | join(","))'
kubectl top pods -n <ns> --containers
```

`kubectl top` shows the instant, not the peak; treat it as a sanity check on the request, not
as the basis for resizing.
