← Übersicht  ·  Skripte & Dateien  ·  cloud-host · OpenShift · Workloads

Service, Route und Probes in OpenShift

Die Zeichnungen dazu stehen hier bei den jeweiligen Schritten. Hier spielst du den ganzen Weg von Hand durch: ein Service entsteht, DNS löst ihn auf, eine Route macht ihn von außen erreichbar – und eine kaputte Probe wirft einen Pod live aus dem Verkehr, ohne dass er stirbt. EX280-relevant.

Stand: 4. September 2026 OKD 4.x / OCP 4.x ns library

Voraussetzung: ein Cluster mit oc login, Namespace library (oc new-project library oder oc project library). Die Demos laufen mit einem einfachen httpd-Image, damit nichts vom Bibliothek-Stack abhängt.

1 Das Service-Modell

Ein Service ist ein fester Name plus feste ClusterIP vor einer wechselnden Menge Pods – das Objekt bleibt, die Pods dahinter nicht.

Pods kommen und gehen, ihre IPs auch. Ein Service legt einen festen Namen und eine feste IP davor. Ein Controller pflegt im Hintergrund die Liste der bereiten Pods dahinter, das Cluster-DNS löst den Namen auf, und OVN biegt jedes Paket auf eine dieser Pod-IPs um – hinter der Service-IP steckt kein Prozess.

