Symfony und Kubernetes: Health Checks mit Liveness und Readiness Probes
AI generated
SF
{ }
Symfony · Kubernetes · DevOps · Observability
Symfony und Kubernetes
Health Checks mit Liveness und Readiness Probes

Ein falsch konfigurierter Health Check ist gefährlicher als gar keiner. Kubernetes startet gesunde Pods neu, weil eine Liveness Probe zu streng geschrieben wurde, oder schickt Traffic an einen Pod, dessen Datenbankverbindung längst abgebrochen ist. Dieser Artikel zeigt, wie ein Health Check für Symfony in Kubernetes wirklich zuverlässig wird, mit Liveness, Readiness und Startup Probe im Zusammenspiel.

17 Min. Lesezeit Liveness · Readiness · Startup Probe · Health Endpoint Symfony 7 · Kubernetes 1.30 · PHP 8.4

1. Warum ein Health Check in Kubernetes über den Betrieb entscheidet

Kubernetes trifft anhand eines Health Check automatisiert Entscheidungen über Leben und Traffic eines Pods: neustarten, aus dem Load Balancer nehmen, weiter warten. Diese Entscheidungen laufen ohne menschliches Zutun ab, und genau deshalb ist die Qualität des zugrunde liegenden Health Check so wichtig. Ein zu einfacher Check, der nur prüft, ob PHP-FPM antwortet, sagt nichts darüber aus, ob die Anwendung tatsächlich Datenbankanfragen beantworten kann.

Umgekehrt ist ein zu strenger Health Check genauso gefährlich. Prüft die Liveness Probe die Datenbankverbindung mit, führt ein kurzer Netzwerk-Hänger bei der Datenbank dazu, dass Kubernetes reihenweise gesunde Anwendungscontainer neustartet, obwohl das eigentliche Problem woanders liegt. Ein durchdachter Health Check für Symfony trennt deshalb strikt zwischen der Frage, ob der Prozess selbst noch lebt, und der Frage, ob er gerade in der Lage ist, Anfragen sinnvoll zu beantworten.

Dieser Artikel behandelt genau diese Trennung: Liveness Probe, Readiness Probe und Startup Probe haben unterschiedliche Aufgaben, unterschiedliche Konsequenzen bei Fehlschlag und sollten deshalb unterschiedliche Endpunkte oder zumindest unterschiedliche Prüftiefen verwenden. Ein einziger, undifferenzierter Health Check für alle drei Probe-Typen ist einer der häufigsten Fehler in Symfony-Kubernetes-Setups.

2. Liveness Probes: wann Kubernetes einen Container neustartet

Die Liveness Probe beantwortet eine einzige Frage: Ist der Prozess im Container noch in einem Zustand, aus dem er sich selbst erholen kann, oder ist er in einem Deadlock oder einer Endlosschleife gefangen, aus der nur ein Neustart hilft. Schlägt die Liveness Probe wiederholt fehl, tötet Kubernetes den Container und startet ihn neu. Genau deshalb sollte dieser Health Check so minimal wie möglich sein: Er darf ausschließlich prüfen, ob der PHP-Prozess selbst antwortet, nicht ob externe Abhängigkeiten erreichbar sind.

Ein Symfony-typischer Fehler ist eine Liveness Probe, die dieselbe Route wie die Readiness Probe nutzt und dabei auch die Datenbankverbindung prüft. Fällt die Datenbank für zehn Sekunden aus, killt Kubernetes plötzlich alle Anwendungspods gleichzeitig, obwohl kein einziger Prozess tatsächlich hängen geblieben ist. Die Datenbank kommt zurück, aber alle Pods starten in diesem Moment gleichzeitig neu, was die Downtime künstlich verlängert statt sie zu verkürzen. Die Liveness Probe für ein sauberes Health Check-Setup prüft daher ausschließlich den lokalen Prozesszustand.


<?php
// src/Controller/HealthController.php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

