# Missing CPU/Memory Limits

> A container without limits can consume whatever the node has. ZopNight reads the pod spec across 3 workload kinds and names each offending container. Note the asymmetry: a missing memory limit risks taking the node down, while a missing CPU limit is often the better choice.

Source: https://zop.dev/integrations/kubernetes/recommendations/missing-cpu-memory-limits
Updated: 2026-08-19

---

## CPU and memory limits are not the same decision

They are usually discussed together and they behave completely differently under pressure. This
is the single most useful thing to understand about this finding.

**Memory is incompressible.** A container exceeding available memory cannot be slowed down. It
gets OOM-killed. Without a limit, one leaking process can consume the node's memory and take
neighbouring pods with it.

**CPU is compressible.** A container exceeding its CPU limit is throttled, not killed. It simply
runs slower.

## Why a missing CPU limit is often correct

CPU limits cause throttling even when the node has idle capacity. A service limited to 500m gets
throttled at 500m regardless of whether the node is 90% idle. Latency spikes for no benefit to
anybody.

Many production Kubernetes practitioners deliberately set CPU *requests* and omit CPU *limits*,
letting workloads burst into spare capacity while requests guarantee the floor.

So treat the CPU half of this finding as a prompt to decide, not a defect to fix. The memory half
is closer to a genuine defect.

## What limits do give you

Quality of Service. A pod with requests equal to limits on both resources is `Guaranteed`, the
last class evicted under node pressure. Requests without limits is `Burstable`. Neither is
`BestEffort`, evicted first.

If a workload must survive node pressure, `Guaranteed` is the only class that gets you there.

## No metric here

This is a configuration read. There is no utilisation threshold and no lookback window: a
container declares limits or it does not. The rule abstains when the workload metadata carries no
containers.

## Filtering containers with no memory limit

```bash
kubectl get deploy,statefulset,daemonset -A -o json | jq -r '
  .items[] | . as $w | .spec.template.spec.containers[]
  | select((.resources.limits.memory // "") == "")
  | "\($w.kind) \($w.metadata.namespace)/\($w.metadata.name): \(.name)"'
```

Filtering on memory alone surfaces the half that actually threatens the node.

## Where to set them

A namespace `LimitRange` applies defaults to anything that does not declare its own, which fixes
the whole backlog at once and stops new workloads arriving without them.
