Skip to content
DevOpsChiefBook a call
← All notes

Resource requests and limits are probably copied from somewhere

Open a random deployment manifest in most clusters and the resource block will be identical to the one next to it. Same requests, same limits, round numbers. It was copied from the first service that worked, and it has been propagating ever since.

This matters in both directions. Requests that are too high waste capacity and inflate the node bill. Requests that are too low cause eviction and noisy-neighbour problems. And limits — particularly CPU limits — cause outages that look like application bugs.

Start with what the workloads actually use

Before changing anything, get percentile usage per container over a period long enough to include a real peak. A fortnight minimum; a month if you have monthly batch work.

You want the distribution, not the average. A service averaging 200m CPU with a p99 of 1200m is a different sizing problem to one that sits flat at 400m, and the average hides it.

A workable starting point: set the CPU request near p95 of observed usage and the memory request near peak, then watch. This is a starting point, not an answer — the point is to replace a copied number with a measured one.

CPU limits deserve particular scepticism

A CPU limit does not cap how much CPU a container may use in the way most people picture. It throttles the container within each scheduling period once it exceeds its quota. A service that briefly needs a burst — during startup, during garbage collection, while handling a spike — gets throttled instead, and the symptom is latency, not an OOM kill.

The result is a class of incident that looks like a slow application and is actually a scheduling constraint. It is one of the most common findings in a cluster review and one of the least suspected by the teams experiencing it.

For most workloads, setting CPU requests carefully and omitting CPU limits entirely produces better behaviour. Requests already guarantee a floor and inform scheduling; the limit mostly buys unpredictable throttling. There are exceptions — genuinely untrusted workloads, or multi-tenant clusters where one team can starve another — but the default should be scepticism rather than reflex.

Memory limits are the opposite

Memory is not compressible. A container exceeding its memory limit is killed, not slowed. That makes memory limits genuinely useful: they contain a leak to one pod instead of taking down a node.

Set memory requests and limits to the same value for predictable workloads. This places the pod in the Guaranteed QoS class, which means it is evicted last under node pressure. For services where that matters, it is worth the capacity it reserves.

Watch for the JVM case specifically. A container limit means nothing to a JVM that was not told about it; heap settings need to derive from the container limit, or you get an OOM kill at a heap size the application thought was fine.

Do it per workload, not per cluster

The instinct after reading this is to apply a policy across everything. Resist it. A batch job, a latency-sensitive API and a background worker have genuinely different profiles, and a single policy will be wrong for at least two of them.

What does generalise is the process: measure, set requests from observed usage, be sparing with CPU limits, be deliberate with memory limits, and revisit after a traffic change.

The cost side

This is usually framed as a reliability exercise, but the capacity effect is often larger than any instance-type optimisation. A cluster where requests reflect real usage packs considerably denser than one sized by copied defaults, and node count follows requests, not usage. Teams reducing over-requested workloads regularly remove nodes without touching a single instance family.