KubernetesXII · Resource Requests and LimitsResource requests and limits
Troubleshooting resource pressure — a production triage framework
What you'll learn
- Apply a triage framework for resource pressure incidents
- Distinguish CPU throttling from memory OOMKill from node eviction
- Gather evidence at the right layer (cgroup, pod, node)
- Apply fixes that address the root cause, not the symptom
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
When a Pod is slow or restarting, the cause is often a resource issue. The triage framework here identifies whether the issue is CPU throttling, memory OOMKill, or node-level eviction; gathers evidence at the right layer; and applies the fix without making things worse.
The three failure modes
flowchart TD
Symptom[Symptom: Pod slow or restarting] --> Mode{Which mode?}
Mode -- CPU throttling --> T1[Latency spike, no restart]
Mode -- Memory OOMKill --> T2[Restart, exit code 137]
Mode -- Node eviction --> T3[Pod terminated, Rescheduled elsewhere]
T1 --> Fix1[Increase CPU limit / reduce usage]
T2 --> Fix2[Increase memory limit / fix leak]
T3 --> Fix3[Add capacity / right-size]
The mode determines the evidence to gather and the fix to apply.
Mode 1: CPU throttling
Symptoms:
- Latency spikes (p99 increases; tail latency worsens).
- Throughput drops (requests per second decreases).
- No container restarts.
kubectl top podshows CPU usage near or above the limit.
Evidence:
- Container is in
state.running(not Waiting or Terminated). restartCountis stable (no restarts).kubectl describe podevents are normal (no OOMKill, no Failed).- On the node: cgroup
cpu.statshows highthrottled_usecandnr_throttled.
Diagnosis:
# On the node hosting the Pod
POD_UID=$(kubectl get pod web-7c8 -o jsonpath='{.metadata.uid}')
CGROUP="/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/pod${POD_UID}"
# Read CPU throttling
cat $CGROUP/container-*/cpu.stat | grep throttled
# nr_throttled 4567
# throttled_usec 987654321
throttled_pct = throttled_usec / (nr_periods * 100000) in
microseconds. If >10%, the container is throttled.
Fix:
- Increase CPU limit (if the workload genuinely needs more CPU).
- Reduce CPU usage (profile the application; optimise hot paths).
- Move the workload to a less contended node.
- Scale horizontally (more replicas with smaller CPU each).
Mode 2: Memory OOMKill
Symptoms:
- Container restarts repeatedly with exit code 137.
kubectl logs --previousmay be empty (process killed before logging).- Pod is in CrashLoopBackOff.
kubectl describe podshowsstate.terminated.reason: OOMKilled.
Evidence:
state.terminated.exitCode: 137.state.terminated.reason: OOMKilled(notError).- On the node: cgroup
memory.eventsshowsoom_kill > 0. kubectl top podshows memory usage near or above the limit before the kill.
Diagnosis:
# Verify OOMKill from cgroup
cat $CGROUP/container-*/memory.events | grep oom
# max 5
# oom 0
# oom_kill 3
# Memory usage at time of kill (if not reset)
cat $CGROUP/container-*/memory.current
# 536870912 # 512Mi — hit the limit
Fix:
- Increase memory limit (if the workload legitimately needs more memory).
- Fix a memory leak (profile with heap dump; review recent changes).
- Reduce memory usage (cache eviction, smaller buffers, streaming).
- Set memory request close to limit so the scheduler reserves enough; helps with placement.
Mode 3: Node-level eviction
Symptoms:
- Pod is evicted (not OOMKilled);
kubectl describe podshowsreason: Evicted. - The Pod is rescheduled on another node if there’s capacity.
- Other Pods on the same node may also be evicted.
kubectl describe nodeshows lowmemory.availableornodefs.available.
Evidence:
state.terminated.reason: Evicted(different from OOMKilled).- The kubelet’s eviction manager logged the reason
(
memory.available,nodefs.available, etc.). - Multiple Pods on the same node are evicted around the same time.
Diagnosis:
# Check node-level pressure
kubectl describe node node-3 | grep -A 5 "Conditions"
# Conditions:
# ... MemoryPressure True ...
# ... DiskPressure True ...
# Check evicted Pods
kubectl get pods -A --field-selector=status.reason=Evicted
Fix:
- Add capacity (scale up nodes; Cluster Autoscaler).
- Reduce per-Pod requests (right-size via VPA).
- Move workloads off contended nodes (Pod topology spread, anti-affinity).
- Tune eviction thresholds to evict earlier (smaller buffer before full pressure).
Triage decision tree
flowchart TD
Start[Pod slow or restarting] --> Q1{Container restarting?}
Q1 -- no --> Q2{Latency spike?}
Q2 -- yes --> CPU[CPU throttling]
Q2 -- no --> Other[Look elsewhere: network, disk, app bug]
Q1 -- yes --> Q3{Exit code?}
Q3 -- 137 --> Q4{state.terminated.reason?}
Q4 -- OOMKilled --> OOM[Memory OOMKill]
Q4 -- Error --> Q5{SIGKILL from kubelet?}
Q5 -- yes --> Grace[Grace period exceeded]
Q5 -- no --> Cgroup{cgroup oom_kill > 0?}
Cgroup -- yes --> OOM
Cgroup -- no --> OtherKill[Other cause: signal, runtime error]
Q3 -- 0 --> Completed[App exited cleanly; check restartPolicy]
Q3 -- 1-127 --> AppErr[Application error; read logs]
Q3 -- 143 --> SIGTERM[Graceful termination; check Pod lifecycle]
The decision tree:
- Container restarting?: if no, it’s CPU throttling or something else.
- Latency spike?: if yes, CPU throttling.
- Exit code 137?: if yes, OOMKill (cgroup) or kubelet SIGKILL (grace period).
state.terminated.reason:OOMKilled= cgroup;Error= kubelet SIGKILL.memory.events.oom_kill: if > 0, confirmed cgroup OOMKill.
Production patterns
CPU throttling — increase limit safely:
# 1. Confirm throttling
kubectl exec web-7c8 -- cat /sys/fs/cgroup/cpu.stat # may not work from inside
# Or check from the node
# 2. Profile the application (pprof, perf, etc.)
# 3. If legitimate, raise the limit
kubectl set resources deployment web -c web --limits=cpu=1000m
Memory OOMKill — fix or raise:
# 1. Confirm OOMKill from cgroup
kubectl describe pod web-7c8 | grep -A 3 OOMKilled
# 2. Profile memory usage (heap dump, /proc/PID/status)
# 3. If legitimate, raise the limit
kubectl set resources deployment web -c web --limits=memory=1Gi
# 4. Set memory request close to limit (helps with scheduling)
kubectl set resources deployment web -c web --requests=memory=768Mi
Node eviction — add capacity:
# 1. Confirm node pressure
kubectl describe node node-3 | grep -A 5 "Conditions"
# 2. Add capacity
# Via Cluster Autoscaler: it does this automatically
# Manually: terraform apply for the node pool
# 3. Or reduce requests
kubectl set resources deployment web -c web --requests=cpu=100m,memory=128Mi
What NOT to do
Common anti-patterns during incident response:
- Don’t raise all limits blindly: making the problem worse by over-allocating.
- Don’t force-delete Pods in CrashLoopBackOff: the Deployment will recreate them; fix the root cause.
- Don’t disable kubelet eviction thresholds: this hides the problem, doesn’t fix it.
- Don’t add nodes without understanding the cause: if the workload is leaking memory, more nodes just delay the crash.
- Don’t ignore memory.events on the node: it’s the ground truth for OOMKill.
Capturing evidence before the incident clears
Production discipline: capture the state before debugging resets it:
# Capture the Pod's full spec
kubectl get pod web-7c8 -o yaml > /tmp/web-7c8.yaml
# Capture events
kubectl get events --field-selector involvedObject.name=web-7c8 \
> /tmp/web-7c8-events.txt
# Capture top metrics (snapshot)
kubectl top pod web-7c8 --containers > /tmp/web-7c8-top.txt
# If you can SSH to the node, capture cgroup state
ssh node-3 "cat /sys/fs/cgroup/.../web-7c8/cpu.stat" > /tmp/cpu.stat
ssh node-3 "cat /sys/fs/cgroup/.../web-7c8/memory.events" > /tmp/memory.events
Save these to the incident channel. The postmortem depends on them.
Cross-course references
- The Linux course part
XXXVII-Linux-Resourcescovers cgroup resource management; the kubelet’s enforcement is the cluster-level equivalent. - The Ansible course part
XLV-Ansible-Debuggingcovers systematic debugging; the triage framework is the same. - The Observability course part
LXXXVII-Kubernetes-MetricsServercoverskubectl top; production resource monitoring uses these metrics.
Quiz
Knowledge check · 4 questions
Q1. A Pod is restarting with exit code 137. What is the FIRST evidence to gather?
Q2. Raising the CPU or memory limit is always the right fix for resource pressure.
Q3. A team's memory limit fix doesn't stick — the Pod OOMKills again after a few hours. They raised the limit twice. Diagnose the root cause.
Initial limit: 512Mi. OOMKill. Raised to 1Gi. OOMKill after 6 hours. Raised to 2Gi. OOMKill after 12 hours. The pattern: each time the limit is raised, the time to OOMKill roughly doubles, suggesting linear growth (a leak).
Q4. What four pieces of evidence should you gather when triaging a resource pressure incident?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Start with
kubectl describe pod. State and events tell you the layer. - Verify with cgroup files. The kubelet’s view is derived from the cgroup; the cgroup is ground truth.
- Capture state before debugging. Pod YAML, events, top metrics, cgroup files. The postmortem depends on them.
- Profile before raising limits. A leak or CPU bottleneck is fixed at the application, not at the limit.
- Monitor resource growth rate. Linear growth is a leak; alert before OOMKill happens.