Writing

Kubernetes OOMKilled: diagnose exit code 137

Published 27 September 2026

Kubernetes OOMKilled means the Linux kernel terminated a process under memory pressure. Exit code 137 means the process received SIGKILL, but that number alone does not prove an out-of-memory event. Confirm Reason: OOMKilled, compare the container's peak working set with its limit, and check node pressure before raising anything.

Read the evidence in the right order

Start with the previous container state. A restarting container may be running normally by the time an engineer opens the incident, while its last state still records the reason, exit code and timestamps.

kubectl describe pod api-7d9f6b8f9c-x2k4p

kubectl get pod api-7d9f6b8f9c-x2k4p \
  -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'

kubectl get events --field-selector involvedObject.name=api-7d9f6b8f9c-x2k4p \
  --sort-by=.lastTimestamp

Exit status conventions commonly encode a fatal signal as 128 plus the signal number, so SIGKILL (9) appears as 137. Kubernetes documentation shows the pair Reason: OOMKilled and Exit Code: 137 for a container that exceeded its memory limit. A deployment, operator or person can also send SIGKILL, so preserve the reason and events instead of treating 137 as the diagnosis.

Container limit, node pressure or eviction?

EvidenceLikely mechanismCheck next
OOMKilled in the container's last stateThe kernel killed a process under memory pressure, often at the container cgroup limit.The container limit, working-set peak, kernel or runtime log and memory-backed volumes
Pod phase Failed with reason EvictedThe kubelet evicted the Pod because the node crossed a pressure threshold.Node conditions, eviction message, competing Pods and system reservations
Exit code 137 without OOMKilledThe process received SIGKILL, but the sender is not established.Rollout history, termination grace period, operator actions and node logs
Node became NotReady or several Pods died togetherNode-wide memory exhaustion or another node failure may have affected several cgroups.Kubelet, kernel and provider events for the same minute

A container can exceed its memory request without exceeding its limit. The scheduler uses requests to place Pods. The kernel enforces memory limits reactively through cgroups, and may terminate a process when memory pressure exposes the overage. That makes request and limit two different controls, not a soft and hard version of the same number.

Find the peak that the dashboard missed

kubectl top is useful for current usage, but a process can rise past its limit and disappear between samples. Query the monitoring system at its finest retained interval, and align the peak with the termination time. Separate working set, resident memory and cache if the runtime exports them.

kubectl top pod api-7d9f6b8f9c-x2k4p --containers
kubectl get pod api-7d9f6b8f9c-x2k4p \
  -o jsonpath='{range .spec.containers[*]}{.name}{" request="}{.resources.requests.memory}{" limit="}{.resources.limits.memory}{"\n"}{end}'
kubectl describe node worker-03

Check the workload shape around the peak: request concurrency, batch size, cache warm-up, queue depth, worker count and recent releases. A memory-backed emptyDir also counts against container memory. Kubernetes warns that an unbounded memory-backed volume can consume the Pod's full memory limit, or node memory when no limit exists.

For short-lived containers, capture a heap or runtime profile before the next kill if the application supports it. Repeated restarts can make average memory look healthy because every kill resets the process before the leak or backlog becomes visible.

Choose the fix from the cause

Removing every memory limit avoids a container-limit kill but moves the failure boundary to the node. One runaway process can then compete with the kubelet and unrelated workloads. Treat that as a deliberate isolation choice, not a troubleshooting shortcut.

What Kubernetes memory QoS changes in 1.37

Kubernetes MemoryQoS graduated to beta in version 1.37 and its feature gate is enabled by default. On Linux nodes using cgroup v2, it can configure memory.high throttling and tiered protection through memory.min and memory.low. The defaults do not turn either behavior on: memoryThrottlingFactor is null and memoryReservationPolicy is None until an operator changes the kubelet configuration.

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservation

Throttling can slow allocation before a cgroup reaches its hard limit. Tiered reservation gives Guaranteed Pods memory.min protection and Burstable Pods memory.low. Neither repairs a leak or makes physical memory larger. The reservation policy is node-wide, and the protected charge includes page cache, so test it on nodes that resemble the production workload before a broad rollout.

Upgrading to 1.37 does not automatically stop OOMKilled containers. The gate is on, but the default kubelet configuration writes no memory.high, memory.min or memory.low values. The original request, limit and workload behavior still decide the failure.

Close the incident with a reproducible check

  1. Record the last termination reason, time, restart count and node.
  2. Graph container and node memory across the minutes before the kill.
  3. Identify whether the limit, node pressure or an external SIGKILL ended the process.
  4. Change code, concurrency, request, limit or node capacity according to that evidence.
  5. Replay the same workload and confirm both the peak and restart count stay within the agreed boundary.
  6. Alert on repeated OOM terminations and memory pressure, not only current usage.

If the affected workload also requests accelerators, memory failure is separate from Kubernetes GPU scheduling: a Pod can be placed on the right GPU and still have an undersized memory limit. If clients report broken connections during every restart, use the TCP reset guide to confirm whether the endpoint, proxy or a middlebox ended the flow.

Sources

Kubernetes 1.37 feature state and documentation were checked on 27 September 2026.