Back to category
Kubernetes

Kubernetes Requests vs Limits: What's the Difference?

Requests drive pod scheduling; limits cap CPU and memory at runtime. Learn how each fails, how to detect throttling and OOMKills, and how to set values.

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

DimensionRequestsLimits
Used bykube scheduler, kubelet (eviction ranking), CPU sharingKernel via cgroups at runtime
When it appliesPod placement, and CPU contentionWhenever the container exceeds the value
Can a container exceed it?Yes, if the node has spare capacityNo
CPU effectRelative CPU share under contentionThrottling via CFS quota
Memory effectScheduling and eviction ranking onlyOOMKill when exceeded
If you omit itDefaults to the limit if a limit is set; otherwise no reservationNo cap (unless a LimitRange sets a default)
Main risk when too lowOverpacked nodes, contention, early evictionThrottling or OOMKills
Main risk when too highWasted cluster capacity and costLittle 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 classRuleBehavior under node memory pressure
GuaranteedEvery container sets CPU and memory requests equal to limitsLeast likely to be evicted
BurstableAt least one container sets a request or limit, but not GuaranteedAt risk when memory use exceeds requests; ranked by priority and usage above request
BestEffortNo requests or limits on any containerUsually 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"} == 1

CPU 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

SymptomLikely causeHow to confirmFix
Pod stuck in PendingRequests larger than any node's free allocatable capacitykubectl describe pod shows Insufficient cpu or Insufficient memoryLower requests to measured usage, or add node capacity
p99 latency spikes, CPU graph looks moderateCPU limit throttlingThrottling ratio query above; bursty per thread usageRaise or remove the CPU limit; set CPU request from p95 usage
Container restarts, exit code 137Memory limit too low, or a memory leakkubectl describe pod shows Last State: Terminated, Reason: OOMKilledCheck working set trend: flat then spike means limit too low; steady climb means leak
Pods evicted during node memory pressureBestEffort or Burstable pods using more memory than requestedPod events show Evicted with memory pressureSet memory requests close to real usage; use Guaranteed for critical pods
Latency degrades only on busy nodesCPU requests too low, losing contentionNode CPU near 100%, container usage far above its requestRaise CPU requests so the scheduler spreads load
Go service throttled at low CPU usageGo runtime older than 1.25 sizing GOMAXPROCS to node coresGOMAXPROCS equals node core count inside the podUpgrade to Go 1.25+, or set GOMAXPROCS to the CPU limit
JVM OOMKilled while heap looks healthyHeap plus metaspace, thread stacks, and direct buffers exceed the limitHeap usage well below limit; working set near limitSize -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

WorkloadCPU requestCPU limitMemory requestMemory limit
Latency sensitive APIp95 of measured usageNone, or 2x to 4x request if policy requires onep99 working set plus headroomEqual to request
Batch or background jobMedian usageSet, to protect neighborsPeak working setEqual to request
JVM servicep95 usageOptional; if set, not below heap sizing assumptionsPeak working set including non heapEqual to request
Critical system componentMeasured usageEqual to request (Guaranteed QoS)Measured peakEqual to request
Multitenant shared clusterMeasured usageRequired by policyMeasured peakEqual 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 react

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