
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
When a pod enters the OOMKilled state, it means the Linux kernel terminated your process because it exceeded its cgroup memory allocation, not necessarily because the node ran out of RAM. Effective container memory limits and OOMKilled debugging requires distinguishing between application leaks, JVM heap misconfigurations, and insufficient resource specifications. This guide provides the exact diagnostic workflow I use in production environments to identify root causes and prevent recurrence without over-provisioning.
kubectl describe pod for "OOMKilled" exit codes, analyzing kernel logs via dmesg, and comparing actual usage against configured limits. Fix issues by right-sizing memory requests/limits based on percentile metrics, tuning runtime garbage collection, or resolving application-level memory leaks.How do you diagnose container memory limits and OOMKilled debugging in Kubernetes?
Diagnosis must confirm whether the termination was caused by the container's hard limit or node-level pressure. Before adjusting any configuration, gather forensic evidence from three layers: the Kubernetes API, the node kernel, and the application runtime. For teams managing complex workloads, understanding Kubernetes resource limits and requests is prerequisite to accurate diagnosis.
Verify the OOMKilled Status
Run kubectl describe pod <pod-name> and inspect the Last State section. An exit code of 137 typically indicates SIGKILL (128 + 9), but you must confirm the reason field explicitly states OOMKilled. If the reason is Error or ContainerCannotRun with exit 137, the issue may be node-level eviction rather than cgroup enforcement.
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].lastState.terminated}'
# Expected output for cgroup OOM:
# {"exitCode":137,"reason":"OOMKilled","startedAt":"...","finishedAt":"..."} Correlate with Node-Level Kernel Logs
Kubernetes events only tell you that a container died, not why the kernel chose it. SSH into the specific node where the pod ran and inspect dmesg or /var/log/kern.log. Search for "oom-kill" entries matching the timestamp of the pod failure. The kernel log reveals the exact memory usage at termination and the cgroup path, confirming whether the limit was truly breached.
# On the node where the pod was scheduled
sudo dmesg -T | grep -i "oom-kill" | tail -20
# Look for: memory cgroup out of memory: Killed process <PID> (<process-name>)
# Total vm:<X>kB, anon-rss:<Y>kB, file-rss:<Z>kB Check Historical Metrics Before Adjusting Limits
A common mistake is immediately doubling the memory limit after seeing one OOM event. Instead, query Prometheus or your monitoring stack for container_memory_working_set_bytes over the past 7 days. If usage consistently sits at 95% of the limit, the workload is properly sized but needs headroom. If usage spikes briefly then drops, you have a transient allocation pattern or leak. Refer to Prometheus metrics monitoring fundamentals for the correct queries to capture these patterns.
What is the difference between memory requests and limits in container runtimes?
Understanding this distinction prevents both OOM kills and cluster instability. Memory requests guarantee scheduling capacity — the kube-scheduler uses this value to find a node with sufficient free RAM. Memory limits enforce a hard ceiling via cgroups; exceeding this triggers an immediate kill with no graceful shutdown.
| Attribute | Memory Request | Memory Limit |
|---|---|---|
| Purpose | Scheduling guarantee & QoS class assignment | Hard enforcement boundary via cgroup v2 |
| Exceeded Behavior | Node may throttle or evict under pressure | Immediate SIGKILL (OOMKilled) |
| Burstable Usage | Can use up to limit if node has free RAM | Cannot exceed under any circumstance |
| QoS Impact | Equal to limit = Guaranteed; Lower = Burstable | No limit set = BestEffort (lowest priority) |
| Right-Sizing Target | p50–p80 baseline usage | p99 peak usage + 10–20% safety margin |
In practice, setting requests equal to limits creates a Guaranteed QoS pod that is never evicted for memory pressure but wastes capacity if the app rarely uses the full amount. For most web services, set requests to observed p80 usage and limits to p99 plus buffer. Database workloads like those covered in PostgreSQL administration essentials often benefit from Guaranteed QoS to avoid cache eviction during maintenance windows.
How do you right-size container memory limits without causing OOMKilled errors?
Right-sizing is an iterative process grounded in observed metrics, not guesswork. In 2026, with cgroup v2 now standard across major distributions and Kubernetes versions, memory accounting is more precise but also stricter about what counts toward the limit (including kernel memory and socket buffers).
- Establish a Baseline: Deploy with generous limits in staging and record
container_memory_working_set_bytesfor at least one full business cycle. Exclude cache-only memory usingcontainer_memory_rssfor Java/Go apps where page cache inflates working set. - Calculate Percentiles: Set requests to the p80 of working set memory. Set limits to p99 + 15%. This accommodates normal variance while catching true anomalies before they crash the pod.
- Account for Runtime Overhead: JVM-based applications require additional headroom for metaspace, code cache, and direct buffers beyond
-Xmx. A safe formula islimit = Xmx + 512Mi + (Xmx * 0.1). For Node.js, account for V8 heap plus native addon allocations. - Validate Under Load: Run synthetic load tests that mirror production traffic patterns. Monitor for gradual memory growth indicating leaks versus stable plateaus confirming correct sizing.
- Implement Vertical Pod Autoscaler (VPA) in Recommendation Mode: Let VPA observe for 1–2 weeks and suggest values. Never enable auto-update mode in production without thorough testing — sudden limit changes can destabilize stateful workloads.
Tuning Application Runtimes to Respect Limits
The container runtime cannot protect an application from itself. Java applications must configure -XX:MaxRAMPercentage=75.0 instead of fixed -Xmx values to dynamically adapt to container limits. Go applications should set GOMEMLIMIT (available since Go 1.19) to inform the garbage collector of the soft memory target, preventing aggressive retention that leads to OOM. Python and Node.js applications lack native cgroup awareness; use wrapper scripts or entrypoint modifications to read /sys/fs/cgroup/memory.max and configure runtime parameters accordingly.
When should you investigate memory leaks versus adjusting container memory limits?
Increasing limits treats symptoms, not causes. Investigate leaks when you observe monotonically increasing memory usage that never plateaus, even during low-traffic periods. Use runtime profiling tools specific to your stack: jmap and Eclipse MAT for Java, pprof for Go, memray for Python, or Chrome DevTools heap snapshots for Node.js.
Compare heap dumps taken hours apart. Objects that grow unboundedly between snapshots indicate retention bugs. Common culprits include unclosed database connections, unbounded caches without TTLs, event listener accumulation, and global state holding request-scoped data. For microservices architectures, also check for circular dependencies or retry storms that amplify memory consumption across service boundaries — patterns discussed in debugging CrashLoopBackOff in Kubernetes.
Automated Leak Detection in CI/CD
Integrate memory profiling into your pipeline. Run integration tests with memory tracking enabled and fail builds that exceed a defined growth threshold between test phases. Tools like pytest-memray for Python or custom Go test benchmarks with -memprofile catch regressions before deployment. This shifts container memory limits and OOMKilled debugging left, reducing production incidents significantly.
Stabilizing Production Workloads Through Disciplined Memory Management
Container memory limits and OOMKilled debugging is ultimately about aligning runtime behavior with infrastructure constraints through measurement, not intuition. Start every investigation with kernel logs and percentile metrics, distinguish between sizing errors and genuine leaks, and tune application runtimes to respect cgroup boundaries. Automate right-sizing recommendations with VPA in observation mode, validate changes under realistic load, and integrate memory profiling into CI to prevent regressions. If your team needs help establishing sustainable memory management practices or audit-ready observability for compliance frameworks, reach out to discuss your infrastructure.