final class HealthController
{
    // Liveness endpoint: only confirms the PHP process itself responds.
    // No external dependency check here — a slow database must never
    // cause Kubernetes to kill and restart otherwise healthy pods.
    #[Route('/health/live', name: 'health_live', methods: ['GET'])]
    public function live(): JsonResponse
    {
        return new JsonResponse(['status' => 'ok'], 200);
    }
}

3. Readiness Probes: wann ein Pod tatsächlich Traffic erhält

Die Readiness Probe beantwortet eine andere Frage: Ist der Pod gerade in der Lage, eingehenden Traffic sinnvoll zu bearbeiten. Schlägt dieser Health Check fehl, entfernt Kubernetes den Pod aus dem Service-Endpunkt, ohne ihn zu killen. Der Container läuft weiter, bekommt aber keinen neuen Traffic mehr, bis die Readiness Probe wieder erfolgreich ist. Das ist der entscheidende Unterschied zur Liveness Probe und der Grund, warum externe Abhängigkeiten hier geprüft werden dürfen und sollen.

Für Symfony bedeutet das: Die Readiness Probe prüft, ob die Datenbankverbindung steht, ob der Cache-Layer erreichbar ist und ob gegebenenfalls eine Message-Queue-Verbindung aufgebaut werden kann. Fällt eine dieser Abhängigkeiten aus, nimmt sich der Pod selbst aus der Rotation, während andere, gesunde Pods weiter Traffic bekommen. Das verhindert, dass Nutzeranfragen auf einem Pod landen, der ohnehin nur mit einem Fehler antworten könnte, ohne dass ein einziger Container neugestartet werden müsste.

4. Startup Probes für langsam startende Symfony Anwendungen

Symfony-Anwendungen mit großem Container, vielen Bundles oder einem kalten OPcache können mehrere Sekunden bis Minuten benötigen, bevor die erste Anfrage zuverlässig beantwortet wird. Ohne eine Startup Probe interpretiert Kubernetes eine langsam startende Anwendung fälschlich als gescheiterte Liveness Probe und killt den Container, noch bevor er überhaupt fertig hochgefahren ist. Das führt zu einer Neustart-Schleife, aus der der Pod nie herauskommt, weil jeder Neustart wieder dieselbe Startzeit benötigt.

Die Startup Probe löst dieses Problem, indem sie Liveness und Readiness Probe so lange deaktiviert, bis der erste erfolgreiche Startup-Check eintrifft. Erst danach übernehmen die regulären Probes die Überwachung. Für ein Health Check-Setup mit Symfony ist die Startup Probe deshalb kein optionales Detail, sondern in Umgebungen mit merklicher Bootzeit ein notwendiger Bestandteil, um Neustart-Schleifen bei jedem Deployment und jedem Node-Wechsel zu vermeiden.


# k8s/deployment.yaml — probes section for a Symfony deployment
containers:
  - name: symfony-app
    image: registry.mironsoft.de/symfony-app:1.4.2
    ports:
      - containerPort: 9000

    # Startup probe: gives the container time to boot before
    # liveness/readiness even start evaluating
    startupProbe:
      httpGet:
        path: /health/live
        port: 8080
      failureThreshold: 30
      periodSeconds: 2

    livenessProbe:
      httpGet:
        path: /health/live
        port: 8080
      initialDelaySeconds: 0
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3

    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080
      initialDelaySeconds: 0
      periodSeconds: 5
      timeoutSeconds: 3
      failureThreshold: 2

5. Einen eigenen Health Check Endpoint in Symfony bauen

Ein sauberer Health Check Endpoint in Symfony gehört nicht in den Anwendungs-Namespace der Business-Logik, sondern in einen eigenen, leichten Controller, der bewusst keine anderen Services der Anwendung berührt außer den explizit zu prüfenden. Wichtig ist außerdem, dass diese Route nicht durch Middleware für Authentifizierung, Session-Handling oder CSRF-Schutz läuft, da diese Schichten selbst wieder Abhängigkeiten wie Session-Storage oder Redis einführen, die den Health Check unnötig verkomplizieren.

