Ein Pod kann in kubectl get pods als Running erscheinen und trotzdem ein schlechtes Ziel für Traffic sein. Er kann auch in einer Neustart-Schleife hängen, weil Kubernetes den Container für tot hält, obwohl er nur langsam startet. Probes sind die Art, wie der kubelet diese beiden Fragen beantwortet.

Dieser Beitrag richtet sich an die Phase, in der Deployments und Services bereits Sinn ergeben — und Traffic trotzdem kaputte Instanzen trifft, Rollouts bei 0 of 3 updated hängen bleiben oder Container alle dreißig Sekunden ohne offensichtlichen Anwendungsfehler neu starten. Probes sind oft die Erklärung.

Sie sind keine Dekoration. Sie sind der Vertrag zwischen Anwendung und Plattform darüber, wann ein Pod Traffic verträgt und wann Eingreifen nötig ist.

Drei Fragen, drei Probentypen

Kubernetes trennt Health in drei Probes, weil die richtige Reaktion auf Fehler unterschiedlich ist:

  • Startup probe — Ist der Container mit dem Boot fertig? Solange Startup fehlschlägt, sind andere Probes deaktiviert. Sinnvoll bei langsamen Starts, damit Liveness sie nicht mitten im Boot killt.
  • Readiness probe — Soll dieser Pod jetzt Traffic bekommen? Bei Fehlschlag verschwindet er aus den Service-Endpoints. Der Container läuft weiter.
  • Liveness probe — Soll Kubernetes den Container neu starten? Fehlschlag löst Kill und Neustart aus.

Diese Unterscheidung zählt in der Praxis:

ProbeFehlschlag bedeutetTypische Reaktion
StartupBoot läuft nochWarten oder Boot-Pfad reparieren
ReadinessNicht sicher für TrafficRouting stoppen; App kann sich erholen
LivenessContainer hängt oder ist totNeustart

Ein typischer Anfängerfehler ist ein oberflächlicher HTTP-Check für alles. Der Prozess antwortet während des Starts auf /health, Liveness besteht, Readiness besteht — und Traffic kommt an, bevor der Datenbank-Pool bereit ist. Oder der Start ist langsam, Liveness greift ein, und der Pod wird nie stabil.

Eine Faustregel, die ich nutze:

  • Readiness soll ehrlich sagen, ob die Instanz jetzt sinnvolle Arbeit leisten kann.
  • Liveness soll konservativ sein — Neustart nur, wenn Erholung ohne Neustart unwahrscheinlich ist.
  • Startup braucht man, wenn Boot länger dauert, als man Liveness-Fehlschläge tolerieren will.

Wie Probes mit Services zusammenhängen

Ein Service prüft die Anwendung nicht von selbst auf Health. Er leitet an Pod-IPs weiter, die in EndpointSlices stehen. Welche Pods dort erscheinen, hängt von Readiness ab.

Schlägt Readiness fehl, fliegt die Pod-IP raus. Besteht sie, ist sie dabei. Bestehende Verbindungen können je nach Client und Plattform noch eine Weile laufen, neuer Traffic sollte aber nicht mehr auf diesen Pod zeigen.

Deshalb reicht 1/1 Running in kubectl get pods nicht. Die Spalte Ready prüfen:

kubectl get pods -l app=checkout -o wide
kubectl get endpoints checkout -o wide
kubectl get endpointslice -l kubernetes.io/service-name=checkout

Sind Endpoints leer, obwohl Pods laufen, schlägt Readiness fehl oder Selektoren passen nicht. Stehen Pods in den Endpoints, die unter Last 500er liefern, ist Readiness zu lasch.

Bei Rolling Updates entscheidet Readiness, wann Pods des neuen ReplicaSets als verfügbar gelten. Strenge Readiness bremst Deploys, schützt aber Nutzer. Lasse Readiness markiert Rollouts als erfolgreich, während Instanzen noch warm werden — dann steigen Fehlerraten, sobald Traffic umschaltet.

Graceful Shutdown passt hierher. Vor dem Exit sollte die App Readiness failen, damit Endpoints den Pod droppen, während laufende Requests fertig werden. Beendet sich der Prozess im Ready-Zustand, treffen Clients vielleicht noch auf eine schließende Verbindung.

Probe-Mechanismen: HTTP, TCP und exec

Alle drei Probentypen unterstützen httpGet, tcpSocket und exec. Die Wahl hängt davon ab, was die Anwendung wirklich anbietet.

HTTP-Probes

