Resource requests and limits
CPU and memory are the two things you tell Kubernetes about, and the two fields are not symmetrical. Getting them wrong is the most common cause of a workload that is slow, a node that is unstable, or a Pod that is evicted for no obvious reason.
The short version:
- A request is a promise to the scheduler — “I need at least this much”. It decides which node the Pod lands on, and how the Pod is treated under pressure.
- A limit is a ceiling enforced at runtime — “you may not exceed this”. What happens when you hit it depends on the resource.
Reference
- https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
- https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/
The Asymmetry That Matters
| CPU | Memory | |
|---|---|---|
| Exceeding the limit | Throttled — the process is slowed, not killed | Killed — OOMKilled, and the container restarts |
| Why | CPU is compressible: sharing it means going slower | Memory is not: you cannot partially give someone a byte |
| Symptom | Latency, timeouts, “the app is slow under load” | CrashLoopBackOff with a Restart Count climbing |
A CPU limit that is too low produces a working but slow application — the failure mode is subtle and easy to blame on the code. A memory limit that is too low produces an obvious crash, which is why the second is usually fixed first.
Terminology
requests— Used for scheduling, and for deciding what to evict when a node is under pressure.limits— Enforced by the kernel through cgroups.- QoS class — A label Kubernetes derives from your requests and limits, which decides eviction order.
| QoS class | When | Eviction order |
|---|---|---|
Guaranteed | Every container has requests equal to limits, for both CPU and memory | Last |
Burstable | Requests set, limits higher or absent | Middle |
BestEffort | Nothing set at all | First |
Guaranteed is not automatically what you want — it prevents bursting, which wastes capacity on spiky workloads. But BestEffort for anything you care about means being the first thing killed when a node gets tight.
LimitRange— A namespace-level default and bound, so a Pod that specifies nothing still gets sensible requests rather than none.ResourceQuota— A namespace-level cap on totals: total CPU, total memory, number of Pods, number of PVCs.
Exercises
Cause an OOM kill, on purpose.
kubectl run oom --image=docker.io/library/busybox --restart=Never -- \ sh -c 'dd if=/dev/zero of=/dev/null bs=1M count=200' --limits=memory=64MiActually set the limit in a manifest rather than a flag, then watch:
kubectl get pod oom kubectl describe pod oom | grep -A5 'Last State'Reason: OOMKilled,Exit Code: 137. You will see this for real, and now you will recognise it.Observe throttling. Give a container a CPU limit of
100mand have it spin in a loop doing arithmetic. Compare how long the same loop takes with a limit of1000m. There is no error, no restart and no event — just slowness.kubectl top podshows usage pinned at exactly the limit.Try
kubectl top, which needs metrics-server and is available in your cluster:kubectl top nodes kubectl top pods --containersRequests are what you asked for; this is what you are actually using. The difference is the interesting part.
Set a default with a LimitRange. Apply one with
defaultRequestanddefault, then create a Pod that specifies nothing and check its resulting QoS class:kubectl get pod <pod> -o jsonpath='{.status.qosClass}{"\n"}'
Gotchas Worth Knowing
- A limit without a request makes the request equal the limit, which can make a Pod unschedulable for no visible reason.
- No resources at all means
BestEffort— the first thing evicted, and the last thing anyone investigates. - Memory limits below what the runtime needs produce restarts that look like application bugs. Check
kubectl describe podbefore reading any code. - Aggressive CPU limits hurt latency. For latency-sensitive services, it is often better to set a generous CPU request and no CPU limit, and let the SLO do the talking.
- Requests are a reservation. Setting them far above real usage wastes the node just as surely as overcommitting it.
- A
ResourceQuotaon a namespace makes requests mandatory — in a quota’d namespace, a Pod with no requests is rejected outright. That is usually a feature.
What to Take Away
- Requests decide placement and eviction; limits decide enforcement.
- CPU over the limit is throttled; memory over the limit is killed.
Guaranteed,BurstableandBestEffortare consequences of your numbers, not separate settings.- Size from measurement —
kubectl topand real load — rather than from guesswork.