Kubernetes Requests vs Limits: What's the Difference?
In Kubernetes, a resource request is the amount of CPU or memory the scheduler reserves for a container when placing its pod on a node, and a resource limit is the maximum the container is allowed to use at runtime. Requests decide where a pod runs and how CPU is shared under contention. Limits decide what happens when a container tries to use more: CPU gets throttled, and memory gets the container killed.
What Is a Resource Request?
A request tells the kube scheduler how much CPU and memory a container needs. The scheduler only places a pod on a node whose unreserved allocatable capacity covers the sum of the pod's requests. If no node has room, the pod stays Pending.
Requests are not caps. A container that requests 250m CPU can use more when the node has spare capacity. Under CPU contention, requests also act as weights: containers get CPU time roughly in proportion to what they requested.
What Is a Resource Limit?
A limit is the most CPU or memory a container may use. According to the Kubernetes resource management documentation, the kubelet and container runtime apply limits, and on Linux the kernel enforces them with cgroups. Limits do not affect scheduling at all.
Requests vs Limits at a Glance
| Dimension | Requests | Limits |
|---|---|---|
| Used by | kube scheduler, kubelet (eviction ranking), CPU sharing | Kernel via cgroups at runtime |
| When it applies | Pod placement, and CPU contention | Whenever the container exceeds the value |
| Can a container exceed it? | Yes, if the node has spare capacity | No |
| CPU effect | Relative CPU share under contention | Throttling via CFS quota |
| Memory effect | Scheduling and eviction ranking only | OOMKill when exceeded |
| If you omit it | Defaults to the limit if a limit is set; otherwise no reservation | No cap (unless a LimitRange sets a default) |
| Main risk when too low | Overpacked nodes, contention, early eviction | Throttling or OOMKills |
| Main risk when too high | Wasted cluster capacity and cost | Little protection for neighbors |
How CPU and Memory Limits Fail Differently
CPU Limits: Throttling
CPU is compressible, so exceeding a CPU limit slows the container instead of killing it. The kernel's Completely Fair Scheduler (CFS) gives each container a quota per scheduling period, 100 ms by default. A container with a 500m limit gets 50 ms of CPU time per 100 ms period. Once it spends that budget, all its threads pause until the next period, even if the node is otherwise idle.
This is why throttling hides on dashboards. A multithreaded service with 4 busy threads can burn a 50 ms budget in about 12.5 ms of wall time, then sit paused for the remaining 87.5 ms. Average CPU usage looks moderate while p99 latency climbs.
Memory Limits: OOMKills
Memory is not compressible. When a container's memory use exceeds its limit, the kernel's out of memory (OOM) killer terminates the process. Kubernetes records the reason as OOMKilled, the exit code is 137 (128 plus signal 9, SIGKILL), and the container restarts according to the pod's restart policy. Repeated OOMKills show up as CrashLoopBackOff.
How Requests and Limits Set QoS Classes
Kubernetes assigns every pod a Quality of Service (QoS) class based on its requests and limits:
| QoS class | Rule | Behavior under node memory pressure |
|---|---|---|
| Guaranteed | Every container sets CPU and memory requests equal to limits | Least likely to be evicted |
| Burstable | At least one container sets a request or limit, but not Guaranteed | At risk when memory use exceeds requests; ranked by priority and usage above request |
| BestEffort | No requests or limits on any container | Usually evicted first, since any usage exceeds a zero request |
One rule surprises people: if you set a limit without a request, Kubernetes copies the limit into the request. A container with only limits set can end up Guaranteed and harder to schedule than you intended.
How to Detect Resource Problems
These PromQL queries use cAdvisor metrics (exposed by the kubelet) and kube state metrics, which most Prometheus compatible setups already collect.
CPU throttling ratio (share of scheduling periods where the container was throttled):
sum by (namespace, pod, container) (
rate(container_cpu_cfs_throttled_periods_total{container!=""}[5m])
)
/
sum by (namespace, pod, container) (
rate(container_cpu_cfs_periods_total{container!=""}[5m])
)Values above 0.25 for 15 minutes on a latency sensitive service deserve investigation. Throttling on a batch job may be acceptable.
Memory working set as a share of the limit (the kernel uses working set, not total usage, when deciding OOM):
sum by (namespace, pod, container) (
container_memory_working_set_bytes{container!=""}
)
/
sum by (namespace, pod, container) (
kube_pod_container_resource_limits{resource="memory"}
)Alert above 0.9 so you can act before the OOMKill.
Recent OOMKills:
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1CPU usage as a share of requests (finds overprovisioning):
sum by (namespace, pod, container) (
rate(container_cpu_usage_seconds_total{container!=""}[5m])
)
/
sum by (namespace, pod, container) (
kube_pod_container_resource_requests{resource="cpu"}
)Containers that sit below 0.3 for weeks are reserving capacity they never use, which is often the largest source of Kubernetes cost waste.
Symptom to Cause: Troubleshooting Table
| Symptom | Likely cause | How to confirm | Fix |
|---|---|---|---|
| Pod stuck in Pending | Requests larger than any node's free allocatable capacity | kubectl describe pod shows Insufficient cpu or Insufficient memory | Lower requests to measured usage, or add node capacity |
| p99 latency spikes, CPU graph looks moderate | CPU limit throttling | Throttling ratio query above; bursty per thread usage | Raise or remove the CPU limit; set CPU request from p95 usage |
| Container restarts, exit code 137 | Memory limit too low, or a memory leak | kubectl describe pod shows Last State: Terminated, Reason: OOMKilled | Check working set trend: flat then spike means limit too low; steady climb means leak |
| Pods evicted during node memory pressure | BestEffort or Burstable pods using more memory than requested | Pod events show Evicted with memory pressure | Set memory requests close to real usage; use Guaranteed for critical pods |
| Latency degrades only on busy nodes | CPU requests too low, losing contention | Node CPU near 100%, container usage far above its request | Raise CPU requests so the scheduler spreads load |
| Go service throttled at low CPU usage | Go runtime older than 1.25 sizing GOMAXPROCS to node cores | GOMAXPROCS equals node core count inside the pod | Upgrade to Go 1.25+, or set GOMAXPROCS to the CPU limit |
| JVM OOMKilled while heap looks healthy | Heap plus metaspace, thread stacks, and direct buffers exceed the limit | Heap usage well below limit; working set near limit | Size -XX:MaxRAMPercentage to leave headroom for non heap memory |
Should You Set CPU Limits?
This is the most debated setting in Kubernetes resource management, and the right answer depends on the workload:
- Arguments against CPU limits: throttling adds tail latency even when the node has idle CPU. CPU requests already guarantee each container its share under contention, so a well requested container is protected without a limit.
- Arguments for CPU limits: multitenant clusters need a hard ceiling so one team cannot consume a node. Limits also make performance more predictable, which helps capacity planning and load tests reflect production.
How to Set Requests and Limits by Workload Type
| Workload | CPU request | CPU limit | Memory request | Memory limit |
|---|---|---|---|---|
| Latency sensitive API | p95 of measured usage | None, or 2x to 4x request if policy requires one | p99 working set plus headroom | Equal to request |
| Batch or background job | Median usage | Set, to protect neighbors | Peak working set | Equal to request |
| JVM service | p95 usage | Optional; if set, not below heap sizing assumptions | Peak working set including non heap | Equal to request |
| Critical system component | Measured usage | Equal to request (Guaranteed QoS) | Measured peak | Equal to request |
| Multitenant shared cluster | Measured usage | Required by policy | Measured peak | Equal to request |
Base values on at least one to two weeks of production usage that includes peak traffic, and revisit them after major releases. The Vertical Pod Autoscaler in recommendation mode can suggest values from history. In place pod resize, which lets you change CPU and memory on running pods without recreating them, is stable as of Kubernetes v1.35, which makes adjustments less disruptive.
Runtime Gotchas That Cause Throttling and OOMKills
Go. Before Go 1.25, the runtime set GOMAXPROCS to the node's CPU count, so a service limited to 2 CPUs on a 64 core node could run 64 threads against a 2 CPU quota and throttle hard. Go 1.25 made GOMAXPROCS container aware on Linux, defaulting to the cgroup CPU limit. Older Go versions need GOMAXPROCS set explicitly or a library such as automaxprocs. For memory, set GOMEMLIMIT a little below the container limit so the garbage collector works harder before the kernel kills the process.
JVM. Modern JVMs read container limits, and by default the maximum heap is 25% of container memory. Teams often raise -XX:MaxRAMPercentage to 75% or more and then hit OOMKills because metaspace, thread stacks, code cache, and direct buffers also count toward the limit. Leave headroom for non heap memory.
Node.js. V8's heap size may not match your container limit on older Node versions. Set --max-old-space-size below the memory limit.
Example: A Balanced Configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
spec:
replicas: 3
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
spec:
containers:
- name: checkout-api
image: registry.example.com/checkout-api:4.2.0
resources:
requests:
cpu: "500m" # p95 of observed usage over 14 days
memory: "768Mi" # p99 working set plus ~20% headroom
limits:
memory: "768Mi" # equal to request: predictable scheduling, no overcommit
# No CPU limit: latency sensitive service, avoid CFS throttling
env:
- name: GOMEMLIMIT
value: "690MiB" # ~90% of the memory limit, gives the GC room to reactMonitoring Requests and Limits in Production
Right sizing is ongoing because usage changes with every release. Middleware's Kubernetes monitoring shows throttling, memory against limits, and OOMKill events alongside the traces and logs from the same pods, so you can tell whether a latency spike came from code or from resource settings.
Frequently Asked Questions
What happens if I don't set requests or limits in Kubernetes?
The pod gets the BestEffort QoS class. The scheduler reserves no capacity for it, so it can land on an already busy node, and it is the first to be evicted when the node runs low on memory. Without a limit it can also use as much CPU and memory as the node has. A LimitRange in the namespace can apply default values automatically.
What does exit code 137 mean in Kubernetes?
Exit code 137 means the process received SIGKILL (128 plus signal 9). In Kubernetes this most often means the container exceeded its memory limit and was OOMKilled. Run kubectl describe pod and check Last State for Reason: OOMKilled. If the reason is different, the kill may have come from a failed liveness probe or a node level eviction.
Should memory requests equal memory limits?
For most production workloads, yes. Because memory cannot be throttled, a gap between request and limit means the scheduler plans for less memory than the container may use, which can overcommit the node and trigger evictions or OOMKills. Setting them equal makes scheduling honest. Some teams leave a small gap for workloads with rare, short spikes.
How do I know if my pod is being CPU throttled?
Compare container_cpu_cfs_throttled_periods_total with container_cpu_cfs_periods_total from cAdvisor. The ratio shows the share of scheduling periods where the container hit its CPU limit. Sustained values above about 25% on a latency sensitive service usually explain tail latency. You can also read cpu.stat in the container's cgroup for nr_throttled and throttled time.
Can Kubernetes change requests and limits without restarting a pod?
Yes, on recent versions. In place pod resize lets you change CPU and memory requests and limits on a running pod, and it is stable as of Kubernetes v1.35. Some limitations apply: only CPU and memory can be resized, the pod's QoS class cannot change, and decreasing memory is best effort. Check your cluster version and container runtime support first.