In der Praxis empfiehlt sich ein eigener Firewall-Bereich in der Symfony Security-Konfiguration, der die Health-Routen komplett von Authentifizierung ausnimmt. So bleibt der Endpoint auch dann erreichbar, wenn die Session-Infrastruktur selbst gerade Probleme hat, was für einen zuverlässigen Health Check unerlässlich ist. Zusätzlich sollte der Endpoint möglichst wenig Speicher allozieren und keine schweren Services aus dem Container instanziieren, die für die eigentliche Prüfung nicht gebraucht werden.


# config/packages/security.yaml — exempt health routes from authentication
security:
  firewalls:
    health:
      pattern: ^/health
      security: false
      stateless: true

    main:
      lazy: true
      provider: app_user_provider

6. Abhängigkeiten prüfen: Datenbank, Cache und Message Queue

Der Readiness-Endpoint prüft typischerweise drei Kategorien von Abhängigkeiten: die primäre Datenbankverbindung über eine minimale Query wie SELECT 1, den Cache-Layer über einen einfachen Ping-Befehl an Redis oder Memcached, und optional die Erreichbarkeit einer Message-Queue wie RabbitMQ. Jede dieser Prüfungen sollte ein enges Timeout von unter einer Sekunde haben, damit ein hängender Check nicht selbst zum Problem wird und die gesamte Probe blockiert.

Entscheidend ist außerdem, dass ein einzelner fehlgeschlagener Abhängigkeits-Check nicht zwangsläufig den gesamten Health Check als rot markieren muss. Ist die Message-Queue für einen asynchronen Reporting-Job kurzzeitig nicht erreichbar, während Datenbank und Cache einwandfrei funktionieren, kann es sinnvoller sein, den Pod weiterhin für synchrone HTTP-Anfragen bereitzuhalten und nur einen internen Alarm auszulösen, statt ihn komplett aus der Rotation zu nehmen. Diese Abwägung hängt von der jeweiligen Anwendung ab und sollte bewusst getroffen werden, nicht implizit durch einen pauschalen Alles-oder-nichts-Check.


<?php
// src/Service/HealthCheckService.php
declare(strict_types=1);

namespace App\Service;

use Doctrine\DBAL\Connection;
use Symfony\Component\Cache\Adapter\RedisAdapter;

final class HealthCheckService
{
    public function __construct(
        private readonly Connection $connection,
        private readonly \Redis $redis,
    ) {
    }

    /**
     * Runs all readiness checks with a strict timeout per dependency.
     *
     * @return array<string, bool>
     */
    public function checkReadiness(): array
    {
        return [
            'database' => $this->checkDatabase(),
            'cache' => $this->checkCache(),
        ];
    }

    private function checkDatabase(): bool
    {
        try {
            $this->connection->executeQuery('SELECT 1');
            return true;
        } catch (\Throwable) {
            return false;
        }
    }

    private function checkCache(): bool
    {
        try {
            return $this->redis->ping() !== false;
        } catch (\Throwable) {
            return false;
        }
    }
}

7. Probes im Kubernetes-Manifest korrekt konfigurieren

Neben der reinen Existenz der Probes entscheidet die Feinkonfiguration über die Stabilität eines Health Check-Setups. periodSeconds bestimmt, wie oft geprüft wird, timeoutSeconds wie lange auf eine Antwort gewartet wird, und failureThreshold wie viele aufeinanderfolgende Fehlschläge nötig sind, bevor Kubernetes reagiert. Für die Liveness Probe empfiehlt sich ein großzügigerer failureThreshold, da ein Neustart die teuerste Konsequenz ist. Für die Readiness Probe darf die Schwelle niedriger liegen, weil das Entfernen aus der Rotation deutlich günstiger und schneller reversibel ist.

Ein weiterer wichtiger Parameter ist initialDelaySeconds, der ohne eine Startup Probe manuell auf die erwartete Boot-Zeit gesetzt werden müsste. Mit einer vorgeschalteten Startup Probe kann dieser Wert für Liveness und Readiness auf null gesetzt werden, weil die Startup Probe bereits sichergestellt hat, dass der Container fertig hochgefahren ist, bevor die regulären Probes überhaupt zu zählen beginnen. Diese Kombination reduziert Rätselraten bei der Wahl der Delay-Werte erheblich.