Fester Name, feste IP – dahinter eine lebende Liste bereiter Pods. Pod (api-gateway) die App öffnet connect catalog:8080 nur ein Name, keine IP bekannt Cluster-DNS (CoreDNS) Deployment in openshift-dns, vom DNS-Operator löst catalog.library.svc.cluster.local 172.30.42.7 (die ClusterIP) jeder Pod: /etc/resolv.conf zeigt hierher, mit Suchliste Service catalog du legst an (oder Helm) · Objekt bleibt, Pods wechseln clusterIP: 172.30.42.7 – virtuell, stabil port: 8080targetPort: 8080 selector: app=catalog – wer gehört dazu hinter der ClusterIP läuft kein Prozess die interne API ist auch nur ein Service: kubernetes.default.svc EndpointSlice wird generiert vom endpoints-Controller – nicht anfassen 10.128.2.11:8080 ready 10.128.2.12:8080 ready 10.128.3.9 not ready – raus Pods mit app=catalog und grüner Readiness-Probe (B5) OVN-Kubernetes programmiert die Nodes-Datapath (früher kube-proxy) DNAT: ClusterIP 172.30.42.7 → eine ready Pod-IP round-robin, Zielliste aus der EndpointSlice der Pod merkt nichts – er redet scheinbar mit der ClusterIP Overlay-Details: F1 Pods catalog-* 10.128.2.11 READY – in der Slice 10.128.2.12 READY – in der Slice 10.128.3.9 NOT READY – rausgenommen Pod-IPs sind flüchtig – jeder Neustart eine neue darum nie eine Pod-IP fest verdrahten wird ein Pod not-ready, fällt neuer Verkehr weg, bestehende Verbindungen laufen aus Headless-Service: DNS gibt diese IPs direkt zurück, eine A-Record je Pod (StatefulSet) 1 · Pod fragt das DNS: wo ist catalog? 2 · verbindet zu 172.30.42.7:8080 füllt die Zielliste 3 · DNAT auf eine ready Pod-IP 4 · der Controller beobachtet die Pods und pflegt die Slice Service-Typen ClusterIP (Vorgabe) – nur clusterintern erreichbar NodePort – zusätzlich ein hoher Port (30000–32767) auf jedem Node LoadBalancer – fragt Cloud / MetalLB nach einer externen IP (Hetzner: MetalLB) ExternalName – CNAME auf einen externen Namen, kein Proxying Headless (clusterIP: None) – keine virtuelle IP, DNS gibt die Pod-IPs direkt sessionAffinity: None (round-robin je Verbindung) oder ClientIP (klebt an der Quell-IP) DNS-Kurzformen: catalog (selber Namespace), catalog.library (übergreifend), voll ….svc.cluster.local Named Ports: targetPort: http statt einer Nummer – der Container benennt den Port prüfen und die vier Klassiker oc describe svc catalog → die Zeile Endpoints. Leer = Selector passt nicht ODER kein Pod ready oc get endpointslices -l kubernetes.io/service-name=catalog oc rsh <pod> -- curl catalog:8080 1 · selector ≠ Pod-Labels → Endpoints leer → connection refused / timeout 2 · falscher targetPort → Verbindung hängt oder wird abgewiesen 3 · Pod flappt not-ready → sporadische 502 an der Route (B4) 4 · falscher Namespace → catalog löst ins Leere, catalog.library nehmen oc get svc · oc get endpoints (alte, aggregierte Sicht) Bibliothek: jeder der 8 Dienste ist ein ClusterIP-Service auf 8080. Die Configs verdrahten http://catalog-service:8080fullnameOverride (B11) muss den Namen exakt treffen ↗ von außen erreichbar erst über eine Route oder ein Ingress (B4) – der Service allein hört nur im Cluster ↗ Merksatz: Service = stabiler Name + IP, EndpointSlice = wer gerade bereit ist, OVN = biegt das Paket um OCP-Unterschied: keiner – der DNS-Operator verwaltet CoreDNS, der Rest ist reines Kubernetes. EndpointSlice löst das alte einzelne Endpoints-Objekt ab.
Ein Name, der immer trifft. Der Service hält einen festen Namen und eine feste clusterIP. Das Cluster-DNS löst den Namen zur ClusterIP auf, der endpoints-Controller schreibt die IPs der bereiten Pods in eine EndpointSlice, und OVN macht bei jedem Paket ein DNAT von der ClusterIP auf eine davon. Hinter der ClusterIP sitzt kein Prozess – sie ist eine Fiktion, die der Node-Datapath umschreibt. Von außen kommt man erst mit einer Route (B4) rein.
  • ein Service ist ein fester Name plus feste clusterIP vor einer wechselnden Menge Pods – das Objekt bleibt, die Pods dahinter nicht
  • der selector (Labels) sagt, welche Pods dazugehören – der endpoints-Controller pflegt daraus die Liste
  • die EndpointSlice listet die bereiten Pod-IPs – nur Pods mit passendem Label und grüner Readiness-Probe (B5). Sie löst das alte Endpoints-Objekt ab
  • hinter der ClusterIP läuft kein Prozess – OVN-Kubernetes (früher kube-proxy) programmiert die Nodes-Datapath und macht DNAT auf eine ready Pod-IP, round-robin
  • das Cluster-DNS (CoreDNS) löst <svc>.<ns>.svc.cluster.local zur ClusterIP auf, kurz <svc> im selben Namespace, <svc>.<ns> übergreifend
  • Service-Typen: ClusterIP (Vorgabe, clusterintern), NodePort (+ hoher Port je Node), LoadBalancer (externe IP via Cloud/MetalLB), ExternalName (CNAME), Headless (clusterIP: None, DNS gibt Pod-IPs direkt)
  • port ist der Service-Port, targetPort der Container-Port – named ports gehen auch
  • sessionAffinity: None (round-robin je Verbindung) oder ClientIP (klebrig nach Quell-IP, mit Timeout)
  • die interne API ist selbst nur ein Service: kubernetes.default.svc
  • prüfen: oc describe svc catalog (Zeile Endpoints leer = Selector-Mismatch oder kein Pod ready), oc get endpointslices -l kubernetes.io/service-name=catalog
  • Fallen: Selector ≠ Pod-Labels, falscher targetPort, flappender Pod → sporadische 502 an der Route, falscher Namespace angenommen
  • Bibliothek: jeder der 8 Dienste ist ein ClusterIP-Service auf 8080api-gateway ruft die Fachdienste über http://catalog-service:8080 usw. auf. Der Name kommt aus fullnameOverride im Helm-Chart (B11) und muss exakt passen, sonst geht die Auflösung ins Leere
  • von außen erst über Route/Ingress (B4) – der Service allein hört nur im Cluster
  • OCP-Unterschied: keiner – der DNS-Operator verwaltet CoreDNS, sonst reines Kubernetes
Typist
ClusterIPVorgabe, nur clusterintern erreichbar
NodePortClusterIP plus ein hoher Port auf jedem Node
LoadBalancerexterne IP über Cloud-Provider oder MetalLB
ExternalNamereiner DNS-CNAME auf einen externen Namen
Headless (clusterIP: None)DNS gibt die Pod-IPs direkt zurück, keine ClusterIP

Der selector (ein Label-Ausdruck) entscheidet, welche Pods dazugehören – der Endpoints-Controller pflegt daraus die EndpointSlice: die Liste der bereiten Pod-IPs, nur Pods mit passendem Label und grüner Readiness-Probe (Schritt 6). Hinter der ClusterIP läuft kein Prozess – OVN-Kubernetes programmiert den Node-Datapath und macht DNAT auf eine ready Pod-IP, round-robin.

2 Service anlegen, per DNS erreichen

Ein Deployment mit zwei Replicas, ein Service davor, und der DNS-Name aus einem dritten Pod heraus getestet.

Laptop · Bash
oc create deployment demo-web -n library --image=registry.access.redhat.com/ubi9/httpd-24 --replicas=2
oc expose deployment demo-web -n library --port=8080 --target-port=8080
oc get svc demo-web -n library

# von einem Test-Pod im selben Namespace aufloesen:
oc run dns-test -n library --rm -it --restart=Never \
  --image=registry.access.redhat.com/ubi9/ubi-minimal -- \
  curl -s -o /dev/null -w "%{http_code}\n" http://demo-web:8080/

# der vollqualifizierte Name geht auch namespaceuebergreifend:
oc run dns-test -n library --rm -it --restart=Never \
  --image=registry.access.redhat.com/ubi9/ubi-minimal -- \
  getent hosts demo-web.library.svc.cluster.local

Kurz demo-web löst nur im selben Namespace auf, demo-web.library namespaceübergreifend, und demo-web.library.svc.cluster.local ist der volle Name, den CoreDNS auflöst. port ist der Service-Port, targetPort der Container-Port – hier gleich, müssen es aber nicht sein.

3 Endpoints live brechen

Der häufigste Service-Fehler: der selector trifft keine Pods mehr.

Laptop · Bash
# Ist-Zustand: Endpoints gefuellt
oc get endpointslices -n library -l kubernetes.io/service-name=demo-web
oc describe svc demo-web -n library | grep Endpoints

# den Selector kaputt machen (Tippfehler nachstellen):
oc patch svc demo-web -n library --type=merge -p '{"spec":{"selector":{"app":"demo-web-typo"}}}'
oc describe svc demo-web -n library | grep Endpoints
# -> Endpoints: 

# von aussen (Route/curl) kommt jetzt 503 statt einer Antwort - der Router
# hat niemanden, an den er weiterreichen kann

# zurueck:
oc patch svc demo-web -n library --type=merge -p '{"spec":{"selector":{"app":"demo-web"}}}'
oc describe svc demo-web -n library | grep Endpoints

Fallenselector ≠ Pod-Labels, falscher targetPort, oder ein flappender Pod (Schritt 7) → sporadische 502/503 an der Route. Erster Griff: oc describe svc <name> – Zeile Endpoints leer heißt Selector-Mismatch oder kein Pod ready.

4 Eine Route anlegen

Eine Route ist ein OpenShift-Objekt: Hostname → Service, mit Port, TLS-Modus und optional einem Pfad. Kubernetes-Pendant ist Ingress (F3).

Ein Service hört nur im Cluster (B3). Eine Route macht ihn von außen erreichbar: sie sagt welcher Hostname auf welchen Service zeigt und wie TLS behandelt wird. Der Router (HAProxy als Pod) hält die öffentlichen Ports 80 und 443 und setzt jede Route in HAProxy-Konfig um.

