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:

  1. Pods laufen Container und lauschen auf Container-Ports.
  2. Services liefern stabile Cluster-IP und DNS-Namen über ready Pods.
  3. 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.

VerantwortungServiceRoute
Stabiler Name im ClusterJa (web, web.myproject.svc)Nein
Load Balancing über PodsJa (via kube-proxy / Dataplane)Nein — leitet an Service weiter
Externer DNS-HostnameNeinJa (spec.host oder generiert)
HTTP-Pfad-Regeln (mehrere Pfade)NeinBegrenzt — für komplexe Pfade siehe Ingress
TLS an der Cluster-EdgeNeinJa (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-FeldRoute-Feld
spec.rules[].hostspec.host
spec.rules[].http.paths[].pathspec.path (einfache Fälle)
backend.service.namespec.to.name
backend.service.portspec.port.targetPort
spec.tlsspec.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 Hostspec.host setzen:

spec:
  host: api.myproject.apps.prod.example.com

Generierter Hostspec.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:

ModusClient zu RouterRouter zu Service/Pod
edgeHTTPS (Router-Zertifikat)Meist HTTP
passthroughHTTPS End-to-EndHTTPS (Router entschlüsselt nicht)
reencryptHTTPS (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:

SymptomWahrscheinliche Ursache
503 / no healthy upstreamService hat keine Endpoints
Falsche AppRoute zeigt auf falschen Service oder Project
ZertifikatsfehlerEdge-Cert/Host-Mismatch, abgelaufenes Cert
Extern connection refusedDNS oder LB nicht auf Router gerichtet
Im Cluster ok, extern nichtRoute/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.