#!/usr/bin/env bash
# check-probes.sh — verify liveness and readiness endpoints locally
# before rolling the manifest out to the cluster
set -euo pipefail

BASE_URL="${1:-http://localhost:8080}"

echo "Checking liveness endpoint..."
curl -fsS -m 2 "${BASE_URL}/health/live" | grep -q '"status":"ok"' \
  && echo "[OK] liveness endpoint healthy" \
  || { echo "[FAIL] liveness endpoint unhealthy" >&2; exit 1; }

echo "Checking readiness endpoint..."
curl -fsS -m 3 "${BASE_URL}/health/ready" | grep -q '"database":true' \
  && echo "[OK] readiness endpoint healthy" \
  || { echo "[FAIL] readiness endpoint unhealthy" >&2; exit 1; }

# Inspect actual probe results reported by Kubernetes for a running pod
kubectl describe pod -l app=symfony-app | grep -A 3 "Liveness\|Readiness"

8. Häufige Fallstricke: Timeouts, Kaskaden und Fehlalarme

Der häufigste Fallstrick bei einem Health Check für Symfony ist ein zu kurzes Timeout in Kombination mit einem PHP-FPM-Pool, der unter Last bereits an seiner Kapazitätsgrenze arbeitet. Ist jeder FPM-Worker mit einer echten Anfrage beschäftigt, muss die Health-Anfrage in derselben Warteschlange auf einen freien Worker warten, was das Timeout reißen lässt, obwohl die Anwendung eigentlich funktioniert, nur überlastet ist. Ein separater, kleiner FPM-Pool ausschließlich für Health-Endpunkte kann dieses Problem entschärfen.

Eine zweite Falle ist die Kaskade: Prüft der Health Check eine Abhängigkeit, die selbst wieder von einer weiteren Abhängigkeit abhängt, etwa eine API, die intern eine andere Datenbank abfragt, kann ein Ausfall tief in der Kette dazu führen, dass reihenweise unabhängige Services als ungesund markiert werden. Health Checks sollten deshalb möglichst direkt prüfen, was der jeweilige Service selbst braucht, ohne transitive Abhängigkeiten weiterzureichen, die an anderer Stelle bereits eigene Probes haben.

9. Liveness, Readiness und Startup Probe im Vergleich

Die folgende Tabelle fasst zusammen, wofür jede Probe-Art in einem Symfony-Kubernetes-Setup zuständig ist und welche Konsequenz ein Fehlschlag jeweils hat.

Probe-Typ Prüft Bei Fehlschlag Externe Abhängigkeiten
Liveness Probe Prozess reagiert überhaupt noch Container wird gekillt und neugestartet niemals prüfen
Readiness Probe Anwendung kann Traffic sinnvoll bedienen Pod wird nur aus der Rotation genommen Datenbank, Cache, Queue prüfen
Startup Probe Anwendung ist fertig hochgefahren Liveness/Readiness starten noch nicht nicht relevant

Wer diese drei Probe-Typen korrekt trennt, vermeidet die zwei häufigsten Betriebsprobleme in Symfony-Kubernetes-Setups: unnötige Neustarts durch einen zu strengen Health Check und träge startende Pods, die in einer Neustart-Schleife feststecken. Der Aufwand für die saubere Trennung ist gering, der Effekt auf die Betriebsstabilität dagegen erheblich.

Mironsoft

Symfony DevOps, Kubernetes-Betrieb und Observability-Setup

Health Checks, die euren Symfony-Betrieb wirklich absichern?

Wir bauen belastbare Liveness-, Readiness- und Startup-Probes für Symfony in Kubernetes, inklusive dediziertem Health-Endpoint und sauber getrennten Abhängigkeits-Checks.

Probe-Audit

Bestehende Kubernetes-Manifeste auf Fehlkonfigurationen bei Health Checks prüfen

