Resolving Kubernetes Pod OOMKilled Errors: Resource Limit Exceeded on Ubuntu 22.04 LTS
Diagnose and fix Kubernetes Pod OOMKilled errors caused by resource limit overages on Ubuntu 22.04. Optimize your cluster for stability and performance.
Diagnose and fix Kubernetes Pod OOMKilled errors caused by resource limit overages on Ubuntu 22.04. Optimize your cluster for stability and performance.
The OOMKilled status in Kubernetes indicates that a container within your Pod was terminated by the Linux kernel's Out-Of-Memory (OOM) killer. This occurs when a process attempts to consume more memory than its allocated cgroup limit, leading to a forced termination to prevent system instability. On Ubuntu 22.04 LTS, with its adoption of cgroup v2, the enforcement mechanisms are robust, making proper resource configuration paramount for stable containerized applications.
This guide will walk you through diagnosing and resolving OOMKilled errors stemming from resource limit overruns in your Kubernetes environment.
Symptom & Error Signature
When a Pod is OOMKilled, you will typically observe the Pod restarting repeatedly, entering a CrashLoopBackOff state, or simply having a high RESTARTS count. The most definitive signs appear in the Pod's status and events.
You can inspect the Pod's status and events using kubectl:
kubectl get pod <pod-name> -n <namespace>
Example output showing multiple restarts:
NAME READY STATUS RESTARTS AGE
my-app-pod 0/1 OOMKilled 5 10m
Detailed events will explicitly mention OOMKilled:
kubectl describe pod <pod-name> -n <namespace>
Snippet from kubectl describe pod output:
Name: my-app-pod
Namespace: default
Priority: 0
Node: my-k8s-node01/192.168.1.10
Start Time: Mon, 26 Oct 2026 10:00:00 +0000
Labels: app=my-app
Annotations: <none>
Status: Running
IP: 10.42.0.5
IPs:
IP: 10.42.0.5
Containers:
my-app-container:
Container ID: containerd://a1b2c3d4e5f6...
Image: my-registry/my-app:1.0
Image ID: containerd://g7h8i9j0k1l2...
Port: 8080/TCP
Host Port: 0/TCP
Limits:
memory: 512Mi
Requests:
memory: 256Mi
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137 # Indicates process was killed by an external signal (SIGKILL)
Started At: Mon, 26 Oct 2026 10:04:30 +0000
Finished At: Mon, 26 Oct 2026 10:04:45 +0000
Ready: False
Restart Count: 5
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-abcde (ro)
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
kube-api-access-abcde:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 9m default-scheduler Successfully assigned default/my-app-pod to my-k8s-node01
Normal Pulling 8m (x5 over 9m) kubelet Pulling image "my-registry/my-app:1.0"
Normal Pulled 8m (x5 over 9m) kubelet Successfully pulled image "my-registry/my-app:1.0" in 1.234s (containerID=...)
Normal Created 8m (x5 over 9m) kubelet Created container my-app-container
Normal Started 8m (x5 over 9m) kubelet Started container my-app-container
Warning OOMKilled 7m (x5 over 8m) kubelet Container my-app-container was OOMKilled
Warning BackOff 5m (x6 over 8m) kubelet Back-off restarting failed container my-app-container in pod my-app-pod_default(a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890)
The Last State: Terminated with Reason: OOMKilled and Exit Code: 137 is the definitive signature. Exit Code 137 (128 + 9) specifically means the process was terminated by signal 9 (SIGKILL), which is what the OOM killer sends.
Root Cause Analysis
The underlying reasons for an OOMKilled event are generally related to the application's memory consumption exceeding its allocated limits, often within the context of Kubernetes' resource management and the Linux kernel's cgroup enforcement.
- Insufficient Kubernetes Memory Limits: This is the most prevalent cause. The
resources.limits.memoryconfigured in your Pod's manifest is simply too low for the application's actual memory footprint under typical or peak load. Kubernetes uses cgroups to enforce these limits, and when a container exceeds its allocated memory, the kernel's OOM killer steps in. - Application Memory Leaks: The application itself might have a memory leak, where it continuously consumes more memory over time without releasing it, eventually hitting any configured limit. This will manifest as a gradual increase in memory usage before the
OOMKilledevent. - Incorrect Memory Requests vs. Limits: While
limitsare the hard ceiling,requestsare used for scheduling and guaranteeing resources. Ifrequests.memoryis set unrealistically high compared tolimits, or vice-versa, it can lead to inefficient scheduling or premature OOMKills. However, anOOMKilledevent specifically points to thelimitsbeing hit. - Java Virtual Machine (JVM) Heap Size Mismatch: For Java applications, the JVM's default heap size or explicitly configured
-Xmx(maximum heap size) might attempt to allocate more memory than the container'smemory.limit. Modern JVMs are more container-aware, but explicit configuration remains crucial. - Sidecar Container Resource Contention: If a Pod has multiple containers (e.g., application + logging agent sidecar), the combined memory usage or unexpected spikes in a sidecar's usage could lead to one of them hitting its individual limit.
- Sudden Spikes in Workload: An unexpected surge in traffic or processing load can cause a temporary but significant spike in memory consumption, pushing the application beyond its configured limits.
- Host Node OOM Conditions (Less Common for Specific Pod OOMKilled): While the kernel's OOM killer prioritizes processes exceeding their cgroup limits, a severely memory-constrained host node can, in extreme cases, lead to more aggressive OOM killing. However,
OOMKilledevents typically mean the cgroup limit was the primary trigger.
Step-by-Step Resolution
Addressing OOMKilled errors requires a systematic approach involving observation, analysis, and precise configuration adjustments.
1. Identify the OOMKilled Pod and Container
First, confirm which Pods and specific containers within those Pods are experiencing the OOMKilled status.
# List all pods with OOMKilled status across all namespaces
kubectl get pods --all-namespaces | grep OOMKilled
# Get detailed information about the problematic pod
kubectl describe pod <pod-name> -n <namespace>
# Retrieve logs from the *previous* instance of the container (after it was OOMKilled)
kubectl logs <pod-name> -n <namespace> -c <container-name> --previous
The kubectl logs --previous command is invaluable as it allows you to examine what the application was doing just before it was terminated. Look for any error messages, large data processing events, or signs of increasing memory consumption.
2. Analyze Application Memory Usage
Understanding the actual memory footprint of your application is crucial.
Using
kubectl top(Requires Metrics Server): If you have the Kubernetes Metrics Server installed (common in most clusters), you can observe live memory usage.# Get overall pod memory usage kubectl top pod <pod-name> -n <namespace> # Get memory usage per container within the pod kubectl top pod <pod-name> -n <namespace> --containerskubectl topshows current usage, which might not reflect peak usage that caused the OOMKill.Monitoring Tools (Prometheus/Grafana): Leverage your cluster's monitoring stack (e.g., Prometheus and Grafana) to review historical memory usage trends for the affected Pod/container. Look for patterns:
- Does memory usage slowly climb over time (potential leak)?
- Are there sudden spikes correlating with specific events or times of day (peak load)?
- What was the peak memory usage before the OOMKilled event? This gives you a baseline for setting new limits.
Local Testing/Profiling: If possible, run the application locally or in a staging environment under similar load conditions. Use profiling tools specific to your application's language (e.g.,
heapdumpfor Node.js,go tool pproffor Go, Java VisualVM for Java,memory_profilerfor Python) to identify memory-intensive operations or potential leaks.
3. Adjust Kubernetes Resource Limits
Once you have a better understanding of your application's memory requirements, the most common fix is to increase the memory.limits in your Pod, Deployment, or StatefulSet manifest.
When increasing memory limits, aim for a value that is safely above the peak observed memory usage, ideally with a buffer (e.g., 10-20%). Be cautious not to set limits excessively high, as this can lead to inefficient scheduling, increased cloud costs, or mask genuine memory leaks.
Edit your Deployment/StatefulSet/Pod manifest: Locate the
resourcessection for the problematic container.apiVersion: apps/v1 kind: Deployment metadata: name: my-app-deployment namespace: default spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app-container image: my-registry/my-app:1.0 ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" # <-- INCREASE THIS VALUE (e.g., to "1Gi" or "1.5Gi") cpu: "1"Apply the changes:
kubectl apply -f your-deployment.yamlThis will trigger a rolling update, replacing the old Pods with new ones configured with the updated memory limits.
While increasing limits directly resolves the
OOMKillederror, it's crucial to continuously monitor the application's memory usage afterward. If the memory usage continues to climb and eventually hits the new, higher limit, it's a strong indicator of a memory leak in the application itself.
4. Investigate Application Memory Leaks
If simply increasing memory limits doesn't solve the issue, or if you observe memory usage steadily climbing towards the new limit, you likely have a memory leak within your application. This requires debugging the application code.
- Code Review: Review recent code changes, especially those related to data structures, caching, or long-running processes.
- Profiling Tools: Use language-specific profiling tools.
- Java: VisualVM, JConsole, JProfiler, or
jmap/jstaton the host. - Node.js: Node.js built-in profiler,
heapdumpmodule. - Python:
memory_profiler,objgraph. - Go:
go tool pprof(for heap profiles).
- Java: VisualVM, JConsole, JProfiler, or
- Enable Debugging: For applications that support it, enable verbose logging or debugging modes related to memory management.
- Run on a Test Environment: Deploy the application to a dedicated test environment with high visibility into its runtime metrics and stress-test it to reproduce the leak.
5. Optimizing JVM Memory (If Applicable)
For Java applications, fine-tuning JVM options is critical. The JVM's memory management interacts with cgroup limits.
Set Explicit Heap Size: Ensure the JVM's maximum heap size (
-Xmx) is set to a value less than the container'smemory.limit. A good practice is to set-Xmxto approximately 70-80% of the container's memory limit to account for non-heap memory (stack, native libraries, garbage collection overhead, etc.).For example, if your container's memory limit is
1Gi:containers: - name: my-java-app image: openjdk:17-jre-slim env: - name: JAVA_OPTS value: "-Xmx768m -Xms256m -XX:MaxRAMPercentage=75.0" # -Xmx768m is 75% of 1Gi resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" # Container limit cpu: "1"Modern JVMs (Java 8u191+, Java 10+) are more container-aware and can derive heap sizes from cgroup limits when
MaxRAMPercentageis used. However, explicitly setting-Xmxor ensuringMaxRAMPercentageis correctly configured is still the most reliable approach.
6. Verify Node Resource Availability
While OOMKilled primarily points to a container hitting its configured limit, overall node resource scarcity can exacerbate the problem or lead to more aggressive OOM killing.
Check Node Memory Usage:
# Check overall node resource usage kubectl top nodes # Get detailed information about the node the pod is running on kubectl describe node <node-name>You can also SSH into the specific node where the OOMKilled Pod was running and check its system-wide memory:
ssh <node-ip> free -h htop # Look for overall memory usage and swap activityScale Cluster: If nodes are consistently running low on memory, consider adding more nodes to your Kubernetes cluster or upgrading existing nodes with more RAM.
7. Implement Horizontal Pod Autoscaler (HPA) for Memory (Advanced)
If your application's memory usage fluctuates significantly with traffic or workload, configuring an HPA can proactively scale your Pods before they hit memory limits and get OOMKilled. This requires the Kubernetes Metrics Server to be deployed.
The HPA scales based on the average utilization of the requested resource. Therefore, it's critical that your
resources.requests.memoryare set realistically (representing baseline usage) to ensure the HPA scales up before the application hits itslimits.
Ensure Metrics Server is running:
kubectl get apiservice v1beta1.metrics.k8s.io -o yamlIf it returns
Accepted: True, metrics server is likely working.Define HPA: Create a YAML file (e.g.,
hpa-my-app.yaml):apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: my-app-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: my-app-deployment # Target your Deployment or StatefulSet minReplicas: 1 maxReplicas: 5 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 70 # Scale up when average memory utilization reaches 70% of requested memoryIn this example, if
resources.requests.memoryformy-app-deploymentis512Mi, the HPA will trigger scaling when the average memory utilization across pods reaches70% of 512Mi(approx. 358Mi).Apply HPA:
kubectl apply -f hpa-my-app.yaml
8. Node OOM Killer Tweaks (Highly Advanced / Last Resort)
For highly specific system-level components where an OOMKilled event on the host node itself would be catastrophic, and cgroup limits aren't sufficient, you might consider adjusting oom_score_adj for critical host processes.
This is generally not recommended for application containers running in Kubernetes. Kubernetes' cgroup management already provides isolation. Manually adjusting
oom_score_adjat the host level can interfere with container runtime behavior and potentially destabilize the node or other workloads. Use with extreme caution and only after exhausting all other options.
For a Kubernetes node on Ubuntu 22.04 LTS (which uses cgroup v2), the container runtime (e.g., containerd) manages the memory.max (equivalent to memory.limit_in_bytes in cgroup v1) for containers within their respective cgroups. The kernel's OOM killer respects these cgroup boundaries.
If you absolutely must prevent a host process from being OOMKilled, you could theoretically modify its oom_score_adj. For example, to make kubelet less likely to be OOMKilled (it's usually configured this way by default in production clusters):
# This is an example for a host process, NOT a container process.
# Generally, don't do this for Kubernetes managed applications.
echo -1000 | sudo tee /proc/<PID_of_process>/oom_score_adj
Again, this is a last resort and typically outside the scope of fixing application Pod OOMKilled errors, which are best addressed by adjusting Pod resource limits or application-level memory management.
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.