KubernetesXII · Resource Requests and LimitsResource requests and limits
Throttling, OOMKill, and the resource pressure lifecycle
What you'll learn
- Trace the resource pressure lifecycle from healthy to OOMKilled
- Detect CPU throttling and memory pressure
- Distinguish throttling from OOMKill and node-level eviction
- Apply the production discipline around preventing resource exhaustion
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
The resource pressure lifecycle is the progression from a healthy Pod to a degraded one to a failed one. This lesson walks through that lifecycle, explains the signals at each stage, and shows the production discipline around preventing resource exhaustion.
The resource pressure lifecycle
stateDiagram-v2
[*] --> Healthy
Healthy --> CPUPressure: usage > request
CPUPressure --> Throttled: usage > limit
Throttled --> Latency: tail latency spikes
Latency --> Healthy: load decreases
Throttled --> Healthy: load decreases
Healthy --> MemPressure: usage > request
MemPressure --> MemLimit: usage approaches limit
MemLimit --> OOMKill: usage > limit (cgroup)
OOMKill --> CrashLoop: repeated OOMKill
CrashLoop --> Healthy: memory limit raised / leak fixed
MemPressure --> NodePressure: usage > node allocatable
NodePressure --> Eviction: kubelet evicts Pods
Three failure modes:
- CPU throttling: gradual; latency increases; container runs slower but doesn’t die.
- Memory OOMKill: immediate; container restarts; logs empty.
- Node pressure eviction: the kubelet evicts Pods to reclaim node resources (memory or disk).
CPU throttling in detail
sequenceDiagram
participant App as Application
participant Sched as CFS Scheduler
participant K as Kernel
App->>Sched: asks for CPU
alt usage < quota
Sched-->>App: CPU granted
else usage > quota
Sched->>App: throttled
Note over Sched: until next period
end
When a container’s CPU usage exceeds its limit, the CFS scheduler throttles the container for the remainder of the period. The container’s threads are paused.
The symptoms:
- Tail latency spikes: requests that take 10ms normally now take 100ms because the thread is throttled.
- Throughput drops: under load, the application processes fewer requests per second.
- No crash: the container continues running; logs show no errors.
Detection:
cat /sys/fs/cgroup/.../container-*/cpu.stat
# nr_throttled 4567
# throttled_usec 987654321
throttled_pct = throttled_usec / (nr_periods * period_us)
in decimal. >10% is a problem.
Memory OOMKill in detail
sequenceDiagram
participant App as Application
participant K as Kernel
participant CG as cgroup
App->>CG: allocates memory
CG->>CG: tracks memory.current
alt memory.current < memory.max
Note over App,CG: running
else memory.current > memory.max
CG->>K: OOM signal
K->>App: SIGKILL
App-->>App: process dies
CG->>CG: oom_kill++
end
When a container’s memory usage exceeds its limit, the cgroup triggers the OOM killer. The kernel sends SIGKILL to the container’s PID 1 (and other processes in the cgroup). The process is killed immediately.
The symptoms:
- Empty logs: the process is killed before it can write
logs.
kubectl logs --previousmay be empty. - CrashLoopBackOff: the kubelet restarts the container with exponential backoff.
- Exit code 137: 128 + SIGKILL (9). The container’s exit code matches.
Detection:
cat /sys/fs/cgroup/.../container-*/memory.events
# oom_kill 3
oom_kill count > 0 confirms cgroup OOMKill. Compare with
the container’s restartCount; if they match, every
restart was an OOMKill.
The OOMKill exit code 137
Exit code 137 is not unique to cgroup OOMKill. It can also be:
- kubelet SIGKILL after grace period: the application didn’t exit in time; kubelet SIGKILLs it.
- cgroup OOMKill: the memory usage exceeded the limit.
To distinguish:
- cgroup OOMKill:
state.terminated.reason: OOMKilled(kubelet reports this),memory.events: oom_kill > 0on the node. - kubelet SIGKILL:
state.terminated.reason: Error,memory.events: oom_kill = 0.
For cgroup OOMKill, the kubelet knows because it reads the cgroup counters after the container exits.
Node-level eviction
The kubelet monitors node-level resource pressure and evicts
Pods to reclaim resources. The thresholds are configured via
--eviction-hard and --eviction-soft flags:
--eviction-hard=memory.available<500Mi
--eviction-hard=nodefs.available<10%
--eviction-soft=memory.available<1Gi
--eviction-soft=nodefs.available<15%
--eviction-grace-period=30s
When memory.available drops below 500Mi (or the soft
threshold for 30s), the kubelet starts evicting Pods. The
eviction order:
- BestEffort Pods (no requests/limits) first.
- Burstable Pods next (by usage vs request).
- Guaranteed Pods last.
flowchart TD
Node[Node memory low] --> Q{Pod QoS?}
Q -- BestEffort --> E1[Evict first]
Q -- Burstable --> E2[Evict second]
Q -- Guaranteed --> E3[Evict last]
Within Burstable, the kubelet evicts the Pod whose usage is furthest above its request. A Pod using 800Mi with a 100Mi request is evicted before one using 300Mi with a 100Mi request.
The evicted Pod is rescheduled elsewhere if there’s capacity. If the cluster has no capacity, the Pod stays Pending.
The pressure lifecycle in practice
A typical production scenario:
- Healthy: Pod runs normally, 300Mi memory, 100m CPU.
- Memory growth: application memory creeps up due to a leak; usage is now 450Mi (limit is 512Mi).
- Memory pressure: usage approaches limit; PSI shows
memory.pressure: avg10 > 5%. Slow performance. - OOMKill: usage exceeds 512Mi; cgroup kills the process. Container exits with 137, reason OOMKilled.
- CrashLoopBackOff: kubelet restarts; container hits OOMKill again; backoff increases.
- Memory limit raised: operator raises limit to 1Gi.
- Healthy again: usage stabilises at 600Mi; no more OOMKills.
Detection points:
- Step 2-3: PSI metrics,
memory.currentgrowth rate. - Step 4:
memory.events: oom_kill > 0,state.terminated.reason: OOMKilled. - Step 5:
restartCountincreasing,CrashLoopBackOffevent.
Production patterns
Detect CPU throttling early:
# node-exporter exposes container_cpu_cfs_throttled_seconds_total
rate(container_cpu_cfs_throttled_seconds_total[5m]) > 0.1
A rate of > 0.1 means the container is throttled more than 10% of the time. Alert when sustained.
Detect memory pressure:
# PSI memory pressure
node_pressure_memory_waiting_seconds_total
/ node_pressure_memory_total_seconds_total
> 0.05
A ratio > 5% means significant memory pressure. Alert when sustained.
Detect OOMKill:
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}
This metric counts Pods whose last termination reason was OOMKilled. Alert on any value > 0.
Diagnosing the lifecycle
The diagnostic workflow:
- Pod is slow: check
kubectl top podand node-exporter cgroup metrics for CPU throttling. - Pod is restarting: check
kubectl describe podfor exit reason; checkmemory.eventsfor oom_kill. - Pod is being evicted: check node-exporter’s
kubelet_evictionsmetric and the node’smemory.available. - Node is under pressure: check
kube_node_status_allocatableandkube_node_status_capacity; consider adding capacity.
Cross-course references
- The Linux course part
XXXVII-Linux-Resourcescovers cgroup resource management; the kubelet’s enforcement is the cluster-level equivalent. - The Docker course part
XXXIX-Docker-Resourcescovers container resource limits; Pod-level resource limits are the cluster-level extension. - The Observability course part
LXXXVII-Kubernetes-MetricsServercoverskubectl top; production resource monitoring uses these metrics.
Quiz
Knowledge check · 4 questions
Q1. Which is NOT a symptom of CPU throttling?
Q2. A Pod with a memory limit is safe from node-level memory pressure eviction.
Q3. A team's application has a slow memory leak. The Pod's memory usage grows 10 MiB per hour. After 5 days, the Pod OOMKills. Diagnose the lifecycle and the prevention.
Application has a memory leak (e.g., an unbounded cache). Initial usage: 200Mi. Growth rate: 10Mi/hour. Memory limit: 512Mi. After ~31 hours, usage hits 512Mi and the cgroup OOMKills the process.
Q4. How do you detect CPU throttling from inside a container (or from a metric system)?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Detect CPU throttling with cgroup metrics. Alert on
throttled_pct > 10%sustained for 5 minutes. - Detect memory pressure with PSI. Alert on
memory.pressure: some avg10 > 10%. - Track OOMKill with exit reasons.
OOMKilledinstate.terminated.reasonis the cgroup kill. - Set memory limits on every container. OOMKill at the limit is better than node-wide eviction.
- Monitor memory growth rate. A leak shows up as sustained growth, not just absolute usage.