Man kennt Kubernetes Services. Vielleicht auch Ingress. Dann kommt ein OpenShift-Cluster und jemand sagt: „Exponiert die App mit einer Route.“ Das YAML wirkt fast vertraut. Die Feldnamen sind es nicht.
Da war ich auch. Ein kaputtes Ingress debuggte ich über Controller, Backend-Service und Endpoint-Readiness. Routes nutzten denselben Mental Stack — stabiler Service in der Mitte, Edge-Objekt davor — aber Tooling und TLS-Defaults überraschten mich, bis ich eine Route genau las und einen Request End-to-End nachverfolgte.
Dieser Beitrag ist für Kubernetes-Nutzer, die diese Nachverfolgung ausgeschrieben haben wollen. Ich vergleiche Route, Service und Ingress, ohne so zu tun, sie seien austauschbar. Ich bleibe bescheiden bei Advanced Routing (Weighted Splits, Shard Router, Gateway API). Das existiert. Die meisten Anfänger brauchen zuerst einen Hostnamen, einen Service und eine funktionierende TLS-Story.
Drei Schichten — Pod, Service, Route
Das Muster ist dasselbe wie anderswo in Kubernetes:
- Pods laufen Container und lauschen auf Container-Ports.
- Services liefern stabile Cluster-IP und DNS-Namen über ready Pods.
- Routes (OpenShift) oder Ingress (portables Kubernetes) veröffentlichen HTTP/S-Hostnamen, die auf einen Service zeigen.
Nichts in diesem Stack ersetzt ein Deployment. Wenn Pods nicht ready sind, erfinden weder Service noch Route gesunde Backends.
Minimaler funktionierender Stack:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: web
labels:
app: web
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: web
labels:
app: web
spec:
to:
kind: Service
name: web
weight: 100
port:
targetPort: http
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
Anwenden und prüfen:
oc apply -f web-stack.yaml
oc get deploy,svc,route -l app=web
oc get endpoints web
Wenn endpoints keine ready Adressen zeigt, zuerst das Deployment fixen, bevor die Route debuggt wird. Ich starre noch immer auf Route-YAML, wenn der Service-Selector falsch ist. Das Edge-Objekt ist selten die erste kaputte Schicht.
Service vs Route — wer wofür zuständig ist
Ein ClusterIP-Service beantwortet: „Welche ready Pods bekommen Traffic für web:80 im Cluster?“
Eine Route beantwortet: „Welcher externe Hostname schickt Traffic an Service web auf welchem Port, und wie wird TLS an der Edge gehandhabt?“
Sie composen. Keiner ersetzt den anderen.
| Verantwortung | Service | Route |
|---|---|---|
| Stabiler Name im Cluster | Ja (web, web.myproject.svc) | Nein |
| Load Balancing über Pods | Ja (via kube-proxy / Dataplane) | Nein — leitet an Service weiter |
| Externer DNS-Hostname | Nein | Ja (spec.host oder generiert) |
| HTTP-Pfad-Regeln (mehrere Pfade) | Nein | Begrenzt — für komplexe Pfade siehe Ingress |
| TLS an der Cluster-Edge | Nein | Ja (spec.tls) |
Im Cluster rufen andere Pods weiter http://web oder http://web.myproject.svc.cluster.local auf. Die Route ersetzt das nicht. Sie ergänzt eine externe Front Door, verwaltet vom OpenShift Router.
Den Service prüfen wie auf jedem Cluster:
kubectl get svc web -o wide
kubectl get endpoints web
kubectl describe svc web
Die Route mit OpenShift-bewussten Befehlen:
oc get route web
oc describe route web
oc get route web -o yaml
Spalten aus oc get route, die oft gebraucht werden:
oc get route
# NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
# web web-myproject.apps.cluster.example.com ... web http edge None
Die HOST-Spalte ist das, was man von außen curlt (wenn DNS oder /etc/hosts stimmt). SERVICES und PORT müssen zu Service-Name und Port-Name oder -Nummer passen.
Route vs Ingress — gleicher Job, andere Objekte
Ingress ist die portable Kubernetes-API (networking.k8s.io/v1). Man installiert einen Ingress-Controller; er beobachtet Ingress-Objekte und konfiguriert einen Proxy.
Route ist OpenShifts route.openshift.io/v1-API. Der OpenShift Router beobachtet Routes standardmäßig auf den meisten Clustern.
Grobe Zuordnung:
| Ingress-Feld | Route-Feld |
|---|---|
spec.rules[].host | spec.host |
spec.rules[].http.paths[].path | spec.path (einfache Fälle) |
backend.service.name | spec.to.name |
backend.service.port | spec.port.targetPort |
spec.tls | spec.tls |
OpenShift-Cluster laufen oft mit beidem — Router für Routes und Ingress Controller für Ingress-Ressourcen. Teams wählen pro App einen Stil, um zwei Edge-Configs für denselben Service zu vermeiden.
Praktischer Rat: Wenn Plattform-Docs und Kollegen Routes nutzen, zuerst Routes lernen. Wenn das GitOps-Repo Ingress für Multi-Cluster-Portabilität standardisiert, Ingress nutzen — aber prüfen, dass IngressClass und Controller auf dem OpenShift-Cluster existieren. Ein Ingress-Objekt ohne Controller verhält sich überall gleich: korrektes YAML, kein Traffic.
Hostnamen — spec.host und generierte Namen
Jede Route braucht einen Hostnamen, den Clients auflösen können. Zwei Muster:
Expliziter Host — spec.host setzen:
spec:
host: api.myproject.apps.prod.example.com
Generierter Host — spec.host weglassen; OpenShift vergibt einen unter der Cluster-Apps-Domain, oft aus Route-Name, Project und DNS-Suffix:
oc create route edge web --service=web --port=http
oc get route web -o jsonpath='{.spec.host}{"\n"}'
Die Cluster-Apps-Domain finden:
oc get ingresscontroller default -n openshift-ingress-operator -o jsonpath='{.status.domain}{"\n"}'
DNS muss Clients an die externe Adresse oder den Load Balancer des Routers bringen. In Firmennetzen löst oft schon ein Wildcard *.apps.cluster.example.com auf. In Lab-Clustern patcht man vielleicht /etc/hosts:
# after oc get route web
curl -v https://web-myproject.apps.cluster.example.com/
Wenn der Browser den Namen auflöst, die Verbindung aber scheitert, eher Firewall, falsches Wildcard-DNS oder unreachable Router-LB vermuten — nicht unbedingt den Pod.
Wildcard-Routes (spec.wildcard: Subdomain) gibt es für Advanced-Fälle. Am ersten Tag brauche ich sie selten.
Edge-TLS — Termination-Modi, die zählen
TLS verwirrte mich, bis ich wo das Zertifikat endet von was der Pod sieht trennte.
OpenShift Route spec.tls.termination — häufige Werte:
| Modus | Client zu Router | Router zu Service/Pod |
|---|---|---|
edge | HTTPS (Router-Zertifikat) | Meist HTTP |
passthrough | HTTPS End-to-End | HTTPS (Router entschlüsselt nicht) |
reencrypt | HTTPS (Router-Zertifikat) | HTTPS (Router nutzt Backend-Zertifikat) |
Edge-Termination ist auf vielen internen Apps das Default-Muster:
spec:
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
Clients rufen https://host/ auf. Der Router beendet TLS. Traffic zum Service ist oft plain HTTP auf Port 80. Die Readiness-Probe am Pod kann HTTP auf dem Container-Port bleiben — normal, kein Config-Fehler.
Passthrough, wenn die App TLS selbst handhaben muss (oder TLS bis zum Pod Pflicht ist):
spec:
tls:
termination: passthrough
port:
targetPort: 8443
Reencrypt, wenn Edge-TLS und verschlüsselter Traffic zum Backend gewünscht sind — das Plattformteam owned meist die destinationCACertificate-Details.
TLS-Secret für ein eigenes Edge-Zertifikat referenzieren (Muster variiert je nach Cluster):
oc create secret tls shop-tls --cert=tls.crt --key=tls.key
Bei curl-Zertifikatsfehlern: Termination-Modus, Host in cert SANs und ob man HTTP gegen eine nur-HTTPS-Route testet.
oc get route und schnelles Anlegen
Routes listen und prüfen:
oc get route
oc get route -A
oc get route web -o wide
oc get route web -o jsonpath='{.spec.host}{"\n"}'
oc describe route web
kubectl get route -n myproject
Mit oc prototypen — für GitOps bevorzuge ich explizites YAML:
oc expose svc web
oc create route edge web --service=web --port=http --hostname=shop.example.com
oc expose deployment/web --port=8080
kubectl apply -f route.yaml
Debugging von Route bis Pod — eine ruhige Reihenfolge
Wenn https://my-host/ scheitert, gehe ich diese Sequenz. Sie spiegelt Ingress-Debugging mit anderen Befehlen an der Edge.
Schritt 1 — Route existiert und hat Host:
oc get route
oc describe route web
Auf Accepted-Status, korrekten Host, Service: web, TargetPort: http (oder numerischen Port) und TLS-Termination achten. oc describe route zeigt Admitted-Conditions und manchmal Zertifikatsdetails.
Schritt 2 — Service und Endpoints:
kubectl get svc web
kubectl get endpoints web
kubectl describe svc web
Null Endpoints heißt: keine ready Pods passen zum Selector, oder Port-Namen stimmen nicht. Labels, Probes oder targetPort zuerst fixen.
Schritt 3 — Pods ready:
kubectl get pods -l app=web
kubectl describe pod -l app=web
kubectl logs -l app=web --tail=50
Schritt 4 — In-Cluster-curl (Route umgehen):
kubectl run curl-test --rm -it --restart=Never --image=curlimages/curl -- \
curl -sv http://web.myproject.svc.cluster.local/
Wenn in-cluster funktioniert, externe Route aber nicht, eher DNS zum Router, TLS-Modus oder Firmen-Proxy — nicht das Deployment.
Schritt 5 — Von außen, verbose curl:
curl -vk https://web-myproject.apps.cluster.example.com/
curl -v http://web-myproject.apps.cluster.example.com/
Redirect-Loops bedeuten manchmal insecureEdgeTerminationPolicy: Redirect, während man nur HTTP testet. TLS-Mismatch manchmal Passthrough-Route gegen HTTP-only-Pod-Port. Router-Logs liegen in openshift-ingress — Plattform-Admins fragen, wenn man den aktuellen Label-Selector braucht.
Typische Symptome:
| Symptom | Wahrscheinliche Ursache |
|---|---|
| 503 / no healthy upstream | Service hat keine Endpoints |
| Falsche App | Route zeigt auf falschen Service oder Project |
| Zertifikatsfehler | Edge-Cert/Host-Mismatch, abgelaufenes Cert |
| Extern connection refused | DNS oder LB nicht auf Router gerichtet |
| Im Cluster ok, extern nicht | Route/TLS/DNS-Schicht, nicht Pod |
OpenShift-Cluster können zusätzlich einen Ingress Controller für Standard-Ingress betreiben — getrennt vom Router. oc get ingressclass prüfen, bevor man annimmt, Ingress-YAML funktioniert. Denselben Service nicht mit Route und Ingress exposen, es sei denn, das Team will das absichtlich.
Abschluss
Routes sind kein Rätsel abseits von Kubernetes-Networking. Sie sind OpenShifts Edge-Deklaration: Hostname und TLS vorne, Service dahinter, Pods unten.
Wer Ingress bereits über Controller → Ingress-Regel → Service → Endpoints → Pods debuggt, kann dieselbe Reihenfolge nutzen. Den ersten Schritt tauschen gegen oc get route und oc describe route. kubectl get endpoints bleibt in der Mitte, wo es immer hingehörte.
Eine explizite Route-YAML lernen, eine mit generiertem Host, ein Edge-TLS-Beispiel. Einmal eine failing URL von curl bis Pod-Logs nachverfolgen. Danach fühlen sich Routes wie ein Dialekt an, den man schon sprach — etwas anderer Wortschatz, dieselbe Grammatik.