Das Symptom
Der Pod lief, und plötzlich steht in seinem Zustand OOMKilled, dazu der Exit-Code
137. Kein Absturzbericht, keine letzte Log-Zeile mit einer Fehlermeldung der
Anwendung, oft nicht einmal ein halber Satz – die Ausgabe endet mitten im Betrieb.
Das kubelet startet den Container neu, alles wirkt kurz normal, und ein paar Stunden
später steht dasselbe wieder da. Auf dem Knoten sind derweil Gigabytes an
Arbeitsspeicher frei.
Die „Layer 8“-Wahrheit
Genau dieses letzte Detail ist der Schlüssel, und es ist der Punkt, an dem die meisten
in die falsche Richtung suchen. Der Knoten hatte reichlich Speicher. Er wurde nie
gefragt. Beendet wurde der Container, weil er die Grenze überschritten hat, die in
seiner eigenen Beschreibung steht – ein Wert, den irgendwann jemand aus dem
Handgelenk hingeschrieben hat, weil das Feld nun einmal ausgefüllt werden wollte. Die
Dokumentation ist da unmissverständlich: Ein Container darf seine Anforderung
überschreiten, solange der Knoten Speicher übrig hat, sein Limit aber niemals.
Das unterscheidet den Fall grundlegend von dem, was auf einem gewöhnlichen Linux-Server
passiert. Dort schlägt der OOM-Killer zu, weil dem gesamten System der Speicher
ausgeht, und er sucht sich sein Opfer nach Verbrauch aus – getroffen wird oft nicht der
Verursacher, sondern der Dickste im Raum. Hier ist beides anders: Es herrscht kein
Mangel, und das Opfer stand von vornherein fest. Der Kernel setzt lediglich eine
Buchhaltungsregel durch, die man selbst aufgestellt hat.
Dazu kommt eine Eigenart, die das Ganze so unberechenbar wirken lässt: Die Grenze wird
reaktiv durchgesetzt. Die Dokumentation schreibt ausdrücklich, dass Beendigungen erst
erfolgen, wenn der Kernel Speicherdruck feststellt – ein Container über seiner Grenze
wird also nicht zwingend sofort beendet. Deshalb überlebt dieselbe Anwendung eine
Woche lang knapp über dem Limit und stirbt dann ausgerechnet nachts um drei, wenn ein
Nachbar-Pod aufwacht. Es sieht nach Zufall aus, ist aber nur verzögerte Konsequenz.
Die Lösung ist entsprechend unromantisch: messen, was die Anwendung wirklich braucht,
und die Grenze danach setzen – statt sie zu raten oder, schlimmer, ganz zu streichen.
Wer das Limit entfernt, hat den Container gerettet und den Knoten geopfert. Das merkt
man dann daran, dass beim nächsten Mal nicht ein Pod stirbt, sondern alle auf dieser
Maschine.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.Den Zustand und die Zahl der Neustarts ansehen
In der Übersicht erscheint der Pod als beendet oder erneut startend, die Zahl der Neustarts wächst. Ein Container, der beim Start sofort stirbt, sieht hier genauso aus wie einer, der erst unter Last umkippt – der Unterschied ergibt sich aus den nächsten Schritten.
kubectl get pods
2.Grund und Exit-Code des letzten Laufs auslesen
Der vollständige Zustand nennt unter lastState.terminated den Grund und den Exit-Code. Die Kubernetes-Dokumentation zeigt für diesen Fall genau diese Ausgabe: reason OOMKilled und exitCode 137. Damit ist der Verdacht nicht mehr Vermutung, sondern belegt.
kubectl get pod POD-NAME -o yaml
3.Denselben Punkt kurz abfragen
Für den schnellen Blick oder ein Skript reicht ein gezielter Auszug. Steht dort nichts, wurde der Container bisher nicht wegen Speichers beendet – dann ist die Ursache eine andere und die Suche geht woanders weiter.
kubectl get pod POD-NAME -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
4.Das gesetzte Limit nachlesen
Jetzt die Vorgabe ansehen, an der der Container gescheitert ist. Wichtig ist der Unterschied zwischen Anforderung und Grenze: Nach der Anforderung wird der Pod auf einen Knoten verteilt, an der Grenze wird er beendet. Wer nur die Anforderung erhöht, verschiebt den Pod – er verhindert den Tod nicht.
kubectl get pod POD-NAME -o jsonpath='{.spec.containers[*].resources}'
5.Den tatsächlichen Verbrauch messen
Wie nah die Anwendung im Normalbetrieb an der Grenze läuft, zeigt die Verbrauchsmessung. Sie setzt einen laufenden metrics-server im Cluster voraus. Liegt der Wert dauerhaft knapp unter dem Limit, ist der nächste Lastspitzen-Abend nur eine Frage der Zeit.
kubectl top pod POD-NAME
6.Den Knoten ausschließen
Hier wird die Abgrenzung entschieden. Zeigt der Knoten Speicherdruck an oder finden sich mehrere verdrängte Pods, war nicht das Limit das Problem, sondern der Knoten war zu klein für alles, was auf ihm liegt. Dann hilft kein höheres Limit, sondern Verteilung oder mehr Kapazität.
kubectl get pods -o wide
kubectl describe node KNOTEN-NAME
7.Die Grenze anpassen oder die Anwendung bändigen
Zwei ehrliche Möglichkeiten: das Limit auf den gemessenen Bedarf anheben, oder der Anwendung beibringen, weniger zu brauchen. Laufzeitumgebungen mit eigener Speicherverwaltung – etwa die JVM oder Node.js – müssen dabei zusätzlich intern begrenzt werden, sonst kennen sie die Grenze des Containers gar nicht und laufen sehenden Auges hinein.
8.Nicht das Limit ganz weglassen
Der bequeme Ausweg, die Grenze zu streichen, verlagert das Problem nur auf den Knoten: Statt eines beendeten Containers gefährdet die Anwendung dann alle Pods, die sich denselben Knoten teilen. Ein Limit ist keine Schikane, sondern die Zusage, dass ein einzelner Ausrutscher nicht die Nachbarn mitreißt.