Layer 8Ehrlicher Tech-Support
SoftwareSchweregrad: LästigLiveness probe failed

Kubernetes: Die Liveness-Probe tötet einen gesunden Container

Der Container läuft, wird aber ständig neu gestartet? Oft ist nicht die Anwendung kaputt, sondern die Liveness-Probe zu streng eingestellt.

Aufwand:
ca. 25 Min.
Voraussetzung:
kubectl, Lesezugriff auf den Namensraum, Schreibrechte auf das Manifest der Arbeitslast
Gilt für:
Kubernetes (Probe-Vorgabewerte laut aktueller Dokumentation)

Das Symptom

Die Spalte RESTARTS zählt hoch, aber die Anwendung ist nicht kaputt. Wer sich in den Container hängt oder die Logs mitliest, sieht ein Programm, das sauber hochfährt, seine Verbindungen aufbaut, anfängt zu arbeiten — und dann mitten im Satz aufhört. Kein Absturz, keine Ausnahme, keine letzte Fehlerzeile. Nach ein paar Runden landet der Pod womöglich sogar in CrashLoopBackOff, und ab da sucht die halbe Abteilung im falschen Programm. Typisch tritt das auf, wenn eine Anwendung beim ersten Start einen Cache aufbaut, eine Datenbankmigration fährt oder mehrere hundert Megabyte an Modelldaten lädt.

Die „Layer 8“-Wahrheit

Hier ist die Abgrenzung, um die es geht: Bei CrashLoopBackOff liegt die Ursache im Container — er startet, bricht ab, und das kubelet wartet zwischen den Versuchen immer länger. Hier ist der Container gesund. Er wird von außen erschossen, weil eine Prüfung ihn für tot erklärt hat, obwohl er nur beschäftigt war. Das Ergebnis sieht identisch aus, die Ursache ist die entgegengesetzte, und genau deshalb kostet dieser Fehler so viel Zeit.

Der Grund liegt in den Vorgabewerten, die niemand liest, weil sie so harmlos klingen. Eine Liveness-Probe ohne weitere Angaben beginnt sofort nach dem Start des Containers, prüft alle zehn Sekunden, gibt der Anwendung eine einzige Sekunde zum Antworten und gibt nach drei Fehlschlägen auf. Macht rund dreißig Sekunden Gnadenfrist. Eine Anwendung, die beim ersten Start eine Minute braucht, hat gegen diese Uhr keine Chance — und sie bekommt auch nie eine, denn nach jedem Neustart beginnt dasselbe Rennen von vorn. Dass zusätzlich timeoutSeconds bei einer Sekunde steht, macht die Sache unter Last zum Glücksspiel: Der Endpunkt antwortet korrekt, nur eben in 1,2 Sekunden, und das zählt als Fehlschlag.

Der übliche Reflex, initialDelaySeconds immer weiter hochzudrehen, ist die falsche Reparatur. Er verlängert nämlich genau das Fenster, in dem eine echte Verklemmung unentdeckt bliebe — man tauscht das eine Problem gegen das andere. Die Dokumentation benennt diesen Zielkonflikt ausdrücklich: Bei Anwendungen mit zusätzlichem Zeitbedarf bei der ersten Initialisierung kann es knifflig sein, die Werte der Liveness-Probe einzustellen, ohne die schnelle Reaktion auf Verklemmungen aufzugeben, die der Grund für diese Probe war. Die Lösung ist eine eigene Prüfung für die Startphase. Die Startup-Probe hält Liveness- und Readiness-Probe zurück, bis sie einmal erfolgreich war, und gibt der Anwendung so viel Zeit, wie failureThreshold mal periodSeconds ergibt. Danach übernimmt die Liveness-Probe wieder — scharf gestellt, wie sie sein soll.