Endpoint-Implementierung

Dedizierten Health-Controller mit Datenbank-, Cache- und Queue-Checks bauen

Observability

Probe-Ergebnisse in Monitoring und Alerting integrieren

10. Zusammenfassung

Ein zuverlässiger Health Check für Symfony in Kubernetes trennt konsequent drei Fragen: Lebt der Prozess noch, kann die Anwendung gerade Traffic bedienen, und ist der Container überhaupt fertig gestartet. Die Liveness Probe bleibt bewusst minimal und prüft nie externe Abhängigkeiten, damit ein Datenbank-Hänger nicht zur Neustart-Kaskade wird. Die Readiness Probe prüft genau diese Abhängigkeiten und nimmt den Pod bei Bedarf sanft aus der Rotation, ohne ihn zu killen.

Die Startup Probe schützt langsam startende Symfony-Anwendungen davor, in einer endlosen Neustart-Schleife gefangen zu bleiben. Ein eigener, von Authentifizierung befreiter Endpoint mit engen Timeouts pro Abhängigkeit rundet ein robustes Health Check-Setup ab. Wer diese Trennung von Anfang an einhält, erspart sich nächtliche Alarme wegen Neustart-Kaskaden, die eigentlich nur ein kurzer, harmloser Netzwerk-Hänger ausgelöst hat.

Symfony Health Checks in Kubernetes — Das Wichtigste auf einen Blick

Liveness Probe

Nur den Prozesszustand prüfen. Nie externe Abhängigkeiten, sonst drohen unnötige Neustart-Kaskaden.

Readiness Probe

Datenbank, Cache und Queue prüfen. Fehlschlag entfernt den Pod nur aus der Rotation, kein Neustart.

Startup Probe

Für langsam startende Anwendungen Pflicht. Verhindert Neustart-Schleifen während des Boot-Vorgangs.

Eigener Endpoint

Ohne Authentifizierung, mit engen Timeouts pro Abhängigkeit, getrennt für Liveness und Readiness.

11. FAQ: Symfony Health Checks in Kubernetes

1Liveness vs. Readiness Probe?
Liveness entscheidet über Neustart und prüft nur den Prozess. Readiness entscheidet über Traffic und darf Abhängigkeiten einbeziehen.
2Liveness prüft Datenbank mit?
Nein. Sonst würde ein kurzer Datenbank-Hänger alle Pods gleichzeitig neustarten. Datenbank-Checks gehören in die Readiness Probe.
3Wann eine Startup Probe nutzen?
Bei mehreren Sekunden bis Minuten Startzeit. Ohne sie interpretiert Kubernetes langsamen Start als gescheiterte Liveness Probe.
4Wo liegt der Health Endpoint?
Eigener leichter Controller, frei von Authentifizierung, Session-Handling und CSRF-Schutz.
5Welche Abhängigkeiten in Readiness?
Datenbank per Query, Cache per Ping, optional Message Queue. Jeweils mit engem Timeout unter einer Sekunde.
6Was passiert bei Readiness-Fehlschlag?
Der Pod wird aus dem Service-Endpunkt entfernt, aber nicht gekillt. Er läuft weiter, bis die Probe wieder erfolgreich ist.
7Timeouts unter Last vermeiden?
Separater, kleiner FPM-Pool nur für Health-Endpunkte, damit Health-Anfragen nicht mit echten Anfragen um Worker konkurrieren.
8Was ist eine Kaskade?
Transitive Abhängigkeiten mit eigenen Probes markieren bei Ausfall reihenweise unabhängige Services als ungesund. Nur direkte Abhängigkeiten prüfen.
9initialDelaySeconds mit Startup Probe?
Auf null setzen, die Startup Probe übernimmt bereits die Absicherung der Boot-Zeit.
10Einzelner Abhängigkeits-Fehler = rot?
Nicht zwangsläufig. Je nach Kritikalität der Abhängigkeit kann interne Alarmierung sinnvoller sein als der Ausschluss aus der Rotation.