Skip to content
Resource requests and limits

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

The Asymmetry That Matters

CPUMemory
Exceeding the limitThrottled — the process is slowed, not killedKilledOOMKilled, and the container restarts
WhyCPU is compressible: sharing it means going slowerMemory is not: you cannot partially give someone a byte
SymptomLatency, 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 classWhenEviction order
GuaranteedEvery container has requests equal to limits, for both CPU and memoryLast
BurstableRequests set, limits higher or absentMiddle
BestEffortNothing set at allFirst

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

  1. 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=64Mi

    Actually 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.

  2. Observe throttling. Give a container a CPU limit of 100m and have it spin in a loop doing arithmetic. Compare how long the same loop takes with a limit of 1000m. There is no error, no restart and no event — just slowness. kubectl top pod shows usage pinned at exactly the limit.

  3. Try kubectl top, which needs metrics-server and is available in your cluster:

    kubectl top nodes
    kubectl top pods --containers

    Requests are what you asked for; this is what you are actually using. The difference is the interesting part.

  4. Set a default with a LimitRange. Apply one with defaultRequest and default, 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 pod before 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 ResourceQuota on 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, Burstable and BestEffort are consequences of your numbers, not separate settings.
  • Size from measurement — kubectl top and real load — rather than from guesswork.