Bleibt die Frage, die man sich vor jeder Liveness-Probe stellen sollte und fast nie stellt: Braucht diese Anwendung überhaupt eine? Wenn ein Prozess bei einem Problem ohnehin von selbst abstürzt, erledigt die Neustart-Regel des Pods die Arbeit bereits, und die Probe fügt nur eine weitere Möglichkeit hinzu, aus Versehen etwas Gesundes zu töten. Eine Liveness-Probe ist ein Werkzeug für Zustände, aus denen sich ein Programm nicht selbst befreien kann. Für „gerade beschäftigt“ und „noch nicht so weit“ gibt es Readiness und Startup — und die nehmen einem laufenden Dienst höchstens den Verkehr weg, nicht das Leben.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Die Ereignisse lesen, bevor irgendetwas geändert wird

    Der Befehl zeigt den Zustand jedes Containers und die letzten Ereignisse. Gesucht sind zwei Zeilen: eine Warnung mit dem Grund Unhealthy und dem Hinweis, dass die Liveness-Probe fehlgeschlagen ist, und unmittelbar danach die Meldung über das Beenden des Containers. Fehlen diese Zeilen, ist die Probe nicht die Ursache und die Suche gehört woanders hin.

    kubectl describe pod POD-NAME
    kubectl events --for pod/POD-NAME
    
  2. 2.

    Die Logs des vorherigen Durchlaufs auf ihre letzte Zeile hin ansehen

    Entscheidend ist hier nicht der Inhalt, sondern das Ende. Bricht die Ausgabe mitten im normalen Betrieb ab, ohne Ausnahme, ohne Abschiedsmeldung, dann hat die Anwendung sich nicht selbst beendet — sie wurde beendet. Endet sie dagegen mit einem Fehler, einer fehlenden Umgebungsvariablen oder einer nicht erreichbaren Datenbank, liegt die Ursache im Container und nicht in der Probe.

    kubectl logs POD-NAME --previous
    kubectl logs POD-NAME -c CONTAINER-NAME --previous
    
  3. 3.

    Die eingestellten Werte der Proben auslesen

    Erst jetzt in das Manifest sehen. Interessant sind die vier Zahlen, die zusammen die Frist ergeben: initialDelaySeconds, periodSeconds, timeoutSeconds und failureThreshold. Fehlt eine davon, gilt der Vorgabewert — und die Vorgabewerte sind streng. Besonders häufig übersehen wird timeoutSeconds: Ohne Angabe hat die Anwendung genau eine Sekunde Zeit zu antworten.

    kubectl get pod POD-NAME -o yaml
    kubectl explain pod.spec.containers.livenessProbe
    
  4. 4.

    Die echte Startzeit messen, statt sie zu schätzen

    Den Prüf-Endpunkt lokal weiterleiten und die Antwortzeit messen. Die erste Zeile öffnet die Weiterleitung, die zweite läuft in einem zweiten Terminal und gibt Statuscode und Gesamtdauer aus. Wichtig ist der allererste Aufruf nach dem Start, nicht der zehnte: Die Dokumentation zählt eine HTTP-Probe erst dann als erfolgreich, wenn der Statuscode größer oder gleich 200 und kleiner als 400 ist. Port und Pfad an die eigene Probe anpassen.

    kubectl port-forward pod/POD-NAME 8080:8080
    curl -sS -o /dev/null -w "%{http_code} %{time_total}\n" http://127.0.0.1:8080/healthz
    
  5. 5.

    Eine Startup-Probe einrichten, statt die Liveness-Probe zu verwässern

    Das ist der Punkt, an dem die meisten den falschen Weg wählen und einfach initialDelaySeconds hochdrehen. Die Dokumentation empfiehlt ausdrücklich das andere: Setze eine Startup-Probe mit derselben Prüfung ein, mit einem Produkt aus failureThreshold und periodSeconds, das lang genug für den schlimmsten Startfall ist. Ihr eigenes Beispiel rechnet es vor — mit failureThreshold 30 und periodSeconds 10 hat die Anwendung höchstens 5 Minuten (30 mal 10 gleich 300 Sekunden), um ihren Start abzuschließen. Sobald die Startup-Probe einmal erfolgreich war, übernimmt die Liveness-Probe und liefert wieder eine schnelle Reaktion auf Verklemmungen. Und was passiert, wenn der Start wirklich nie gelingt, steht auch dort: Ist die Startup-Probe nie erfolgreich, wird der Container nach 300 Sekunden getötet und unterliegt der restartPolicy des Pods.

    startupProbe:
      httpGet:
        path: /healthz
        port: liveness-port
      failureThreshold: 30
      periodSeconds: 10
    
  6. 6.

    Zeitgrenze und Toleranz der Liveness-Probe an die Wirklichkeit anpassen

    Nach der Startup-Probe bleibt die Frage, ob die laufende Anwendung die Antwortfrist überhaupt einhalten kann. Antwortet der Endpunkt unter Last in 800 Millisekunden, ist ein timeoutSeconds von 1 ein Zufallsgenerator. Die Dokumentation warnt an dieser Stelle deutlich: Eine fehlerhaft umgesetzte Liveness-Probe kann zu kaskadierenden Fehlern führen — der Container wird unter hoher Last neu gestartet, Anfragen von Clients scheitern, weil die Anwendung schlechter skaliert, und die verbleibenden Pods bekommen zusätzliche Last ab, weil einige ausgefallen sind. Ein verbreitetes Muster ist laut Dokumentation, denselben günstigen HTTP-Endpunkt wie für die Readiness-Probe zu benutzen, aber mit einem höheren failureThreshold.

    livenessProbe:
      httpGet:
        path: /healthz
        port: liveness-port
      periodSeconds: 10
      timeoutSeconds: 3
      failureThreshold: 3
    
  7. 7.

    Prüfen, ob überhaupt eine Liveness-Probe gebraucht wird

    Die unbequemste Frage zum Schluss, und die Dokumentation stellt sie selbst: Wenn der Prozess in deinem Container von sich aus abstürzt, sobald er auf ein Problem stößt oder ungesund wird, brauchst du nicht zwingend eine Liveness-Probe — das kubelet führt dann automatisch die richtige Maßnahme gemäß der restartPolicy des Pods aus. Eine Liveness-Probe lohnt sich für Zustände, aus denen sich eine Anwendung nicht selbst befreien kann, etwa eine Verklemmung. Für alles andere ist die Readiness-Probe das mildere und meist richtige Werkzeug: Sie nimmt den Verkehr weg, statt den Prozess zu töten.

    kubectl get pod POD-NAME -o yaml
    kubectl get endpointslices
    