Ein Hostname, ein Router, drei Arten mit TLS umzugehen. Browser library-frontend .apps.<cluster> HTTPS auf Port 443 Wildcard-DNS *.apps.<cluster> ein A-Record für alle Routes, zeigt auf die Router-Nodes / den LB du legst ihn einmal an Router (HAProxy) Pods router-default im Namespace openshift-ingress hält :80 und :443 auf den Nodes (hostNetwork) liest alle Route-Objekte clusterweit schreibt daraus die HAProxy-Konfig, live bringt ein Default-Wildcard-Zertifikat mit für *.apps.<cluster> (auf OKD/SNO selbstsigniert) → eine edge-Route braucht kein eigenes Zertifikat, kein Let's Encrypt Route library-frontend du legst an (oder Helm) · oc expose / oc create route host + to.service + port + tls.termination + path Status Admitted: True = der Router hat sie angenommen weiter wie B3 Router → Service (ClusterIP) → bereite Pods Service ohne Endpoints → der Router antwortet 503 Pod not-ready → fällt aus der Rotation (B5) Sharding: mehrere IngressController mit routeSelector (intern / extern) der Router liest die Route edge – TLS endet am Router Router entschlüsselt, dahinter HTTP zum Pod Router setzt X-Forwarded-Proto: https, X-Forwarded-For, Forwarded Pfad-Routing möglich (spec.path: /api) Keycloak-Falle: der Pod sieht HTTP und baut http://-Redirects – aktuelle Images: KC_PROXY_HEADERS=xforwarded in der Bibliothek der Normalfall für alle Web-Routes Spring: server.forward-headers-strategy=framework damit die App die echte URL rekonstruiert passthrough – Router fässt TLS nicht an die verschlüsselte Verbindung geht direkt zum Pod der Pod hält das Zertifikat, nicht der Router nötig für Client-Zertifikate / mTLS kein Pfad-Routing, keine Header, nur SNI-Host Router macht Routing rein über den SNI-Hostnamen Beispiel: eine gRPC- oder mTLS-Backend-Route Diagramm F4 (Service Mesh) macht mTLS anders – im Sidecar oc create route passthrough --service=grpc-svc reencrypt – zweimal TLS Router entschlüsselt und baut eine neue TLS-Verbindung zum Pod braucht destinationCACertificate Ende-zu-Ende verschlüsselt und trotzdem L7-Routing der Router kann Header setzen und Pfade routen passt zu einem Pod mit serving-cert (F5) der service-ca-Operator gibt dem Pod ein internes Zertifikat eigenes Public-Zertifikat: route.spec.tls.certificate / .key inline, oder ein Secret via cert-manager Optionen als Annotationen: haproxy.router.openshift.io/timeout: 30s, balance, rate-limit, ip_whitelist, disable_cookies oc expose svc/library-frontend erzeugt eine reine HTTP-Route oc create route edge --service=library-frontend --hostname=... Ingress statt Route: OpenShift übersetzt ein Ingress automatisch in eine Route (portabel bleiben, F3) Bibliothek: nur library-frontend und api-gateway haben eine Route (edge) – die anderen 6 Dienste reden nur clusterintern Merksatz: Route = Hostname + Service + TLS-Modus. Router = ein HAProxy, der alle Routes kennt. OCP-Unterschied: dort kann der Ingress-Operator das Cluster-Wildcard von einer echten CA holen lassen – auf OKD/SNO bleibt es selbstsigniert.
Von außen rein – über genau einen HAProxy. Das Wildcard-DNS *.apps.<cluster> zeigt auf den Router. Der liest alle Route-Objekte und baut daraus seine HAProxy-Konfig. Die Route sagt: dieser Hostname geht auf diesen Service, und so wird TLS behandelt – edge (endet am Router), passthrough (Pod hält das Zertifikat), reencrypt (zweimal TLS). Dahinter geht es weiter wie in B3.
  • eine Route ist ein OpenShift-Objekt: Hostname → Service, mit Port, TLS-Modus und optional einem Pfad – Kubernetes-Pendant ist Ingress
  • der Router (HAProxy als Pod) hält Port 80 und 443 auf den Nodes, liest alle Routes clusterweit und schreibt daraus die HAProxy-Konfig
  • das Wildcard-DNS *.apps.<cluster> ist ein einziger A-Record, der auf den Router zeigt – du legst ihn einmal an
  • der Router bringt ein Default-Wildcard-Zertifikat für *.apps.<cluster> mit – darum braucht eine edge-Route kein eigenes Zertifikat (auf OKD/SNO selbstsigniert)
  • edge: TLS endet am Router, dahinter HTTP zum Pod – der Router setzt X-Forwarded-*, Pfad-Routing geht
  • passthrough: der Router fässt TLS nicht an, der Pod hält das Zertifikat – nötig für Client-Zertifikate, dafür kein Pfad-Routing und keine Header
  • reencrypt: Router terminiert und baut eine neue TLS-Verbindung zum Pod (mit destinationCACertificate) – Ende-zu-Ende und L7-Routing
  • eigenes Zertifikat: inline in route.spec.tls.certificate/.key, oder ein Secret über cert-manager
  • Keycloak-Falle: bei edge sieht der Pod HTTP und baut http://-Redirects. Aktuelle Images brauchen KC_PROXY_HEADERS=xforwarded plus passende KC_HOSTNAME/KC_HTTP_ENABLED=true, alte Images KC_PROXY=edge. Bei Spring hilft server.forward-headers-strategy
  • anlegen: oc expose svc/library-frontend (reine HTTP-Route), oc create route edge --service=library-frontend --hostname=...
  • Route-Status Admitted: True = der Router hat sie angenommen. HostAlreadyClaimed wenn zwei Routes denselben Host wollen
  • ein Service ohne Endpoints → der Router antwortet 503. Ein flappender Pod → sporadische 502 (B5)
  • OpenShift übersetzt ein Ingress automatisch in eine Route – wer portabel bleiben will, schreibt Ingress (F3)
  • Bibliothek: drei edge-Routes – library-frontend, api-gateway und keycloak unter *.apps.sno.<ip>.nip.io, in der Jenkins-Prod-Stage per oc expose angelegt. Die anderen 6 Dienste sind rein clusterintern
