Veröffentlicht am

Exit Code 137: OOMKilled auf Kubernetes beheben

Teilen:
Authors

Exit Code 137: OOMKilled von einem Probe-Kill unterscheiden

TL;DR

137 ist 128 plus 9. Der Prozess hat SIGKILL bekommen und konnte sich nicht mehr sauber beenden. Auf Kubernetes gibt es dafür zwei Alltagsursachen. OOMKilled steht als Reason, wenn der cgroup das Memory-Limit überschritten hat. Error bei demselben Code ist meist die Liveness-Probe. Beides sehen Sie nur im letzten Zustand:

kubectl describe pod POD

Suche nach Last State → Reason und Exit Code. Liegt der Pod schon wieder im CrashLoopBackOff, ist 137 der Grund für den Loop, nicht ein zweites Problem.

Reason entscheidet, nicht die Zahl allein

Last StateBedeutungFix
Reason OOMKilled, Exit 137Speicherlimit gerissenLimit anheben oder Verbrauch senken
Reason Error, Exit 137SIGKILL ohne OOM, oft LivenessProbe entschärfen, startupProbe setzen
Reason Error, Exit 143SIGTERM, Grace-Period lief abProzess fängt SIGTERM, Grace erhöhen

Ein manuelles kubectl delete schickt zuerst SIGTERM. Antwortet der Prozess nicht innerhalb terminationGracePeriodSeconds (Default 30s), folgt SIGKILL und der Code kann 137 sein. Das ist ein Stopp, kein OOM. OOM erkennen Sie am Wort OOMKilled, nicht an der 137.

Limit, Request und das, was die Anwendung glaubt

Das Limit ist eine harte cgroup-Grenze. Der Kernel killt den Prozess, sobald der Verbrauch darüber liegt, inklusive Page Cache je nach Zählung. Der Request ist nur die Scheduler-Reservierung. Ein Request von 512Mi bei einem Limit von 256Mi ist ungültig. Ein Request von 128Mi bei einem Limit von 256Mi schedult leicht und killt, sobald die Anwendung über 256Mi geht.

Java, Node und .NET kennen diese Grenze nicht, solange Sie sie nicht durchreichen. Eine JVM mit -Xmx2g in einem Limit von 1Gi wird zuverlässig OOMKilled. Das Heap-Limit muss unter dem Container-Limit liegen, mit Luft für Metaspace, Threads und Direct Buffers. Faustregel: Heap bei etwa 75 Prozent des Memory-Limits, dann messen.

resources:
  requests:
    memory: 512Mi
  limits:
    memory: 1Gi

Ohne Limit killt der Node irgendwann den größten Verbraucher über den System-OOM, nicht über OOMKilled am Pod. In describe fehlt dann der Reason, auf dem Node steht ein Kernel-OOM. Für Produktion ein Limit setzen. Sonst debuggen Sie den falschen Prozess.

Messen, bevor das Limit verdoppelt wird

kubectl top pod POD --containers

kubectl top zeigt den aktuellen Verbrauch, nicht den Peak, der den Kill ausgelöst hat. Liegt der Wert schon knapp unter dem Limit, ist die Richtung klar. Liegt er weit darunter, war es eine Spitze: Startup, ein Report, ein Cache. Dann hilft ein höheres Limit oder weniger Gleichzeitigkeit, nicht ein dauerhaft dreifacher Request. Der Request entscheidet, ob der Pod noch einen Node findet. Wer nur das Limit anhebt und den Request mitzieht, landet als Nächstes bei Pod Pending, weil kein Node die Reservierung frei hat.

Liveness, die wie OOM aussieht

Die Probe schlägt fehl, das Kubelet wartet failureThreshold ab und killt den Container. Exit 137, Reason Error, kein OOMKilled. Typisch, wenn der Check schneller ist als der Start oder auf einen Pfad geht, der unter Last blockiert. startupProbe für den Start, Liveness nur für „der Prozess ist tot“. Ein HTTP-Check auf einen Endpoint, der die Datenbank anfasst, killt den Pod genau dann, wenn die Datenbank langsam ist.

Quellen

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen