Kubernetes requests and limits define how much CPU and memory Kubernetes reserves for a container and where its ceiling sits. Without these settings a container consumes as much as it wants and can starve other containers on the same node. With requests and limits in the manifest, you give Kubernetes the information it needs for scheduling and protection.
I run Kubernetes clusters in production and wrote the Kubernetes book published by Rheinwerk Computing. Requests and limits are one of the settings where values that are too tight and values that are too generous both cause trouble, with consequences you notice in day to day operations. You can find an overview of all my Kubernetes articles on the Kubernetes page.
Kubernetes requests versus limits: the difference in two sentences
A request is not the measured consumption, it is the value you set for scheduling: Kubernetes places the Pod on a node that still has exactly that amount free and books it as taken there. The basis for it is the measured normal need plus a safety margin. Whether a container can go beyond that is decided by the resource and the limit you set, not by the request (Kubernetes documentation on resource management).
A limit is the ceiling, and it works differently per resource: CPU is throttled once the container reaches it, while memory ends in the kernel terminating the process when the container asks for more. Without a limit, a container could in theory claim an entire node for itself. Setting a limit above the request lets the container briefly use more than its request without being scheduled for that extra amount.
A practical example: an application measures at roughly 0.4 CPU cores and 450 MiB of memory in normal operation. With some margin you set the request to 0.5 cores and 512 MiB. During a short traffic spike it may need up to 1 CPU core. That is your limit. Kubernetes schedules the Pod based on the 0.5 cores but still allows it to briefly use twice that amount when needed.
What happens at the limit: CPU throttling versus out of memory
CPU and memory behave differently once a container hits its limit, and that difference often decides how you should debug an issue. When a container exceeds its CPU limit, it is not terminated but throttled: the kernel denies it further CPU time for the rest of a time window. Your application keeps running, just noticeably slower, without any visible error or restart.
Memory has no such soft brake. When a container exceeds its memory limit, the kernel kills the process immediately with an out of memory error, known as an OOM kill. kubectl describe pod shows this as OOMKilled in the container's last state, after which Kubernetes restarts it according to its restart policy.
| Resource |
Behavior at the limit |
Visible in kubectl |
| CPU |
throttling, no termination |
high latency, kubectl top does not show throttling directly |
| Memory |
immediate kill of the process |
OOMKilled in the container status, restart |
For troubleshooting this means: if an application slows down with no obvious error, check the CPU limit first. If it keeps restarting instead, check the memory limit and the Pod's events first.
QoS classes: how Kubernetes ranks your Pods under pressure
From your requests and limits, Kubernetes automatically derives a quality of service class that decides which Pod on a node has to give way first when resources run short.
| Class |
Condition |
Behavior under pressure |
| Guaranteed |
request equals limit for CPU and memory in every container |
evicted last |
| Burstable |
at least one request or limit set, but not equal |
evicted after BestEffort |
| BestEffort |
no requests and limits set at all |
evicted first |
When a node comes under memory pressure, the kubelet uses these classes to decide which Pods to terminate first to keep the node stable. A Pod in the Guaranteed class is the safest choice for anything that must not go down, at the cost of the flexibility a limit above the request would give you.
Finding the right values instead of guessing
Setting requests and limits by gut feeling almost always leads to one of two mistakes: too generous, which wastes capacity on the node, or too tight, which causes throttling and OOM kills. Both mistakes can be avoided with the same tools.
If Prometheus already runs in your cluster, the P95 usage of CPU and memory over a longer period is the most reliable basis for your requests. The Vertical Pod Autoscaler can automate the same work: in recommender mode it observes actual usage and suggests values without touching your running Pods at all. That is the mode I reach for first in my own clusters, because it gives you numbers before it starts acting automatically.
A single measurement is not enough. Watch your application over several days and through a real load test, because one quiet night tells you nothing about usage at the end of the month.
CPU limits: set them or leave them out?
There is a debate in the community that I resolve differently depending on the cluster: CPU limits are a common cause of throttling that looks inexplicable at first glance, because kubectl top does not show it directly. Leaving out a CPU limit, on the other hand, risks a single buggy application claiming an entire node for itself. There is no universal answer here: how much a CPU limit actually costs you depends on the load profile, and anyone recommending you drop them across the board is generalizing an observation from latency-critical services to every workload.
My rule of thumb: I always set memory limits, because an OOM kill is visible and manageable. I am more careful with CPU limits and set them mainly where several teams share a namespace and I have to stop one application from slowing down the others. On isolated, well monitored workloads I sometimes deliberately skip the CPU limit and rely on a well chosen request instead.
Here is a Pod manifest with requests and limits for both resources:
apiVersion: v1
kind: Pod
metadata:
name: checkout-service
spec:
containers:
- name: checkout-service
image: registry.example.com/checkout-service:stable
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"
The value 250m stands for 250 millicpu, a quarter of one CPU core. Memory is given in mebibytes (Mi) or gibibytes (Gi), since bytes would only make the manifest harder to read. In this example the container may use twice the CPU it requested, but gets no headroom on memory at all. That puts the Pod in the Burstable class: memory is guaranteed, CPU has room to move.
LimitRange and ResourceQuota: limits for the whole namespace
Setting requests and limits on an individual Pod is only half the job. The other half is making sure nobody forgets them or sets them too generously. Two objects at the namespace level handle that.
A LimitRange sets default requests and limits for manifests that do not bring their own, and can additionally enforce minimum and maximum values per container. A ResourceQuota caps the sum of all requests and limits across the entire namespace, for example a maximum of 10 GiB of memory for all Pods combined. In my client projects, platform teams set both objects by default in every new namespace, so that one forgotten limit does not become a problem for the whole cluster. Anyone who wants to enforce these rules technically, for instance with a policy engine like Gatekeeper, can prevent a manifest without requests from ever reaching the cluster in the first place.
Frequently asked questions
What is the difference between requests and limits in Kubernetes?
The request is the value Kubernetes counts for scheduling on a node and books as taken there. The limit is the ceiling for actual consumption: CPU gets throttled, memory ends in the process being terminated. Between request and limit a container may fluctuate freely.
Should I set CPU limits for every container?
It depends on the cluster. In shared namespaces with several teams, a CPU limit stops one application from slowing down the others. On isolated, well monitored workloads, skipping the CPU limit is a deliberate choice that allows more flexibility during traffic spikes.
What does OOMKilled mean and how do I avoid it?
OOMKilled means a container exceeded its memory limit and the kernel terminated it as a result. You avoid it with a memory limit derived from a realistic load test rather than a guess, and by asking whether the application really needs that memory or simply never gives it back.
How do I find the right values for requests and limits?
The most reliable numbers come from Prometheus measurements over several days plus a real load test, complemented by the Vertical Pod Autoscaler in recommender mode. A single snapshot is not enough for this.
Are requests and limits enough resource control for a namespace on their own?
No. For the whole namespace, a LimitRange adds default values and per-container limits, while a ResourceQuota caps the sum of all requests and limits in the namespace. Together they stop one forgotten value from putting load on the entire cluster.
Where to go next
Requests and limits are the foundation for two topics that build directly on them: if you scale your application automatically, Kubernetes Autoscaling with HPA works from these exact requests. And whether your Pod even notices it is struggling under load is decided by Liveness and Readiness Probes Explained. If you are separating several teams inside one cluster, the right foundation for that is Kubernetes Namespaces Done Right.
My suggestion for today: check one of your namespaces with kubectl describe pod to see whether requests and limits are set at all, and compare them with actual usage via kubectl top pod. The official reference is the Kubernetes documentation on managing resources.
In full detail, with a worked calculation, an overcommitment diagram and troubleshooting steps, this is covered in chapter 8 of my Kubernetes Practical Guide (Rheinwerk Computing).