Laptop · Bash
# reine HTTP-Route:
oc expose svc/demo-web -n library
oc get route demo-web -n library

# oder gleich verschluesselt:
oc create route edge demo-web-tls --service=demo-web -n library
oc get route demo-web-tls -n library -o jsonpath='{.spec.host}{"\n"}'

curl -s -o /dev/null -w "%{http_code}\n" http://$(oc get route demo-web -n library -o jsonpath='{.spec.host}')/
curl -sk -o /dev/null -w "%{http_code}\n" https://$(oc get route demo-web-tls -n library -o jsonpath='{.spec.host}')/
ModusTLS endetkann
edgeam Router, dahinter HTTP zum PodPfad-Routing, Header wie X-Forwarded-*
passthrougham Pod – der Router fasst TLS nicht anClient-Zertifikate, aber kein Pfad-Routing, keine Header
reencryptam Router, neue TLS-Verbindung zum Pod (destinationCACertificate)Ende-zu-Ende und L7-Routing

oc get route demo-web -n library zeigt Admitted: True, sobald der Router sie angenommen hat – HostAlreadyClaimed, wenn zwei Routes denselben Host wollen.

5 Der Router und das Wildcard-Zertifikat

Der Router (HAProxy als Pod) hält Port 80 und 443 auf den Nodes, liest alle Routes clusterweit und schreibt daraus die HAProxy-Konfig neu.

Das Wildcard-DNS *.apps.<cluster> ist ein einziger A-Record, der auf den Router zeigt – du legst ihn einmal an, jede neue Route braucht danach keinen eigenen DNS-Eintrag. Der Router bringt ein Default-Wildcard-Zertifikat für *.apps.<cluster> mit (auf OKD/SNO selbstsigniert) – darum braucht eine edge-Route kein eigenes Zertifikat, außer du willst ein anderes: inline in route.spec.tls.certificate/.key, oder ein Secret über cert-manager.

