info@kube-it-consulting.com
BelgiumFranceSwitzerlandUnited Arab Emirates
LinkedInFacebook
Kube IT Consulting
My coursesContact us

Requests and limits — CPU and memory are not analogous

What happens when a container exceeds its CPU limit versus its memory limit, why the two answers differ, and what that means for how you set them.

Requests and limits look like one concept applied to two resources. They aren’t, and the difference is the source of a lot of confusion about why pods die.

Requests and limits do different jobs

Requests are for the scheduler. A request is a reservation: the scheduler will only place a pod on a node with that much unallocated capacity. Requests don’t constrain what a container uses at runtime.

Limits are for the kernel on the node. A limit is a ceiling enforced while the container runs. The scheduler doesn’t consider limits when placing pods.

Three consequences follow. A pod with a high request and no limit can consume an entire node. A pod with a low request and a high limit will schedule easily and then fight its neighbours. And a node can be scheduled to 100% of requests while sitting nearly idle, because requests are a claim rather than a measurement.

Exceeding a CPU limit: throttling

CPU is a compressible resource. Exceeding its limit doesn’t kill the container; the kernel throttles it. The process keeps running, just more slowly, in enforced idle periods.

This is where the trouble hides, because throttling is nearly invisible. No event, no restart, no obvious log line. Your service just gets slower, and the latency graph shows p99 spikes that correlate with nothing in the application.

The signal to watch is container_cpu_cfs_throttled_periods_total against container_cpu_cfs_periods_total. If a meaningful fraction of periods are throttled, your limit is the bottleneck rather than your code.

There’s a subtlety that catches people. Throttling is enforced per 100ms period, so an application whose work arrives in short bursts can be throttled hard while its average CPU usage sits well under the limit. Average utilisation graphs will show plenty of headroom and the application will still be slow.

Exceeding a memory limit: death

Memory is incompressible. You can’t use a fraction of a byte. Exceed the limit and the kernel OOM-kills the container immediately.

No warning, no throttling, no grace. The container’s exit code is 137 and kubectl describe pod shows OOMKilled.

That asymmetry is the whole point of this post. A CPU limit set too low degrades performance in a way that’s hard to see. A memory limit set too low kills your process in a way that’s obvious but frequently misdiagnosed as an application bug.

The two resources do not behave alike
 CPUMemory
CompressibleYesNo
Exceeding the limit throttles the processYesNo
Exceeding the limit kills the containerNoYes
Failure appears in pod eventsNoYes
Failure is visible without a metricNoYes
We recommend setting a limitPartialYes
We recommend limit equal to requestNoYes

The row that matters most is the fourth: a CPU limit set too low produces no event at all, so nothing tells you where the latency came from.

QoS classes fall out of these settings

Kubernetes derives a quality-of-service class from what you set, and it decides eviction order when a node runs out of memory.

Guaranteed — every container has requests equal to limits for both CPU and memory. Evicted last.

Burstable — requests are set and are lower than limits, or only some are set. Evicted after BestEffort.

BestEffort — nothing set at all. Evicted first.

This is why “we’ll set limits later” is a decision rather than a deferral. Unset means BestEffort, which means your workload is first against the wall when a node comes under memory pressure.

The advice

Always set memory requests and limits, and set them equal. Memory usage for most services is fairly predictable, an OOM kill is severe, and equal values buy you Guaranteed QoS. Base the number on observed peak plus real headroom, not on a guess and not on the average.

Set CPU requests. Think hard before setting CPU limits. The request gets you scheduled correctly. The limit caps your ability to use idle capacity that’s sitting there anyway, and it introduces throttling that’s difficult to diagnose.

The counter-argument is real: without limits, a runaway container can starve its neighbours. Which way to go depends on whether your nodes run trusted first-party workloads or a mixed multi-tenant set. On a dedicated cluster running your own services, we usually omit CPU limits and monitor. On shared infrastructure, set them, and then watch the throttling metric.

Don’t copy requests from another team’s manifest. The numbers in the YAML you inherited were right for a different workload on different hardware, if they were ever right at all. Measure your own.

Verifying

Two commands worth knowing:

# Actual usage against what you asked for
kubectl top pods --containers

# What the node has committed versus what it has
kubectl describe node <node> | grep -A 8 "Allocated resources"

The second one is the reality check. A node showing 95% of CPU requested and 10% actually used means your requests are fiction, and you’re paying for nodes you don’t need.

This material is examinable in CKAD’s largest domain and CKA’s scheduling section. The throttling behaviour, though, is one of those things you only really learn by watching a latency graph and not understanding it for a week.

Next steps

Practise it

Run the CKAD track in a real terminal

Every objective on CertLabs is graded against live system state rather than the command you typed, on a sandboxed cluster that resets between exercises. The CKAD track covers Deploy, config, probes.

Open CertLabs

CertLabs is our own practice platform.

Get help

Running this in production?

We operate Kubernetes and OpenShift for clients across the EU and the Gulf, and train the teams who inherit them. Platform assessments, migrations and hands-on enablement.

Talk to us