Liveness, Readiness und Startup Probe in Kubernetes

1. Übersicht

Wenn du eine Anwendung auf Kubernetes ausführst, weiß der Cluster nicht automatisch, ob dein Pod „am Leben” ist oder „bereit für Traffic” — er muss dies über Probes abfragen. Dieser Artikel erklärt die Unterschiede zwischen Liveness-, Readiness- und Startup-Probe, zusammen mit praktischen Beispielen für Spring Boot Actuator und Deployment YAML. Nach dem Lesen kannst du Probes korrekt für einen echten Service wie OrderService konfigurieren, ohne dass Pods unnötig neu gestartet werden.

2. Warum Probes notwendig sind

Standardmäßig weiß Kubernetes nur, ob der Prozess im Container läuft (Container-Status Running) oder nicht. Es weiß nicht, ob die Anwendung darin Deadlock-Zustände hat, die Datenbankverbindung verloren hat oder noch hochfährt.

Die Folgen ohne Probes:

  • Pod bleibt hängen (Deadlock), empfängt aber immer noch Traffic → zahlreiche Request-Timeouts.
  • Pod startet gerade, hat die DB-Verbindung noch nicht hergestellt, wird aber bereits von Service als Endpoint hinzugefügt → 500-Fehler.

Drei Probe-Typen lösen drei verschiedene Probleme:

Probe Beantwortet die Frage Im Fehlerfall
Liveness Läuft der Container noch? Kubernetes startet den Container neu
Readiness Ist der Container bereit für Traffic? Kubernetes entfernt den Pod aus Service-Endpoints (kein Neustart)
Startup Ist der Container hochgefahren? Kubernetes wartet, deaktiviert Liveness/Readiness vorübergehend

3. Liveness Probe

Liveness Probe beantwortet die Frage: „Befindet sich dieser Prozess in einem Zustand, aus dem er sich nicht selbst erholen kann?“. Falls die Probe mehr als failureThreshold mal fehlschlägt, startet kubelet den Container neu.

3.1. Spring Boot Actuator hinzufügen

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info

Die Zeile management.endpoint.health.probes.enabled=true aktiviert zwei Unter-Endpoints: /actuator/health/liveness und /actuator/health/readiness, genau wie Kubernetes es erwartet.

3.2. Im Deployment deklarieren

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
            failureThreshold: 3
  • initialDelaySeconds: 20: Warte 20 Sekunden nach dem Start des Containers, bevor die erste Probe läuft.
  • periodSeconds: 10: Probe alle 10 Sekunden.
  • failureThreshold: 3: Nach 3 aufeinanderfolgenden Fehlschlägen gilt der Container als defekt → Neustart.

Wichtig: Liveness Probe sollte nur überprüfen, „läuft die Anwendung noch?“, nicht externe Abhängigkeiten (DB, Redis…). Wenn die DB ausfällt und Liveness dadurch fehlschlägt, wird Kubernetes den Pod ständig neu starten, ohne das eigentliche Problem zu lösen — das ist ein sehr häufiger Fehler.

4. Readiness Probe

Readiness Probe beantwortet: „Kann der Pod Traffic verarbeiten?“. Bei Fehlschlag wird der Pod aus den Endpoints des Service entfernt — Traffic gelangt nicht mehr hin, der Container wird aber nicht neu gestartet.

4.1. Benutzerdefinierter Readiness-Indikator

Angenommen, OrderService muss sich erst mit PaymentGateway verbinden, bevor er bereit ist:

@Component
public class PaymentGatewayReadinessIndicator implements HealthIndicator {

    private final PaymentGatewayClient paymentGatewayClient;

    public PaymentGatewayReadinessIndicator(PaymentGatewayClient paymentGatewayClient) {
        this.paymentGatewayClient = paymentGatewayClient;
    }

    @Override
    public Health health() {
        if (paymentGatewayClient.isConnected()) {
            return Health.up().withDetail("gateway", "connected").build();
        }
        return Health.down().withDetail("gateway", "unreachable").build();
    }
}

Um diesen Indikator in die readiness-Gruppe einzubeziehen (ohne Liveness zu beeinflussen), definiere eine Gruppe:

management.endpoint.health.group.readiness.include=readinessState,paymentGateway
  • health() gibt Health.down() zurück, wenn sich PaymentGatewayClient nicht verbinden kann — die Readiness-Probe schlägt fehl, der Pod erhält keinen Traffic, bis das Gateway bereit ist.
  • Spring nennt automatisch das Bean paymentGatewayReadinessIndicator im Health-Response um zu paymentGateway.

4.2. Test für den Indikator

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <scope>test</scope>
</dependency>
class PaymentGatewayReadinessIndicatorTest {

    @Test
    void shouldReportDownWhenGatewayNotConnected() {
        PaymentGatewayClient client = mock(PaymentGatewayClient.class);
        when(client.isConnected()).thenReturn(false);

        PaymentGatewayReadinessIndicator indicator = new PaymentGatewayReadinessIndicator(client);
        Health health = indicator.health();

        assertThat(health.getStatus()).isEqualTo(Status.DOWN);
        assertThat(health.getDetails()).containsEntry("gateway", "unreachable");
    }
}
  • mock(PaymentGatewayClient.class) simuliert die externe Abhängigkeit, ohne echte Verbindung.
  • assertThat(health.getStatus()) bestätigt, dass der Status DOWN ist, wenn das Gateway nicht verbunden ist — das erwartete Verhalten der Readiness.