Keycloak-FalleBei edge sieht der Pod nur HTTP und baut http://-Redirects. Aktuelle Keycloak-Images brauchen KC_PROXY_HEADERS=xforwarded plus passende KC_HOSTNAME/ KC_HTTP_ENABLED=true, ältere Images KC_PROXY=edge. Bei Spring hilft server.forward-headers-strategy.

OpenShift übersetzt ein Ingress-Objekt automatisch in eine Route – wer portabel bleiben will, schreibt Ingress und überlässt OpenShift die Übersetzung.

6 Das Probe-Modell

Drei Probes, drei getrennte Aufgaben – die häufigste Verwechslung kostet einen unnötigen Restart.

Drei Sonden, drei Fragen. readiness: soll dieser Pod gerade jetzt Verkehr bekommen? liveness: hängt der Container und braucht einen Neustart? startup: ist er mit dem Hochfahren fertig? Jede greift woanders ein – und eine falsch eingestellte Probe verursacht die meisten „läuft, aber tut nichts“-Fälle.

Drei Sonden: bereit? · hängt? · schon hochgefahren? Container startet startupProbe läuft – readiness & liveness pausiert Start fertig readiness gated Verkehr · liveness bewacht den Prozess – solange der Pod läuft readinessProbe Frage: Verkehr jetzt schicken? bei Fehler: Pod fliegt aus der EndpointSlice (B3), bekommt keinen Verkehr – bleibt aber am Leben wird sie wieder grün → Pod kommt zurück entscheidet allein über Endpoint-Mitgliedschaft soll die Abhängigkeiten prüfen (DB erreichbar, Cache warm) der Rollout ohne Ausfall hängt hier dran: neuer Pod bekommt erst Verkehr, wenn bereit (B1) Spring: /actuator/health/readiness MicroProfile: /health/ready livenessProbe Frage: hängt der Prozess und muss neu? bei Fehler (failureThreshold mal): der kubelet tötet den Container, restartPolicy startet ihn neu zu knapp eingestellt → CrashLoopBackOff soll nur prüfen „antwortet der Prozess überhaupt“ Falle: liveness prüft die DB → DB zuckt → ganze App startet neu Faustregel: liveness flach halten, Abhängigkeiten in die readiness viele Apps brauchen gar keine livenessProbe Spring: /actuator/health/liveness startupProbe Frage: ist das Hochfahren fertig? solange sie läuft: readiness & liveness pausiert bei Fehler: Container wird getötet (wie liveness) für langsame Starter: JVM, Keycloak (5+ min) failureThreshold × periodSeconds = maximale Bootzeit Beispiel: 30 × 10s = 5 min Grace ohne startupProbe auf einer langsamen JVM: liveness tötet den Container mitten im Start → CrashLoop Alternative früher: großes initialDelaySeconds an der liveness – startupProbe ist der sauberere Weg Arten & Parameter Arten: httpGet (200–399 = ok), tcpSocket, exec (exit 0), grpc initialDelaySeconds – wann die erste Prüfung periodSeconds (Vorgabe 10) · successThreshold timeoutSeconds (Vorgabe 1 – oft zu kurz!) failureThreshold (Vorgabe 3) – wie oft daneben, bevor es zählt grün / rot am Endpunkt, nicht per Log – die App muss den Pfad wirklich bedienen „Running, aber 0/1“ – und die anderen Fallen 0/1 Running = Prozess läuft, ist aber nicht ready → in keinem Endpoint → 503 an der Route 1 · timeoutSeconds: 1 auf einem langsamen Endpunkt → falsche Fehlschläge 2 · keine startupProbe auf einer JVM → liveness killt beim Booten 3 · liveness prüft die DB → DB-Blip startet alle Replicas neu 4 · readiness zeigt grün, obwohl noch nicht warm → erste Requests scheitern oc describe pod <p> # Events zeigen die Probe-Fehler Bibliothek: die 8 Spring-Dienste nutzen Actuator-liveness und -readiness, Keycloak braucht eine startupProbe (langer Boot) Merksatz: readiness steuert Verkehr, liveness steuert Neustart, startup hält beide zurück, bis es läuft oc get pod – die Spalte READY (2/2) ist die Summe der readiness-Ergebnisse, nicht der Status OCP-Unterschied: keiner – Probes sind reines Kubernetes. OpenShift zeigt sie zusätzlich in der Topologie-Ansicht der Konsole.
Drei Fragen, drei Eingriffspunkte. Die readinessProbe allein entscheidet, ob der Pod in der EndpointSlice steht – fällt sie, kommt kein Verkehr mehr, der Pod bleibt aber am Leben. Die livenessProbe lässt den Container töten und neu starten – zu scharf eingestellt gibt das eine CrashLoop. Die startupProbe hält die anderen beiden zurück, bis eine langsame JVM oder Keycloak fertig gebootet ist. Ein Rollout ohne Ausfall (B1) hängt komplett an der readiness.
  • readinessProbe: entscheidet allein über die Endpoint-Mitgliedschaft (B3) – fällt sie, kein Verkehr, der Pod bleibt am Leben und kommt zurück, wenn sie wieder grün ist
  • livenessProbe: fällt sie failureThreshold-mal, tötet der kubelet den Container und restartPolicy startet ihn neu – zu knapp = CrashLoop
  • startupProbe: solange sie läuft, sind readiness und liveness pausiert – für langsame Starter, failureThreshold × periodSeconds = maximale Bootzeit
  • readiness prüft Abhängigkeiten (DB erreichbar, Cache warm), liveness bleibt flach („antwortet der Prozess“) – sonst startet ein DB-Blip alle Replicas neu
  • Arten: httpGet (200–399 = ok), tcpSocket, exec (exit 0), grpc
  • Parameter: initialDelaySeconds, periodSeconds (10), timeoutSeconds (1 – oft zu kurz), failureThreshold (3), successThreshold
  • „0/1 Running“ = läuft, aber nicht ready → in keinem Endpoint → 503 an der Route
  • ohne startupProbe auf einer JVM tötet die liveness den Container mitten im Boot → CrashLoop – früher half ein großes initialDelaySeconds
  • feste Health-Pfade: Spring Actuator /actuator/health/liveness und /actuator/health/readiness, MicroProfile Health /health/live und /health/ready
  • der Rollout ohne Ausfall hängt an der readiness (B1) – ein neuer Pod bekommt erst Verkehr, wenn bereit, und maxUnavailable zählt bereite Pods
  • prüfen: oc describe pod <p> (Events), die Spalte READY bei oc get pod ist die Summe der readiness-Ergebnisse
  • Bibliothek-Stolperstein: die liveness mit initialDelaySeconds: 30 tötete catalog-service mitten im Spring-Kaltstart auf der geladenen SNO → hochgesetzt auf 150 s liveness / 90 s readiness. Alle 8 Dienste mit Actuator-Probes, Keycloak zusätzlich mit startupProbe (langer Boot)
  • OCP-Unterschied: keiner – Probes sind reines Kubernetes, die Konsole zeigt sie zusätzlich in der Topologie-Ansicht