Am häufigsten bei Web-Services. Der kubelet sendet eine HTTP-Anfrage an Pfad und Port im Pod-Netzwerk-Namespace.

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3
livenessProbe:
  httpGet:
    path: /live
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 20
  timeoutSeconds: 3
  failureThreshold: 3
startupProbe:
  httpGet:
    path: /live
    port: 8080
  periodSeconds: 5
  failureThreshold: 30

Separate Endpunkte im Anwendungscode sind sinnvoll, wenn möglich. /ready kann die Datenbank prüfen. /live soll günstig sein — Prozess läuft, Event-Loop nicht blockiert. Beide auf / zu legen, das immer 200 liefert, lehrt Kubernetes nichts.

HTTP-Probes unterstützen Custom-Header und HTTPS. Der Port muss zu dem passen, worauf der Container lauscht — nicht zwingend zum Service-targetPort, falls die abweichen.

TCP-Probes

Der kubelet baut eine TCP-Verbindung zum Port auf. Erfolg heißt: etwas hat die Verbindung angenommen.

readinessProbe:
  tcpSocket:
    port: 5432
  periodSeconds: 10

TCP-Probes helfen, wenn es keinen HTTP-Endpunkt gibt — Datenbanken, Message Broker, Legacy-Services. Grenze: ein offener Port heißt nicht, dass die Anwendung bereit ist. PostgreSQL nimmt vielleicht Verbindungen an, während es noch recovered. HTTP oder exec bevorzugen, wenn sich echte Readiness ausdrücken lässt.

Exec-Probes

Der kubelet führt einen Befehl im Container aus. Exit-Code 0 bedeutet Erfolg.

livenessProbe:
  exec:
    command:
      - /bin/sh
      - -c
      - pg_isready -U app -d checkout
  periodSeconds: 15

Exec-Probes sind flexibel, aber schwerer. Das Binary muss im Image existieren, unter Last korrekt reagieren und innerhalb von timeoutSeconds fertig werden. Eine langsame Exec-Probe unter CPU-Druck kann selbst Fehlschläge auslösen.

Ein vollständiges Deployment-Beispiel

So sieht ein Muster aus, das ich bei stateless HTTP-Services mit langsamem JVM-Boot oder ähnlichem nutze:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout
spec:
  replicas: 3
  selector:
    matchLabels:
      app: checkout
  template:
    metadata:
      labels:
        app: checkout
    spec:
      containers:
        - name: checkout
          image: ghcr.io/example/checkout:2.1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /live
              port: 8080
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /live
              port: 8080
            periodSeconds: 5
            failureThreshold: 24

Mit periodSeconds: 5 und failureThreshold: 24 erlaubt Startup grob zwei Minuten, bevor Liveness greift. Werte aus gemessenem Boot in Staging ableiten, nicht schätzen.

Dazu ein Service mit denselben Labels. Readiness auf Pod-Seite und Selektoren auf Service-Seite sind beide nötig für gesundes Routing.

apiVersion: v1
kind: Service
metadata:
  name: checkout
spec:
  selector:
    app: checkout
  ports:
    - port: 80
      targetPort: 8080

Felder beim Tuning, die wirklich zählen

Probe-Specs wirken klein. Diese Felder verursachen die meisten Überraschungen in Produktion:

  • initialDelaySeconds — Wartezeit vor der ersten Probe. Startup-Probes reduzieren die Abhängigkeit von großen Werten, die Fehlererkennung verzögern.
  • periodSeconds — Wie oft probiert wird. Zu aggressiv erzeugt Last; zu langsam verzögert das Entfernen aus Endpoints.
  • timeoutSeconds — Maximale Wartezeit pro Probe. Muss unter periodSeconds liegen. Unter Last führen kurze Timeouts zu Fehlalarmen.
  • successThreshold — Aufeinanderfolgende Erfolge nach Fehlschlag. Readiness defaultet auf 1; höhere Werte glätten Flapping, verlangsamen aber die Rückkehr in Rotation.
  • failureThreshold — Aufeinanderfolgende Fehlschläge bis zum Fail. Multipliziert mit periodSeconds ergibt sich grob die Zeit bis zur Reaktion.

Bei Readiness entfernt failureThreshold: 1 einen Pod schnell aus Endpoints — gut für Nutzer, hart für Apps mit kurzen Stalls. Bei Liveness verursachen niedrige Schwellwerte plus aggressive Perioden Neustart-Schleifen.

Unter realistischer Last testen, nicht auf einem idle Laptop. GC-Pausen, erschöpfte Connection-Pools und kurze Dependency-Ausfälle zeigen sich erst bei Traffic, der Produktion ähnelt.

Häufige Fehler

