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

Service, Route und Probes in OpenShift

Das Modell steht in den Atlas-Diagrammen B3, B4 und B5. Hier spielst du den ganzen Weg von Hand durch: ein Service entsteht, DNS löst ihn auf, eine Route macht ihn von aussen 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.

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 heisst 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).

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, ausser 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.

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 heisst 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