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. The scheduler looks for a node where the requests already booked plus your new request still fit into the available (allocatable) capacity, and books yours as taken there. How much memory is physically free on that node right now plays no part in it. The basis for the value itself 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, as of September 2026).
A limit is the ceiling, and it works differently per resource. CPU is throttled once the container reaches it, while memory limits are enforced reactively, which means the kernel terminates the container once it detects memory pressure. 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 spike it may need up to 1 CPU core, and that is your limit. Kubernetes schedules the Pod on the 0.5 cores but still allows twice that 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 throttled rather than terminated: the kernel denies it further CPU time for the rest of a time window. Your application keeps running, just noticeably slower, with no visible error or restart.
Memory has no such soft brake. A memory limit you do set is not a block of memory reserved up front either, and not a barrier that stops the container at the moment it goes over. The Kubernetes documentation states that memory limits are enforced reactively: a container may temporarily use more memory than its limit allows, and the kernel only terminates it once it detects memory pressure (Kubernetes documentation on resource management, as of September 2026). That is why the kill need not become visible at the exact moment the container goes over its limit. The memory pressure the kernel reacts to can arise inside the container's own cgroup; the node does not have to be full overall. kubectl describe pod shows this as OOMKilled in the container's last state, after which Kubernetes restarts it per its restart policy, with growing delays if it keeps crashing.
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: what requests and limits say about your eviction risk
From your requests and limits, Kubernetes automatically derives a quality of service class. It does not decide directly who has to give way when resources run short, but it says a lot about the risk.
When a node comes under memory pressure, the kubelet evicts Pods to keep the node stable. The ranking does not go by QoS class, though: according to the Kubernetes documentation on node-pressure eviction (as of September 2026), what counts is usage above the requests first, then pod priority, then usage relative to the requests. The class acts indirectly, because a Guaranteed Pod normally never exceeds its requests and a BestEffort Pod has no requests to exceed. Guaranteed therefore lowers the risk of being evicted. It is not protection from going down, and it costs you 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, or too tight, which causes throttling and OOM kills. Both are avoidable 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 that observation and suggest values. I still only reach for it in an emergency, and its own documentation gives solid reasons: whenever it updates a Pod's resources, the Pod is recreated, so every container inside restarts. It cannot guarantee that an evicted Pod comes back up. And it is explicitly not meant to run alongside the Horizontal Pod Autoscaler on the same resource metric, meaning CPU or memory (known limitations of the VPA, as of September 2026). In my clusters the Horizontal Pod Autoscaler is by far the more stable route: it changes the number of replicas instead of the resources of a running Pod. The two solve different problems, one scales out, the other scales up. If you only want the VPA for measurements, it has a mode in which it writes recommendations only and leaves running Pods alone.
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, because kubectl top does not show it directly. Leaving one out, on the other hand, risks a single buggy application claiming an entire node. There is no universal answer: how much a CPU limit 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 own line: I set requests almost everywhere, because that is what the scheduler plans with. Memory limits I set as a rule, without exception. CPU limits I set situationally, depending on how well the cluster is equipped and how critical the application is. I hold them to be less critical than memory limits anyway, because a throttled process keeps running while a process above its memory limit dies. The recommendation that follows: a CPU limit pays off above all where several teams share a namespace and one application can slow down the others. On isolated, well monitored workloads, skipping it is a defensible choice as long as the request fits.
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 a 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. That puts the Pod in the Burstable class: on memory, request and limit are equal, on CPU there is room to move.
LimitRange and ResourceQuota: limits for the whole namespace
Setting requests and limits on an individual Pod is half the job. The other half is making sure nobody forgets them or sets them too generously, and 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. As a default it pays to create both objects in every new namespace, so that one forgotten limit does not become a problem for the whole cluster. To be honest about my own practice: I do not do this everywhere, but where several parties share a cluster. Anyone who wants to enforce these rules technically, for instance with a policy engine like Gatekeeper, can stop a manifest without requests from reaching the cluster at all.
Frequently asked questions
What is the difference between requests and limits in Kubernetes?
The request is the value Kubernetes counts against a node's available capacity for scheduling and books as taken there. The limit is the ceiling for actual consumption: CPU gets throttled as a hard limit, memory is enforced reactively and ends in termination once the kernel detects memory pressure. 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 it is a deliberate choice that allows more flexibility during spikes.
What does OOMKilled mean and how do I avoid it?
OOMKilled means the kernel terminated the container because memory ran out. There are two cases: the container used more memory than its limit allows, or the node ran out of memory altogether, which also hits containers with no limit of their own. Enforcement is reactive, so not necessarily at the moment the limit is exceeded. You avoid it with a memory limit derived from a realistic load test rather than a guess, with enough headroom on the nodes, 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. The Vertical Pod Autoscaler can suggest values, but in its active modes it intervenes in running Pods; I only use it in an emergency. A single snapshot is never enough.
Are requests and limits enough resource control for a namespace on their own?
No. For the whole namespace, a LimitRange adds defaults and per-container limits, while a ResourceQuota caps the sum of all requests and limits. Together they stop one forgotten value from loading the entire cluster.
Where to go next
Requests and limits are the foundation for two topics that build on them: if you scale automatically, Kubernetes Autoscaling with HPA works from these exact requests, and whether your Pod even notices it is struggling is decided by Liveness and Readiness Probes Explained. If you separate several teams inside one cluster, the foundation is Kubernetes Namespaces Done Right.
My suggestion for today: check one namespace 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).