4.3. Readiness im Deployment deklarieren

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3
  successThreshold: 1
  • successThreshold: 1: Nur eine erfolgreiche Probe reicht, um wieder als bereit zu gelten (bei Liveness brauchst du das normalerweise nicht setzen, da der Default bereits 1 ist).
  • periodSeconds: 5 ist kürzer als bei Liveness, da Readiness schnell auf Änderungen in Abhängigkeiten reagieren muss.

5. Startup Probe

Bei Anwendungen mit langsamen Starts (große Caches laden, DB-Migrationen) ist ein festes initialDelaySeconds riskant — du musst eine Zahl raten, die groß genug ist, und verschwendest Zeit bei jedem späteren Neustart.

Startup Probe löst das: Während die Startup-Probe noch nicht erfolgreich war, deaktiviert Kubernetes temporär die Liveness- und Readiness-Probes und verhindert so, dass die App während des legitimen Starts unnötig beendet wird.

startupProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10
  failureThreshold: 3
  • startupProbe nutzt failureThreshold: 30 und periodSeconds: 10 → erlaubt maximal 300 Sekunden (30 × 10s) für den Start vor Timeout.
  • Nach dem ersten erfolgreichen startupProbe schaltet Kubernetes auf livenessProbe mit kleinerer Schwelle um (3 × 10s = 30s) — reagiert schnell auf echte Fehler im stabilen Betrieb.

Hinweis: Wenn deine Anwendung schnell startet (unter wenigen Sekunden, wie eine einfache REST API ohne große Cache-Laden), brauchst du startupProbe nicht — es macht das YAML nur länger, ohne echten Nutzen.

6. Alle drei in einem Deployment kombinieren

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            failureThreshold: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 5
            failureThreshold: 3

Ablauf, wenn ein neuer Pod erstellt wird:

  1. Container startet, Kubernetes beginnt sofort startupProbe zu rufen (kein initialDelaySeconds, Startup verwaltet das selbst).
  2. Währenddessen laufen livenessProbe und readinessProbe nicht.
  3. Wenn startupProbe zum ersten Mal HTTP 200 antwortet, stoppt Kubernetes die Startup-Probe und startet Liveness und Readiness parallel.
  4. Readiness schlägt fehl → Pod wird aus Service-Endpoints entfernt, kein Traffic, Container läuft aber.
  5. Liveness schlägt fehl → Container wird beendet, Kubernetes erstellt einen neuen (im selben Pod), Ablauf beginnt neu bei Schritt 1.

Prüfe den realen Probe-Status mit kubectl describe pod:

kubectl describe pod order-service-7d9f8c6b5-xk2pq
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True

Events:
  Type     Reason     Age   From     Message
  Warning  Unhealthy  2m    kubelet  Readiness probe failed: HTTP probe failed with statuscode: 503

Die Zeile Readiness probe failed zeigt, dass Readiness derzeit fehlschlägt — prüfe das Anwendungs-Log oder rufe /actuator/health/readiness direkt auf, um zu sehen, welche Details DOWN sind.

7. Häufige Fehler

  • Denselben Endpoint für Liveness und Readiness verwenden: Wenn externe Abhängigkeiten (DB, Cache) ausfallen, schlagen beide Probes fehl → Pod wird ständig neu gestartet (Crash Loop), obwohl die Anwendung selbst noch läuft. Nutze /actuator/health/liveness nur für interne Checks und /actuator/health/readiness für Abhängigkeiten.
  • initialDelaySeconds zu klein für langsamen App-Start: Liveness schlägt fehl, während die App noch hochfährt, verursacht endlose Restart-Loops. Lösung: Nutze startupProbe statt willkürliche initialDelaySeconds.
  • periodSeconds × failureThreshold zu groß: Reale Fehler werden erst nach Minuten erkannt, beeinflusst SLA.
  • Readiness-Probe ruft einen unwichtigen Downstream-Service auf: Z.B. einen Logging-Service — fällt dieser aus, wird OrderService fälschlicherweise als not-ready markiert, obwohl die Kernfunktion noch funktioniert.

8. Fazit

Die drei Probe-Typen erfüllen drei unterschiedliche Aufgaben: startupProbe schützt die Anwendung in der langsamen Startphase, livenessProbe erkennt Deadlocks, damit Kubernetes neu startet, und readinessProbe steuert Traffic basierend auf echtem Dependency-Status. Das wichtigste Prinzip: Lass die Liveness-Probe nicht von externen Systemen abhängen — das verwandelt einen Fehler eines Downstream-Service in einen Crash Loop deines eigenen Service. Mit den drei korrekt konfigurierten Probes heilt dein Cluster sich selbst eleganter, und die Downtime bei Deployments oder Infrastruktur-Ausfällen sinkt deutlich.

kubernetesspring-bootdevopshealth-checkmicroservices