Setting resource requests and limits is one of the most critical decisions you make when deploying applications to Kubernetes. Get them right, and your cluster runs smoothly, scales efficiently, and survives traffic spikes without wasting expensive cloud compute. Get them wrong, and you will face CPU throttling, mysterious out-of-memory (OOM) pod terminations, noisy-neighbor contention, or nodes that sit mostly idle while refusing to schedule new workloads.
Despite their apparent simplicity, requests and limits behave in fundamentally different ways depending on whether you are managing CPU or memory. This guide explains how the Kubernetes scheduler and Linux kernel cgroups enforce these boundaries, walks through Quality of Service (QoS) classes, analyzes CPU throttling versus OOM killing, and outlines a dependable strategy for production cluster sizing.
If you are planning resources for a deployment right now, test your numbers in our browser-based Kubernetes resource requests and limits calculator. It calculates pod totals, determines your QoS class, models node capacity, and outputs ready-to-use YAML.
Requests vs Limits: The Core Distinction
Every container in a pod can declare two types of resource boundaries for compute resources:
- Requests (
resources.requests): The minimum amount of CPU or memory guaranteed to the container. The Kubernetes scheduler (kube-scheduler) uses requests at schedule time to find a node that has enough allocatable capacity to run the pod. If no node has enough unreserved capacity, the pod remains inPendingstate. Once placed, requests establish the baseline share of node capacity reserved exclusively for your container. - Limits (
resources.limits): The absolute ceiling on resources the container is allowed to consume. Limits are enforced at runtime by the container runtime (containerd or CRI-O) through Linux kernel control groups (cgroups). A container cannot exceed its limits even if the host node has idle headroom available.
To understand the practical difference, think of requests as reservations for scheduling and limits as fences for runtime protection.
Why the Difference Matters
Many engineers assume that Kubernetes looks at actual real-time CPU and memory usage when placing pods. It does not. The scheduler only adds up the existing requests of all pods already assigned to a node. If an 8-core node has four pods each requesting 1.5 cores, the scheduler considers 6.0 cores committed, leaving 2.0 cores available for new pods—regardless of whether those existing pods are idling at 2% CPU or working at 99%.
Limits, on the other hand, play zero role in scheduling. You can configure pod limits that sum to 10 times the physical capacity of a node. This technique, known as overcommitting, allows efficient density when workloads have bursty traffic patterns, but requires strict understanding of how CPU and memory react under pressure.
Compressible vs Incompressible Resources
The single most important concept in Kubernetes resource tuning is the distinction between compressible and incompressible resources:
CPU is Compressible
CPU is measured in execution time cycles over small scheduling periods (typically 100 milliseconds in the Linux Completely Fair Scheduler, or CFS). Because compute cycles cannot be “full” in a permanent way, CPU is compressible.
When a container attempts to use more CPU than its limit allows, the kernel throttles the container by withholding CPU time slices. The process slows down, API latency climbs, and HTTP request queues build up, but the process continues running. The kernel does not kill the process.
Similarly, when a node experiences overall CPU contention because multiple burstable pods spike simultaneously, the Linux kernel divides available CPU cycles among pods proportionally based on their CPU requests.
Memory is Incompressible
Memory cannot be throttled. A byte of RAM is allocated physical space that cannot be temporarily squeezed or compressed without swapping. Because standard Kubernetes nodes run with swap disabled (or strictly limited), a container that attempts to allocate more memory than its limit allows encounters a hard boundary.
When a process asks for more memory than its cgroup memory limit, the Linux kernel terminates the process immediately via the Out-Of-Memory Killer (OOM Killer). In Kubernetes, this appears as termination reason OOMKilled with exit code 137 (128 + 9 for SIGKILL).
If an entire node runs low on physical memory because containers without limits expanded into unreserved space, the kubelet initiates node eviction. It selects pods to evict based on their Quality of Service class and relative memory consumption to protect system daemons and prevent kernel panics.
Units and Syntax: Getting the Numbers Right
Kubernetes uses specific unit conventions that differ subtly from standard desktop notations:
CPU Units
CPU is specified in units of cores or millicores:
1or1.0represents 1 full virtual CPU core (vCPU on AWS/GCP, or physical hyperthread on bare metal).500mrepresents 500 millicores, which equals 0.5 cores.100mrepresents 100 millicores (one-tenth of a core).
Fractional quantities can be written either as decimals (0.25) or millicores (250m). The millicore form is universally preferred in Kubernetes YAML manifests to prevent floating-point ambiguity. The smallest assignable increment is 1m.
Memory Units
Memory is specified as plain integers (bytes) or using standard binary or decimal suffixes:
- Binary SI suffixes (powers of 1024):
Ki(kibibytes),Mi(mebibytes),Gi(gibibytes),Ti(tebibytes). - Decimal suffixes (powers of 1000):
k,M,G,T.
A frequent mistake is writing 512M (512,000,000 bytes) when 512Mi (536,870,912 bytes) was intended. Always use binary suffixes (Mi, Gi) for Kubernetes memory manifests, as operating systems, memory chips, and runtime metrics report in base-2 powers. Note that suffixes are strictly case-sensitive: Gi is valid, whereas gi, gb, or GB will cause manifest validation errors.
Quality of Service (QoS) Classes
Kubernetes inspects container requests and limits across the entire pod to classify every pod into one of three Quality of Service tiers. The QoS class governs eviction priority when a node runs out of memory:
1. Guaranteed
A pod receives the Guaranteed class if and only if:
- Every container in the pod has both CPU and memory requests specified.
- For every container, CPU request equals CPU limit, and memory request equals memory limit.
Guaranteed pods are the highest priority workloads in your cluster. They are assigned the lowest OOM score (-997), meaning the kernel will never terminate them during node memory pressure unless a lower-priority pod has already been evicted and no other alternatives exist. Guaranteed pods are ideal for core databases, stateful caches, and mission-critical payment or checkout microservices.
2. Burstable
A pod receives the Burstable class when:
- The pod does not qualify for Guaranteed.
- At least one container has a CPU or memory request or limit configured.
Burstable is the default and most common class in production. These workloads are allowed to burst above their requests up to their configured limits when node headroom permits. However, their OOM score is proportional to the percentage of requested memory they currently consume. If a Burstable pod requests 256Mi but bursts to 1Gi, it becomes an attractive target for eviction when node resources tighten.
3. BestEffort
A pod receives the BestEffort class if:
- No container in the pod specifies any CPU or memory requests or limits.
BestEffort pods have zero resource guarantees. They can consume any unused capacity on a host, but they carry an OOM score of 1000. The instant a node experiences memory pressure, the kubelet terminates BestEffort pods first. Use this class exclusively for batch jobs, disposable development pods, or non-critical background scrapers that can tolerate frequent restarts.
The Great Debate: Should You Set CPU Limits?
While setting memory limits is universally recognized as mandatory to avoid node-wide cascading crashes, whether to set CPU limits remains one of the most hotly debated topics in cloud engineering.
The Problem with CFS Quotas
When you define a CPU limit (for example, cpu: 500m), Linux CFS assigns your container a quota of 50ms of CPU runtime per 100ms enforcement period. If a multithreaded application spawns four worker threads that execute simultaneously, all four threads burn through that 50ms allowance in just 12.5 milliseconds. For the remaining 87.5 milliseconds of the period, the kernel freezes the container.
This phenomenon causes severe latency tail degradation (p99 latency spikes) in high-throughput HTTP services, even when the overall node CPU utilization is below 30%.
Recommended Strategy
- For latency-sensitive web APIs and microservices: Set realistic CPU requests based on peak average utilization. Consider omitting CPU limits or setting them generously (e.g., 3x to 5x of the request) while relying on Horizontal Pod Autoscaling (HPA) to scale pods out before CPU saturation occurs.
- For memory: Always set memory limits, and strongly consider setting
requests.memory == limits.memory. Because memory is incompressible, allowing memory to burst unpredictably invites OOM kills. Matching memory request to limit guarantees that any memory your pod needs was already verified and reserved by the scheduler. - For multi-tenant clusters: Enforce
LimitRangeandResourceQuotapolicies per namespace so that rogue applications cannot monopolize node CPUs or exhaust cluster capacity.
Sizing Strategy: A Step-by-Step Production Workflow
Follow this practical workflow to determine healthy numbers for any workload:
- Benchmark under realistic load: Run load tests in staging or run the service in production with temporary generous limits for 7 days.
- Inspect p95 metrics: Use Prometheus, Datadog, or
kubectl top podto observe the 95th percentile of CPU and memory consumption during peak business hours. - Calculate requests: Set the CPU request equal to the p95 CPU usage, and set the memory request to
1.2xthe p95 memory footprint to account for buffer caches and GC cycles. - Define limits: Set memory limit equal to memory request for stateful or memory-sensitive runtimes (like the JVM or Node.js). For CPU limits, either leave them unconstrained in dedicated namespaces or set them to
2x-4xthe request to allow rapid warmup and burst handling. - Autoscale: Configure an
HorizontalPodAutoscalertargeting 70% average CPU utilization against your request baseline.
By calculating your pod requests and limits deliberately, you protect your applications from throttling, eliminate random OOM crashes, and ensure your Kubernetes cluster delivers predictable performance at optimal infrastructure cost.