Liveness für langsamen Start. Ohne Startup-Probe reicht größeres initialDelaySeconds nur bedingt. Boot-Zeit ändert sich mit Datenmenge und Cache. Startup-Probe ist die sauberere Lösung.

Readiness und Liveness auf demselben teuren Check. Wenn /ready die Datenbank trifft und Liveness denselben Pfad nutzt, startet ein DB-Ausfall alle Pods neu statt sie nur aus dem Traffic zu nehmen. Liveness günstig halten.

Falscher Port oder Pfad. Probes laufen im Pod-Netzwerk-Namespace gegen den Container-Port. Ein Service-Port 80 spielt hier keine Rolle. Ein Tippfehler im Pfad liefert Verbindungsfehler und failt sofort.

Gar keine Readiness-Probe. Kubernetes markiert den Pod vielleicht als ready, sobald der Container startet. Traffic kommt an, bevor die App lauscht oder Migrationen fertig sind.

Probes, die immer bestehen. Statisches 200 auf /, während Worker nicht konsumieren, Migrationen laufen oder echte Routes Fehler liefern — Readiness lügt, Deploys wirken grün, Nutzer sehen Ausfälle.

timeoutSeconds bei Rollouts ignorieren. Eine Probe, die lokal besteht, aber unter Last timeoutet, entfernt Pods in Spitzenzeiten aus Endpoints.

Graceful Shutdown vergessen. Bei SIGTERM zuerst Readiness failen, kurz warten, dann drainen. Ohne das schicken Load Balancer und EndpointSlices vielleicht noch Requests an einen sterbenden Pod.

Probe-Fehler debuggen

Wenn Pods neu starten oder Not Ready bleiben, mit Events und describe anfangen:

kubectl describe pod checkout-7b9d4c8d6f-q2xk9
kubectl get events --field-selector involvedObject.name=checkout-7b9d4c8d6f-q2xk9
kubectl logs checkout-7b9d4c8d6f-q2xk9 --previous

In Events nach Liveness probe failed, Readiness probe failed oder Startup probe failed suchen. Die Meldung enthält oft HTTP-Status oder connection refused — das grenzt die Ursache schnell ein.

In den Pod gehen und die Probe manuell ausführen:

kubectl exec -it checkout-7b9d4c8d6f-q2xk9 -- curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/ready

Bestehen manuelle Checks, Probes aber nicht, verdächtigen: Timeouts, falsches Scheme (HTTP vs HTTPS), oder die App lauscht nur auf 127.0.0.1, während die Probe den Container-Port auf allen Interfaces erwartet.

Bei Neustart-Schleifen prüfen, ob Liveness vor Ende von Startup feuert. Liveness vorübergehend in Staging deaktivieren, Boot-Zeit messen, dann Startup-failureThreshold mit Reserve setzen.

Wann einfache Defaults reichen

Nicht jeder Workload braucht am ersten Tag drei Custom-Probes. Ein einfaches internes Tool mit schnellem Boot und toleranten Nutzern kann mit Readiness allein starten:

readinessProbe:
  httpGet:
    path: /
    port: 8080
  initialDelaySeconds: 3
  periodSeconds: 5

Liveness ergänzen, wenn es Belege für hängende Prozesse gibt, die nicht von selbst beenden. Startup ergänzen, wenn Boot grob über dreißig Sekunden dauert oder stark variiert, sodass Liveness Pods mitten im Start killt.

Plattform-Teams injizieren manchmal Defaults per mutating admission. Prüfen, was der Cluster hinzufügt. Anwendungseigene Endpunkte schlagen generische TCP-Checks auf dem Hauptport.

Abschluss

Probes sind die Brücke zwischen „Container gestartet“ und „diese Instanz soll am Service teilnehmen“. Sie lassen sich leicht aus Tutorials kopieren und sind schwer ehrlich zu machen.

Ich behandle Probe-Änderungen weiterhin wie kleine Vertragsänderungen: Boot-Zeit messen, definieren was ready für diese App heißt, Liveness günstiger als Readiness halten — und Endpoints während eines Staging-Rollouts beobachten, bevor grüner Status in Produktion vertraut wird.

Wenn Probes die Wahrheit sagen, wirken Deploys auf die gute Art langweilig — EndpointSlices aktualisieren sich, Traffic schaltet um, und niemand debuggt mysteriöse Teilausfälle, die wie Load-Balancer-Geister aussehen. Lügen sie oder kämpfen sie mit langsamem Start, sieht die Pod-Spalte gut aus, während Nutzer es zuerst merken. Diese Lücke lohnt sich früh zu schließen.