readinessentscheidet allein über die Endpoint-Mitgliedschaft (Schritt 1) – fällt sie, kein Verkehr, der Pod bleibt am Leben und kommt zurück, wenn sie wieder grün ist
livenessfällt sie failureThreshold-mal, tötet der kubelet den Container, restartPolicy startet ihn neu – zu knapp eingestellt heißt CrashLoop
startupsolange sie läuft, sind readiness und liveness pausiert – für langsame Starter, failureThreshold × periodSeconds = maximale Bootzeit

readiness prüft Abhängigkeiten (DB erreichbar, Cache warm), liveness bleibt flach („antwortet der Prozess überhaupt“) – sonst startet ein kurzer DB-Blip gleich alle Replicas neu. Arten: httpGet (200–399 = ok), tcpSocket, exec (exit 0), grpc. Parameter: initialDelaySeconds, periodSeconds (10), timeoutSeconds (1 – oft zu kurz), failureThreshold (3), successThreshold.

7 Eine readiness-Probe live brechen

Ein Pod mit einer readiness-Probe auf einen Pfad, der 500 antwortet – und beobachten, wie er aus den Endpoints fliegt, ohne zu sterben.

Laptop · Bash
cat <<'YAML' | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-probe
  namespace: library
spec:
  replicas: 1
  selector: { matchLabels: { app: demo-probe } }
  template:
    metadata: { labels: { app: demo-probe } }
    spec:
      containers:
        - name: web
          image: registry.access.redhat.com/ubi9/httpd-24
          ports: [{ containerPort: 8080 }]
          readinessProbe:
            httpGet: { path: /nonexistent-path, port: 8080 }
            periodSeconds: 5
            failureThreshold: 2
          livenessProbe:
            httpGet: { path: /, port: 8080 }
            periodSeconds: 10