Häufige Fragen (FAQ)

?Wo ist der Unterschied zwischen Startup-, Liveness- und Readiness-Probe?

Drei Prüfungen mit drei verschiedenen Fragen, und die Dokumentation trennt sie sauber. Die Startup-Probe prüft, ob die Anwendung im Container gestartet ist; ist eine solche Probe eingerichtet, führt Kubernetes weder Liveness- noch Readiness-Probe aus, bis sie erfolgreich war — sie gibt der Anwendung also Zeit, ihre Initialisierung abzuschließen, und wird laut Dokumentation im Gegensatz zu den beiden anderen nur beim Start ausgeführt. Die Liveness-Probe bestimmt, wann ein Container neu zu starten ist; sie kann zum Beispiel eine Verklemmung auffangen, bei der eine Anwendung zwar läuft, aber nicht mehr vorankommt. Scheitert ein Container häufiger an ihr als die eingestellte Toleranz erlaubt, startet das kubelet diesen Container neu. Die Readiness-Probe bestimmt, wann ein Container bereit ist, Verkehr anzunehmen; scheitert sie, entfernt der EndpointSlice-Controller die IP-Adresse des Pods aus den EndpointSlices aller passenden Services. Kurz: Readiness nimmt Verkehr weg, Liveness tötet, Startup hält die anderen beiden zurück.

?Woran erkenne ich, dass die Probe schuld ist und nicht die Anwendung?

An drei Befunden zusammen. Erstens stehen in den Ereignissen des Pods Meldungen mit dem Grund Unhealthy und dem Text, dass die Liveness-Probe fehlgeschlagen ist — und direkt danach die Meldung, dass der Container beendet wird. Zweitens enden die Logs des vorherigen Durchlaufs nicht mit einer Ausnahme oder einer Fehlermeldung, sondern mitten im normalen Betrieb: Die Anwendung wurde von außen abgeräumt, sie ist nicht gefallen. Drittens passt die Uhr: Der Abbruch kommt reproduzierbar nach initialDelaySeconds plus failureThreshold mal periodSeconds und nicht zu einem fachlich sinnvollen Zeitpunkt. Genau dieser Rechenweg steht in der Dokumentation, und zwar als Kriterium dafür, wann eine Startup-Probe nötig ist: Startet dein Container üblicherweise in mehr als initialDelaySeconds plus failureThreshold mal periodSeconds, solltest du eine Startup-Probe angeben, die denselben Endpunkt prüft wie die Liveness-Probe.

?Welche Werte gelten, wenn ich in der Probe nichts angebe?

Die Vorgabewerte sind der Grund, warum dieses Problem so oft aus dem Nichts auftaucht — sie sind knapp. Die Dokumentation nennt sie einzeln: initialDelaySeconds ist die Zahl der Sekunden nach dem Start des Containers, bevor Startup-, Liveness- oder Readiness-Proben beginnen, mit dem Vorgabewert 0 Sekunden und dem Mindestwert 0. periodSeconds gibt an, wie oft geprüft wird, mit dem Vorgabewert 10 Sekunden und dem Mindestwert 1. timeoutSeconds ist die Zahl der Sekunden, nach denen die Probe in eine Zeitüberschreitung läuft, mit dem Vorgabewert 1 Sekunde und dem Mindestwert 1. successThreshold, die Mindestzahl aufeinanderfolgender Erfolge nach einem Fehlschlag, hat den Vorgabewert 1 und MUSS für Liveness- und Startup-Proben 1 sein. failureThreshold hat den Vorgabewert 3 und den Mindestwert 1: Nachdem eine Probe failureThreshold mal hintereinander fehlgeschlagen ist, betrachtet Kubernetes die Prüfung insgesamt als gescheitert. Eine Anwendung, die beim ersten Start 40 Sekunden braucht, ist mit diesen Vorgaben nach rund 30 Sekunden tot.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWLästigCrashLoopBackOff

Kubernetes: Pod in CrashLoopBackOff

CrashLoopBackOff ist kein Fehler von Kubernetes, sondern die Meldung, dass dein Container beim Start abbricht. So kommst du an die Ursache.

SWAnfängerfehlerImagePullBackOff

Kubernetes: ImagePullBackOff und ErrImagePull

Der Pod wartet, weil das Abbild nicht kommt: falscher Name, falsches Tag, fehlende Zugangsdaten. So liest du die Ursache statt zu raten.