Troubleshooting Kubernetes Pod OOMKilled: Resource Limits Exceeded on Ubuntu 20.04 LTS
Resolve Kubernetes Pod OOMKilled errors on Ubuntu 20.04 LTS. Learn to diagnose and adjust resource limits (CPU/memory) to prevent applications from exceeding allocated resources and crashing.
Resolve Kubernetes Pod OOMKilled errors on Ubuntu 20.04 LTS. Learn to diagnose and adjust resource limits (CPU/memory) to prevent applications from exceeding allocated resources and crashing.
Introduction
In the dynamic world of containerized applications, an "OOMKilled" error in Kubernetes signifies a critical resource contention issue: your application pod has attempted to consume more memory than it was allocated, leading to its abrupt termination by the kernel's Out Of Memory (OOM) killer. This guide provides a comprehensive, highly technical approach to diagnose and resolve OOMKilled errors for Kubernetes Pods running on Ubuntu 20.04 LTS worker nodes, a common scenario in web hosting and DevOps environments. Understanding and correctly configuring resource limits is paramount for stable, performant, and reliable application deployments.
Symptom & Error Signature
When a Kubernetes Pod is OOMKilled, its container(s) are forcefully terminated by the operating system kernel due to excessive memory consumption. Users typically observe intermittent application unavailability, service restarts, or slow responses. On the terminal, you'll see specific status messages and events indicating the OOMKilled state.
Here's how to identify the error signature:
Pod Status and Last State:
kubectl get pods -n <your-namespace>You'll often see pods repeatedly crashing and restarting:
NAME READY STATUS RESTARTS AGE my-app-pod-abcdefg-hijkl 0/1 OOMKilled 7 3h another-service-mnopq-rstuv 1/1 Running 0 5hFor detailed information on a specific pod:
kubectl describe pod my-app-pod-abcdefg-hijkl -n <your-namespace>Look for the
Last StateandReasonfields within theContainerssection:... Containers: my-app-container: Container ID: containerd://a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 Image: my-registry/my-app:latest Image ID: my-registry/my-app@sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Port: 80/TCP Host Port: 0/TCP State: Running Started: Thu, 01 Jan 1970 00:00:00 +0000 Last State: Terminated Reason: OOMKilled Exit Code: 137 Started: Thu, 01 Jan 1970 00:00:00 +0000 Finished: Thu, 01 Jan 1970 00:00:00 +0000 ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning OOMKilled 2m (x7 over 3h) kubelet Container my-app-container was OOM-killed. Normal Pulled 2m (x7 over 3h) kubelet Container image "my-registry/my-app:latest" already present on machine Normal Created 2m (x7 over 3h) kubelet Created container my-app-container Normal Started 2m (x7 over 3h) kubelet Started container my-app-containerThe
Reason: OOMKilledandExit Code: 137(which translates to128 + 9, where 9 isSIGKILL) are definitive indicators.Kubernetes Events:
You can filter events specifically for OOMKilled occurrences:
kubectl get events --field-selector reason=OOMKilled -n <your-namespace>This will show a history of OOMKilled events across your namespace.
Host OS OOM Logs (Ubuntu 20.04 LTS Node):
If you have access to the underlying Kubernetes worker node, you can check the kernel logs for direct OOM killer reports.
ssh user@<node-ip> sudo dmesg -T | grep -i "oom-killer"Example
dmesgoutput:[Thu Nov 5 10:30:45 2026] my-app-container invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=999 [Thu Nov 5 10:30:45 2026] CPU: 3 PID: 12345 Comm: my-app-container Not tainted 5.4.0-nn-generic #nn-Ubuntu ... [Thu Nov 5 10:30:45 2026] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=my-app-container-id,mems_allowed=0,global_oom,task_memcg=/kubepods/burstable/pod-uuid/container-id,task=my-app-container,pid=12345,uid=1000 [Thu Nov 5 10:30:45 2026] Memory cgroup out of memory: Killed process 12345 (my-app-container) total-vm:1024MB, anon-rss:512MB, file-rss:0MB, shmem-rss:0MBThis confirms the kernel's action on the specific process within the container.
Root Cause Analysis
An OOMKilled event signals that a process within a container tried to allocate more memory than its assigned memory.limit in Kubernetes. This can stem from several underlying issues:
Misconfigured Kubernetes Resource Limits:
- Insufficient
memory.limits: The most common cause. Theresources.limits.memorydefined in the Pod's YAML manifest is simply too low for the application's actual operational needs under typical or peak load. - Aggressive
memory.requests: Whilerequestsprimarily influence scheduling, if they are set very low, Kubernetes might schedule the pod onto a node that is already heavily utilized, increasing the chance of contention even if thelimitsare seemingly adequate. - No Limits Defined: If no
memory.limitsare specified, the pod can theoretically consume all available memory on the node, potentially leading to the node's OOM killer activating and indiscriminately killing processes, including other pods or even critical system services.
- Insufficient
Application Memory Leaks or Inefficiency:
- Memory Leaks: The application itself may have a bug causing it to continuously allocate memory without releasing it, leading to a gradual increase in consumption until the limit is hit.
- Inefficient Code/Configuration: The application might be designed inefficiently, consuming more memory than necessary for its workload (e.g., loading entire datasets into memory, poor caching, excessive object creation).
- Misconfigured Application Runtimes: For environments like Java JVMs, Node.js, or Python, the application's internal memory management (e.g., JVM heap size with
-Xmx) might be set higher than the container'smemory.limit, leading to the application attempting to exceed the cgroup limit.
Insufficient Node Resources:
- Node Over-subscription: Even with correctly configured pod limits, if the Kubernetes node itself is over-subscribed (i.e., the sum of all pod requests/limits exceeds the node's physical capacity), resource contention can occur.
- Kernel Overhead: The Linux kernel and Kubernetes components (
kubelet,containerd/docker) themselves require memory, reducing the total allocatable memory for pods.
Workload Spikes and Unexpected Load:
- An sudden surge in traffic or processing load can push an application's memory usage beyond its typical baseline, exceeding configured limits.
- Batch jobs or data processing tasks might have variable memory requirements, occasionally spiking and triggering the OOM killer.
Step-by-Step Resolution
Resolving OOMKilled errors requires a methodical approach, starting with diagnosis and moving to configuration adjustments and potential application optimization.
#### 1. Identify the OOMKilled Pod and its Resource Usage
Begin by gathering comprehensive data about the struggling pod.
Get detailed pod information:
kubectl describe pod <pod-name> -n <your-namespace>Pay close attention to the
Containerssection forLimitsandRequestsvalues, and theEventssection for OOMKilled reports.Check current resource limits and requests: It's crucial to see what's currently configured.
kubectl get deployment <deployment-name> -n <your-namespace> -o yaml | grep -A 5 -B 5 "resources:"(Replace
deployment-namewith the actual deployment name managing the pod).Monitor actual pod resource usage (if Metrics Server is installed): If the Kubernetes Metrics Server is deployed in your cluster, you can get real-time (or near real-time) usage.
kubectl top pod <pod-name> -n <your-namespace> --containersThis can show you the memory usage before the pod was killed, or its usage pattern if it restarts successfully for a brief period. Also check node-level usage:
kubectl top nodeThis helps identify if the entire node is under pressure.
Examine node kernel logs for OOM events: SSH into the Kubernetes worker node where the affected pod was running and check
dmesg.ssh user@<node-ip> sudo dmesg -T | grep -i "oom-killer|memory cgroup" | tail -n 20This confirms the kernel's perspective and can reveal the exact process ID and cgroup context of the killed container.
#### 2. Analyze Application Memory Patterns
Understanding why your application is using memory is key.
Review Application Metrics: Utilize your monitoring stack (e.g., Prometheus/Grafana, Datadog, New Relic) to analyze historical memory consumption trends for the application. Look for:
- Gradual memory increase over time (potential leak).
- Sudden spikes coinciding with specific events or peak load.
- High baseline memory usage.
- Memory usage during different times of day or specific operations.
Application Logs: Check application-level logs for any internal memory warnings, garbage collection issues (for Java/Go), or errors that might indicate inefficient memory handling.
Profiling (if applicable): If you suspect a memory leak or inefficiency, consider running memory profiling tools on a development or staging environment. For Java, tools like VisualVM or YourKit can be invaluable. For Node.js, the built-in V8 profiler or
heapdumplibrary.
#### 3. Adjust Kubernetes Resource Limits and Requests
Based on your analysis, the primary action is often to tune the resources section of your Pod's manifest (usually defined in a Deployment, StatefulSet, or DaemonSet).
Edit the Deployment/StatefulSet/DaemonSet: Use
kubectl editor directly modify your YAML files in your Git repository.kubectl edit deployment <deployment-name> -n <your-namespace>Incrementally Increase
memory.limits: Start by increasing thememory.limitsfor the affected container. A good starting point is to increase by 25-50% based on observed peak usage, then monitor.apiVersion: apps/v1 kind: Deployment metadata: name: my-app-deployment spec: replicas: 3 template: spec: containers: - name: my-app-container image: my-registry/my-app:latest resources: limits: memory: "768Mi" # Increased from e.g., 512Mi cpu: "1000m" # (1 full CPU core) requests: memory: "512Mi" # Adjust requests accordingly cpu: "500m" # (0.5 CPU core)Always increase
memory.limitsincrementally. Over-provisioning too much memory can lead to inefficient resource utilization across your cluster and potentially starve other pods if the node runs out of physical RAM.Adjust
memory.requests:memory.requestsdefines the minimum memory guaranteed to the container and is used for scheduling. Setting it too low can lead to pods being scheduled on nodes with insufficient actual free memory, even iflimitsare higher.- If you want to ensure the pod always gets a certain amount of memory, make
requestsequal tolimits. This gives the pod a "Guaranteed" Quality of Service (QoS) class, which is less likely to be OOMKilled under node-level memory pressure. However, it can reduce cluster utilization flexibility. - A common strategy for performance-critical applications is to set
requestsslightly belowlimits(e.g., 75-80%) to allow for some burst capacity while ensuring a decent baseline.
Consider
cpu.limitsandcpu.requests: While OOMKilled directly relates to memory, CPU throttling can sometimes indirectly impact memory usage by slowing down garbage collection or processing, leading to memory accumulation. Ensure CPU limits are also appropriately set.# Example for CPU limits and requests resources: limits: cpu: "1000m" # 1 CPU core memory: "768Mi" requests: cpu: "500m" # 0.5 CPU core memory: "512Mi"Apply Changes: Save the changes, and Kubernetes will automatically roll out new pods with the updated resource configurations.
#### 4. Optimize Application Memory Usage
If increasing resource limits doesn't fully resolve the issue, or if you identify memory leaks/inefficiencies, application-level optimization is necessary.
Tune Runtime Memory Settings: For applications running on specific runtimes, ensure their internal memory settings are aligned with the container's
memory.limit.Java (JVM): Adjust
-Xmx(maximum heap size) and-Xms(initial heap size). For example, ifmemory.limitis768Mi, you might set-Xmxto600mto leave room for non-heap memory usage (e.g., native memory, thread stacks, garbage collection overhead).# Example for Java application in Deployment YAML env: - name: JAVA_OPTS value: "-Xmx600m -Xms256m"Node.js: For older Node.js versions,
max-old-space-sizemight be relevant for heap size. Newer versions often handle this better by default in containers.# Example for Node.js application command: ["node"] args: ["--max-old-space-size=600", "server.js"]Python: Ensure libraries are used efficiently, and large data structures are managed carefully (e.g., generators for large iterations).
Code Review and Dependency Updates:
- Examine application code for obvious memory-intensive operations.
- Update third-party libraries and frameworks, as newer versions often come with performance and memory optimizations.
Caching Strategies: Implement or optimize caching to reduce repeated data fetching and processing, thereby lowering memory footprint.
#### 5. Monitor and Iterate
After making changes, continuous monitoring is crucial.
Observe Pod Behavior:
kubectl get pods -n <your-namespace> -w # Watch for status changes kubectl describe pod <new-pod-name> -n <your-namespace> kubectl logs <new-pod-name> -n <your-namespace>Review Metrics: Check your monitoring dashboards to see if the application's memory usage now stays consistently within the new limits and if the
OOMKilledevents have ceased.Iterate: If
OOMKilledpersists, repeat the diagnostic steps (1and2) and further adjust limits (3) or optimize the application (4). This process is often iterative.
#### 6. Consider Node Resource Capacity (Advanced)
If you're consistently running into OOMKilled issues across multiple pods, even after individual pod tuning, the underlying Kubernetes worker nodes might be the bottleneck.
Node-Level Resource Utilization:
kubectl top nodeIf
kubectl top nodeconsistently shows high memory utilization, or if your monitoring stack indicates nodes are frequently at or near their capacity, it's time to consider scaling up or out.Scale Node Resources:
- Vertical Scaling: Upgrade your existing worker node instance types to ones with more CPU and RAM.
- Horizontal Scaling: Add more worker nodes to your cluster to distribute the workload.
Check Kubelet Configuration: Ensure
kubeleton your Ubuntu 20.04 nodes is correctly configured with the cgroup driver matching your container runtime (usuallysystemdforcontainerd). Inconsistencies can lead to resource management issues.# On the Kubernetes worker node cat /etc/default/kubelet # or /var/lib/kubelet/config.yamlLook for
--cgroup-driver=systemd(orcgroupDriver: systemdin config file).Modifying
kubeletconfiguration directly can impact cluster stability. Always backup configuration files and perform changes in a controlled environment first.sysctlfor Memory Overcommit: While generally not recommended to change without deep understanding, thevm.overcommit_memorykernel parameter on the Ubuntu host can influence how the kernel handles memory allocation.0(default): Heuristic overcommit, allows some overcommit.1: Always overcommit, allows memory requests even if not physically available.2: Never overcommit, requires strict memory availability. For Kubernetes, the default0is usually fine, as cgroups manage pod limits. Changing this to2can lead to more application allocation failures rather than OOMKills, but it can also make scheduling much harder.
# On the Ubuntu worker node sudo sysctl vm.overcommit_memory
By diligently following these steps, you can effectively diagnose, resolve, and prevent Kubernetes Pod OOMKilled errors, ensuring the stability and performance of your containerized applications on Ubuntu 20.04 LTS.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.