Workloads whose CPU request exceeds their observed 30-day peak
What does ZopNight detect here?
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.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1770 · RC-1870 · RC-1970 · RC-1771 · RC-1871 · RC-1971 |
| Category | rightsizing |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | internal/rules/k8s/cpu_over_provisioned.go |
Where it applies
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
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> --containerskubectl top shows the instant, not the peak; treat it as a sanity check on the request, not
as the basis for resizing.