KubernetesXII · Resource Requests and LimitsResource requests and limits
CPU and memory requests — scheduling and the resource model
What you'll learn
- Distinguish CPU and memory as resource types (compressible vs incompressible)
- Configure resource requests for scheduling
- Reason about request sizing and its impact on placement
- Understand how requests and limits interact with the kubelet
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Resource requests and limits are the foundation of Kubernetes scheduling and enforcement. CPU and memory behave differently under contention; requests drive scheduling, limits drive cgroup enforcement. This lesson covers the resource model, how the scheduler uses requests, and how the kubelet enforces limits.
CPU and memory as resource types
Two fundamentally different resource types:
- CPU: compressible. A container that exceeds its CPU share is throttled — the kernel slows it down. The container can continue running; it just runs slower.
- Memory: incompressible. A container that exceeds its memory limit is OOMKilled — the kernel kills the process. The container must restart.
flowchart LR
CPU["CPU usage > limit"] --> Throttle[Throttled; runs slower]
Mem["Memory usage > limit"] --> OOM[OOMKilled; restarts]
This asymmetry shapes everything about resource management:
- CPU pressure is gradual: latency goes up, throughput goes down. The application has time to react.
- Memory pressure is immediate: the process dies. There is no graceful response.
Production discipline: every container must have a memory limit. CPU limits are debated (some teams omit them to avoid throttling); memory limits are mandatory.
Resource requests and limits
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Two fields per resource:
requests: the resources reserved for the container. The scheduler uses this for placement.limits: the maximum the container can use. The kubelet enforces this via cgroups.
The semantics:
- CPU request: the scheduler reserves 100 millicores of CPU on the chosen node. If the node cannot fulfil this request, the Pod is not scheduled there.
- CPU limit: the container can use up to 500 millicores. If it tries to use more, the kernel throttles it.
- Memory request: the scheduler reserves 128 MiB of memory on the chosen node.
- Memory limit: the container can use up to 512 MiB. If it tries to use more, the kernel OOMKills it.
The relationship between requests and limits determines the QoS class (Part XIII):
- Guaranteed: requests == limits for both CPU and memory.
- Burstable: requests < limits for at least one resource.
- BestEffort: no requests or limits set.
CPU units
CPU is specified in cores or millicores:
1= 1 core.100m= 100 millicores = 0.1 core.0.5= 500 millicores = half a core.
A node with 4 cores has 4000 millicores of CPU. A Pod requesting 500m reserves 1/8 of the node’s CPU.
Memory units
Memory is specified in bytes with IEC prefixes:
128Mi= 128 × 1024 × 1024 bytes = 128 mebibytes.1Gi= 1024 MiB = 1 gibibyte.1G= 10^9 bytes = 1 gigabyte (slightly different from Gi).
The Mi and Gi suffixes are exact powers of 2; M and
G are powers of 10. For memory limits, prefer Mi/Gi
to avoid confusion.
How requests drive scheduling
The scheduler’s job: place each Pod on a node that can satisfy its requests. The scheduler subtracts the Pod’s requests from each candidate node’s allocatable; if the result is non-negative for CPU and memory, the Pod can be scheduled.
flowchart LR
Pod[Pod: requests cpu=500m, mem=1Gi] --> Sched[Scheduler]
Sched --> Node1[Node 1: allocatable cpu=2, mem=4Gi]
Sched --> Node2[Node 2: allocatable cpu=4, mem=8Gi]
Node1 --> Check1[2 - 0.5 >= 0 and 4 - 1 >= 0]
Check1 -- yes --> Fit1[Pod fits]
Node2 --> Check2[4 - 0.5 >= 0 and 8 - 1 >= 0]
Check2 -- yes --> Fit2[Pod fits]
Both nodes can host the Pod. The scheduler picks one based on other criteria (affinity, taints, spread).
If no node can satisfy the Pod’s requests, the Pod stays in
Pending with FailedScheduling events.
How the kubelet enforces limits
The kubelet configures cgroups for each container:
cpucgroup: contains the CFS quota and period. The container can use up to its CPU limit; beyond that, the kernel throttles it.memorycgroup: contains the memory limit. If the container’s memory usage exceeds the limit, the kernel triggers the cgroup OOM killer.
flowchart LR
Kubelet[Kubelet] --> CGroup["cgroup: cpu"]
Kubelet --> MGroup["cgroup: memory"]
CGroup --> Throttle[Throttled]
MGroup --> OOM[OOMKilled]
The enforcement is at the kernel level, not the kubelet. The kubelet sets up the cgroups; the kernel enforces them.
The CPU request and CFS shares
CPU request determines CFS shares, not just the scheduler. A container’s CFS shares are proportional to its CPU request. Under contention, the kernel allocates CPU proportionally to shares.
containers:
- name: low-priority
resources:
requests:
cpu: 100m
- name: high-priority
resources:
requests:
cpu: 1000m
Under CPU contention, the high-priority container gets 10x the CPU of the low-priority container. This is CFS shares behaviour.
Request sizing in production
The discipline:
- CPU request: typical usage. The scheduler reserves this; under-utilisation wastes node capacity, over- utilisation causes scheduling failures.
- CPU limit: peak usage plus headroom. Throttling at peak is better than starving other containers.
- Memory request: typical usage. Same logic as CPU request.
- Memory limit: peak usage plus headroom. OOMKill at peak is worse than scheduling failure, but a memory leak that grows unboundedly is worse than OOMKill.
For a typical web service:
resources:
requests:
cpu: 100m # typical: 50-100m
memory: 128Mi # typical: 80-128Mi
limits:
cpu: 500m # peak: 300-500m
memory: 512Mi # peak: 300-512Mi
Production discipline: measure before sizing. Use
kubectl top pod or Prometheus metrics to understand actual
usage; size requests and limits accordingly.
Production patterns
VPA (Vertical Pod Autoscaler) for sizing:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web
updatePolicy:
updateMode: Auto
VPA observes the workload’s actual usage and recommends (or applies) resource requests. Part LXXXIII-Kubernetes-VPA covers VPA in depth.
ResourceQuota for namespace limits:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-prod-quota
namespace: team-a-prod
spec:
hard:
requests.cpu: "32"
requests.memory: 64Gi
limits.cpu: "64"
limits.memory: 128Gi
The quota caps the total requests and limits in the namespace. Part CVIII-Kubernetes-ResourceQuota covers quotas in depth.
LimitRange for default requests:
apiVersion: v1
kind: LimitRange
metadata:
name: default
namespace: team-a-prod
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
The LimitRange sets default requests and limits for containers that don’t declare them. Part CIX-Kubernetes-LimitRange covers LimitRange in depth.
Cross-course references
- The Linux course part
XXXVII-Linux-Resourcescovers cgroups, CFS quotas, and memory limits; the kubelet exposes these primitives to containers. - The Ansible course part
XXXVII-Ansible-Driftcovers configuration drift; resource sizing is the cluster-level equivalent. - The Observability course part
LXXXVII-Kubernetes-MetricsServercoverskubectl topand metrics that inform resource sizing.
Quiz
Knowledge check · 4 questions
Q1. What is the key difference between CPU and memory as resource types in Kubernetes?
Q2. It is acceptable to omit memory limits on production containers if memory usage is well-understood.
Q3. A team deploys 100 Pods on a 3-node cluster. Each Pod requests 1 CPU. The cluster becomes slow under load. Walk through the diagnosis.
Cluster: 3 nodes, each with 4 CPU capacity. After kubelet reservations, each node has ~3.6 CPU allocatable. Total cluster allocatable: ~10.8 CPU. 100 Pods each requesting 1 CPU = 100 CPU requested. 100 Pods each with a limit of 2 CPU = 200 CPU limit.
Q4. What happens when a container exceeds its CPU limit, and what is the production implication?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Set memory limits on every container. Memory is incompressible; OOMKill at the limit is better than node-wide eviction.
- Set CPU requests based on typical usage. Requests drive scheduling; over-sized requests waste node capacity.
- Measure before sizing. Use
kubectl topand Prometheus to understand actual usage; size requests and limits from data, not guesses. - Distinguish CPU behaviour from memory behaviour. Throttling is gradual; OOMKill is immediate.
- Pair resources with QoS and quotas. Requests determine the QoS class (Part XIII); namespaces enforce ResourceQuotas (Part CVIII).