YAML

oc get pod -n library -l app=demo-probe -w
# -> READY 0/1, RESTARTS bleibt 0 (liveness ist gruen, nur readiness ist rot)

oc describe pod -n library -l app=demo-probe | grep -A2 Readiness
oc get events -n library --field-selector involvedObject.name=$(oc get pod -n library -l app=demo-probe -o jsonpath='{.items[0].metadata.name}') | grep -i unhealthy

oc delete deployment demo-probe -n library

was zu sehen ist0/1 Running, keine Restarts – genau der Unterschied zwischen readiness (raus aus dem Verkehr) und liveness (Neustart). Ein Service davor hätte in dieser Zeit Endpoints: <none> (Schritt 3) und der Router 503.

8 Typische Fallen

Symptommeist
Route antwortet 503Service hat keine Endpoints – Selector-Mismatch oder alle Pods not-ready
sporadische 502ein flappender Pod wird kurz in den Endpoints geführt und fällt wieder raus
0/1 Running dauerhaftreadiness prüft eine Abhängigkeit, die nicht erreichbar ist (falscher DB-Host, Netzwerkpolicy)
CrashLoopBackOff kurz nach dem Startliveness ohne startupProbe tötet einen langsamen Starter mitten im Boot
Deploy hängt bei „progressing“der neue ReplicaSet wird nie ready – ohne grüne readiness bekommt er nie Verkehr, der Rollout wartet ewig (B1)

9 Wie die Bibliothek es macht

Bausteinreal
Servicejeder der 8 Dienste ist ein ClusterIP-Service auf 8080 – api-gateway ruft die Fachdienste über http://catalog-service:8080 usw. Der Name kommt aus fullnameOverride im Helm-Chart und muss exakt passen
Routenur drei edge-Routes – library-frontend, api-gateway und keycloak unter *.apps.sno.<ip>.nip.io, per oc expose in der Jenkins-Prod-Stage angelegt. Die anderen 6 Dienste sind rein clusterintern
Probesalle 8 Dienste mit Spring-Actuator-Probes (/actuator/health/liveness, /actuator/health/readiness) – initialDelaySeconds: 30 tötete catalog-service mitten im Kaltstart auf der geladenen SNO, hochgesetzt auf 150 s liveness / 90 s readiness. Keycloak zusätzlich mit einer startupProbe für den langen Boot

OCP-Unterschied bei allen dreien: keiner – Service, Route-Übersetzung des DNS-Operators und Probes sind entweder Kern-Kubernetes oder Kern-OpenShift, identisch auf OKD und OCP.

+ Die Kurzfassung

Servicefester Name + ClusterIP, selector füllt die EndpointSlice – nur ready Pods sind drin
RouteHostname → Service am Router (HAProxy), edge/passthrough/reencrypt steuern wo TLS endet
Probesreadiness = raus/rein aus dem Verkehr, liveness = Neustart – beide verwechseln kostet entweder Downtime oder eine CrashLoop
⌂ Cockpit