Liveness- und Readiness-Endpoints für Kubernetes
Kubernetes entscheidet anhand von Health-Checks, ob ein Pod neu gestartet oder aus dem Load-Balancing herausgenommen werden muss, doch beide Entscheidungen brauchen unterschiedliche Informationen. Ein Liveness-Check beantwortet nur die Frage, ob der PHP-Prozess überhaupt noch läuft, während ein Readiness-Check prüfen muss, ob die Anwendung tatsächlich Traffic sinnvoll verarbeiten kann, etwa weil die Datenbankverbindung steht. Wer beide Checks über denselben Endpoint beantwortet, riskiert entweder unnötige Neustarts oder Traffic auf Pods, die eigentlich nicht bereit sind.
Inhaltsverzeichnis
- 1. Der Unterschied zwischen Liveness und Readiness
- 2. Ein dedizierter, ungesicherter Health-Endpoint
- 3. Readiness-Check für die Datenbankverbindung
- 4. Checks für Redis und externe Abhängigkeiten
- 5. Eine zentrale HealthCheckRegistry für erweiterbare Checks
- 6. Die passende Kubernetes-Probe-Konfiguration
- 7. Graceful Shutdown: Readiness während des Pod-Terminierens berücksichtigen
- 8. Startup-Probes für langsam startende Anwendungen
- 9. Häufige Fehler bei der Umsetzung
- 10. Zusammenfassung
- 11. FAQ
1. Der Unterschied zwischen Liveness und Readiness
Eine Liveness-Probe beantwortet eine sehr einfache Frage: Läuft der Prozess noch und reagiert er überhaupt auf Anfragen? Schlägt die Liveness-Probe mehrfach hintereinander fehl, geht Kubernetes davon aus, dass der Container in einem nicht mehr reparierbaren Zustand hängt, etwa durch einen Deadlock oder eine Endlosschleife, und startet den Container neu. Eine Liveness-Probe sollte deshalb bewusst minimal gehalten werden und keine externen Abhängigkeiten prüfen, denn ein Ausfall der Datenbank ist kein Grund, einen ansonsten gesunden PHP-Prozess neu zu starten.
Eine Readiness-Probe beantwortet dagegen die Frage, ob der Pod aktuell in der Lage ist, eingehenden Traffic sinnvoll zu verarbeiten. Schlägt sie fehl, entfernt Kubernetes den Pod aus dem Service-Endpoint, ohne den Container neu zu starten, und leitet keinen Traffic mehr dorthin, bis die Probe wieder erfolgreich ist. Das ist etwa während des Hochfahrens sinnvoll, wenn die Anwendung noch Verbindungen aufbaut, oder während eines vorübergehenden Ausfalls einer Abhängigkeit wie der Datenbank, ohne dass der Pod selbst als defekt gilt.
2. Ein dedizierter, ungesicherter Health-Endpoint
Health-Endpoints sollten außerhalb der regulären Firewall- und Authentifizierungs-Konfiguration liegen, da Kubernetes-Probes ohne Session-Cookies oder API-Tokens anfragen. In der security.yaml wird dafür typischerweise ein eigener Firewall-Eintrag mit pattern auf /health(/.*)? und security: false angelegt, sodass diese Routen komplett außerhalb jeder Authentifizierungs-Prüfung liegen, aber weiterhin durch das normale Symfony-Routing und Controller-System bedient werden.
Wichtig ist, den Health-Endpoint bewusst schlank zu halten und keine sensiblen Informationen preiszugeben. Eine Readiness-Antwort sollte im Erfolgsfall nur einen einfachen Status wie {"status": "ok"} liefern und im Fehlerfall zwar mitteilen, welche Abhängigkeit fehlgeschlagen ist, aber keine internen Details wie Connection-Strings oder Stacktraces preisgeben, da der Endpoint öffentlich ohne Authentifizierung erreichbar ist.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Health\HealthCheckRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
/**
* Liveness- und Readiness-Endpoints für Kubernetes-Probes.
*/
final class HealthController
{
public function __construct(private readonly HealthCheckRegistry $registry)
{
}
#[Route('/health/live', name: 'health_live', methods: ['GET'])]
public function live(): JsonResponse
{
// Liveness: nur prüfen, ob der Prozess überhaupt antwortet.
return new JsonResponse(['status' => 'ok']);
}
#[Route('/health/ready', name: 'health_ready', methods: ['GET'])]
public function ready(): JsonResponse
{
$results = $this->registry->runAll();
$healthy = array_reduce(
$results,
static fn (bool $carry, bool $ok): bool => $carry && $ok,
true,
);
return new JsonResponse(
['status' => $healthy ? 'ok' : 'unavailable', 'checks' => $results],
$healthy ? 200 : 503,
);
}
}
3. Readiness-Check für die Datenbankverbindung
Ein Datenbank-Check sollte nicht einfach nur die Doctrine-Connection aus dem Container holen, sondern aktiv prüfen, ob eine Verbindung tatsächlich aufgebaut werden kann. Üblich ist eine minimale Query wie SELECT 1, die keine Tabellenzugriffe braucht und daher auch dann funktioniert, wenn das Schema noch nicht vollständig migriert ist. Wichtig ist ein kurzes Timeout für diese Prüfung, damit eine hängende Datenbankverbindung nicht die gesamte Readiness-Probe blockiert und Kubernetes länger als nötig auf eine Antwort warten lässt.
Für den Verbindungsaufbau selbst empfiehlt es sich, eventuell auftretende Exceptions gezielt abzufangen und in ein einfaches boolesches Ergebnis zu übersetzen, statt sie ungefangen bis zum Controller durchzureichen. So bleibt die Readiness-Antwort auch bei einem Datenbankausfall ein sauberes JSON mit HTTP-Status 503, statt eine unformatierte Fehlerseite mit potenziell sensiblen Stacktrace-Informationen zu zeigen.
4. Checks für Redis und externe Abhängigkeiten
Für Redis, etwa als Cache- oder Session-Backend, eignet sich ein einfacher PING-Befehl, der bei erfolgreicher Antwort PONG zurückliefert und damit sowohl Netzwerk-Erreichbarkeit als auch grundsätzliche Funktionsfähigkeit bestätigt. Auch hier gilt: Ein kurzes Timeout verhindert, dass ein hängender Redis-Server die gesamte Readiness-Probe unnötig verzögert.
Bei externen Abhängigkeiten wie Drittanbieter-APIs ist zu überlegen, ob ein Ausfall dieser Abhängigkeit tatsächlich bedeuten soll, dass der Pod als nicht bereit gilt. Ist die externe API nur für eine Nebenfunktion nötig, etwa einen optionalen Versand-Tracking-Abruf, sollte ihr Ausfall nicht die komplette Readiness-Probe scheitern lassen, da sonst der gesamte Pod aus dem Load-Balancing genommen wird, obwohl der Kernfunktionsumfang weiterhin einwandfrei funktioniert.
5. Eine zentrale HealthCheckRegistry für erweiterbare Checks
Statt einzelne Checks fest im Controller zu verdrahten, bietet sich eine HealthCheckRegistry an, die einzelne Check-Klassen über ein gemeinsames HealthCheckInterface einsammelt, etwa per Symfony-Service-Tagging. Jeder Check implementiert eine einzige Methode, die true oder false zurückgibt, und bekommt seine Abhängigkeiten, etwa die Doctrine-Connection oder den Redis-Client, ganz normal per Constructor Injection.
Dieser Ansatz macht es einfach, neue Checks hinzuzufügen, ohne den Controller selbst anzufassen: Eine neue Check-Klasse wird geschrieben, als Service mit dem passenden Tag registriert, und die Registry nimmt sie beim nächsten Request automatisch mit auf. Das ist besonders in wachsenden Systemlandschaften mit mehreren externen Abhängigkeiten praktisch, weil sich der Health-Endpoint so organisch mit den tatsächlichen Abhängigkeiten der Anwendung weiterentwickelt.
6. Die passende Kubernetes-Probe-Konfiguration
In der Pod-Spezifikation werden livenessProbe und readinessProbe als eigene httpGet-Blöcke mit jeweils eigenem Pfad konfiguriert, also /health/live für die Liveness- und /health/ready für die Readiness-Probe. Wichtig sind auch initialDelaySeconds und periodSeconds: Eine zu kurze initiale Verzögerung kann dazu führen, dass Kubernetes einen Pod neu startet, der schlicht noch beim Hochfahren ist, während ein zu langes periodSeconds bedeutet, dass ein tatsächlicher Ausfall erst spät erkannt wird.
Ein häufiger Fehler ist, failureThreshold für die Liveness-Probe zu niedrig zu setzen, wodurch kurzzeitige Lastspitzen, die die Antwortzeit vorübergehend erhöhen, fälschlicherweise als Ausfall gewertet werden und einen unnötigen Neustart auslösen. Ein höherer Schwellenwert bei der Liveness-Probe im Vergleich zur Readiness-Probe ist meist sinnvoll, da ein Neustart eine deutlich einschneidendere Maßnahme ist als das vorübergehende Herausnehmen aus dem Load-Balancing.
7. Graceful Shutdown: Readiness während des Pod-Terminierens berücksichtigen
Ein oft übersehener Aspekt bei Readiness-Probes ist das Verhalten während des Pod-Terminierens selbst. Wenn Kubernetes einen Pod beendet, sendet es zunächst ein SIGTERM-Signal und wartet bis zu terminationGracePeriodSeconds, bevor es den Prozess hart mit SIGKILL beendet. Problematisch wird es, wenn Kubernetes den Pod parallel noch als bereit führt und weiterhin Traffic dorthin leitet, während die Anwendung bereits mit dem Herunterfahren begonnen hat, was zu abgebrochenen Requests führen kann, weil eingehende Verbindungen mitten in der Verarbeitung abreißen.
Die übliche Lösung ist ein preStop-Hook, der vor dem eigentlichen SIGTERM eine kurze Wartezeit einlegt, typischerweise wenige Sekunden, damit sich die Änderung am Service-Endpoint im gesamten Cluster verteilen kann, bevor der Prozess tatsächlich beendet wird. Zusätzlich kann die Readiness-Probe so implementiert werden, dass sie bei einem eingehenden SIGTERM sofort false zurückgibt, auch wenn der Prozess selbst noch läuft, damit Kubernetes den Pod schnellstmöglich aus dem Load-Balancing entfernt, während bereits laufende Requests noch geordnet zu Ende verarbeitet werden können.
8. Startup-Probes für langsam startende Anwendungen
Bei Symfony-Anwendungen mit aufwendigem Boot-Prozess, etwa durch große Container-Kompilierung oder Cache-Warmup, kann eine zusätzliche startupProbe sinnvoll sein. Sie unterdrückt Liveness- und Readiness-Probes so lange, bis die Anwendung einmal erfolgreich geantwortet hat, und verhindert damit, dass Kubernetes einen Pod während eines längeren, aber normalen Startvorgangs fälschlicherweise als hängend interpretiert.
Die startupProbe kann denselben Endpoint wie die Liveness-Probe verwenden, sollte aber mit großzügigeren failureThreshold- und periodSeconds-Werten konfiguriert werden, da hier bewusst mehr Zeit für den initialen Start eingeräumt wird, ohne dass die reguläre Liveness-Probe während des laufenden Betriebs dieselbe Nachsicht zeigen muss.
9. Häufige Fehler bei der Umsetzung
Ein verbreiteter Fehler ist, denselben Endpoint für Liveness und Readiness zu verwenden und darin auch die Datenbankverbindung zu prüfen. Fällt die Datenbank kurzzeitig aus, wertet Kubernetes das dann als Liveness-Fehler und startet alle Pods neu, obwohl ein Neustart das eigentliche Problem, den Datenbankausfall, gar nicht löst und stattdessen zusätzliche Instabilität durch gleichzeitige Neustarts erzeugt.
Ein weiterer häufiger Fehler ist, den Health-Endpoint versehentlich hinter der regulären Authentifizierung zu belassen. Kubernetes-Probes senden keine Credentials mit, sodass ein solcher Endpoint immer mit HTTP 401 antwortet und Kubernetes den Pod dauerhaft als nicht bereit oder nicht lebendig einstuft, selbst wenn die Anwendung intern vollkommen funktionsfähig ist.
| Probe-Typ | Prüft | Bei Fehlschlag | Typische Prüfungen |
|---|---|---|---|
| Liveness | Prozess reagiert | Container-Neustart | einfacher HTTP-200-Response |
| Readiness | Traffic-Bereitschaft | Entfernung aus Service | DB, Redis, kritische Abhängigkeiten |
| Startup | Boot abgeschlossen | unterdrückt andere Probes | wie Liveness, mit mehr Geduld |
| Health-Endpoint | beide Zustände | je nach Route | getrennte Pfade /health/live, /health/ready |
Mironsoft
Symfony-Architektur, saubere Domänenlogik und Legacy-Modernisierung
Symfony-Anwendungen, die nach zwei Jahren noch wartbar sind?
Wir prüfen bestehende Symfony-Projekte auf aufgeblähte Controller, fehlende Serviceabstraktionen und ungetestete Kernlogik und bauen daraus eine Architektur, die neue Features aufnimmt, ohne bei jedem Release brüchiger zu werden.
Architektur-Review
Bundle-Struktur, Dependency Injection und Serviceabstraktionen auf Wartbarkeit prüfen.
Legacy-Modernisierung
Schrittweise Migration veralteter Symfony-Versionen ohne kompletten Neuschrieb.
Test- und Qualitätssicherung
PHPUnit, PHPStan und CI-Pipelines für nachhaltige Codequalität aufbauen.
10. Zusammenfassung
Health-Checks für Kubernetes: Das Wichtigste auf einen Blick
Liveness
minimal, prüft nur, ob der Prozess überhaupt antwortet
Readiness
prüft echte Traffic-Bereitschaft inklusive Datenbank und Redis
Endpoint
dediziert, außerhalb der Firewall-Authentifizierung
Registry
erweiterbare HealthCheckRegistry statt fest verdrahteter Checks