DNS und Netzwerk-Troubleshooting
Service Discovery debuggen und Netzwerk-Probleme lösen.
Lernziele
Nach dieser Lektion hast du eine feste Diagnose-Reihenfolge für „Service A erreicht Service B nicht” im Kopf: erst DNS, dann Endpoints, dann Netzwerkpfad, dann Policies. Du kannst mit netshoot, nslookup und tcpdump gezielt Schicht für Schicht testen, kennst die typischen CoreDNS-Probleme und weißt, wie du im Interview ein Netzwerk-Troubleshooting-Szenario strukturiert durchspielst.
Das Problem: „kann Service nicht erreichen” sagt fast nichts
Die Meldung im Incident-Channel lautet selten präziser als „Backend nicht erreichbar”. Dahinter können völlig verschiedene Defekte stecken: DNS löst nicht auf, der Service hat keine Endpoints, eine NetworkPolicy verwirft Pakete, kube-proxy hat veraltete Regeln, das CNI hat ein Routing-Problem — oder die Anwendung ist schlicht abgestürzt. Wer wild durcheinander testet, verbrennt im Incident Zeit und wirkt im Interview planlos.
Der Schlüssel ist, die Frage zu zerlegen. Eine Verbindung „Pod A → backend.payments → Pod B” hat drei trennbare Stufen: Namensauflösung (wird aus dem Namen eine IP?), Service-Schicht (zeigt der Service auf lebende Pods?) und Paketweg (kommen Pakete physisch an — oder filtert sie etwas?). Jede Stufe hat eigene Werkzeuge und eigene typische Defekte. Die Reihenfolge ist nicht beliebig: DNS zuerst, weil ohne Auflösung alles andere gar nicht erst startet.
Schon die Fehlermeldung grenzt ein: „could not resolve host” → Stufe 1. „Connection refused” → Pakete kommen an, aber niemand lauscht (falscher targetPort, App down) — Stufe 2. „Timeout” → Pakete versickern, typisch für NetworkPolicy oder Routing — Stufe 3.
Stufe 1: DNS — kennt CoreDNS den Namen?
Erster Reflex: einen Debug-Pod starten und auflösen lassen. Das Schweizer Taschenmesser dafür ist das netshoot-Image — es bringt dig, nslookup, curl, tcpdump und mehr mit:
kubectl run tmp --rm -it --image=nicolaka/netshoot -- bash
# darin:
nslookup backend.payments.svc.cluster.local
nslookup backend.payments # Kurzform über Search-Domains
cat /etc/resolv.conf # zeigt Nameserver (kube-dns ClusterIP) + Search-Domains
Schlägt die Auflösung fehl, gibt es drei Verdächtige. Erstens: Der Service existiert nicht oder heißt anders (kubectl get svc -n payments). Zweitens: Du bist im falschen Namespace unterwegs — die Kurzform backend funktioniert nur im eigenen Namespace, sonst brauchst du backend.payments (Lektion 11). Drittens: CoreDNS selbst hat ein Problem. Dann prüfst du:
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
# Erreiche ich CoreDNS direkt, am resolv.conf-Eintrag vorbei?
dig @<kube-dns-ClusterIP> backend.payments.svc.cluster.local
Funktioniert dig @CoreDNS direkt, aber die normale Auflösung nicht, liegt das Problem zwischen Pod und CoreDNS — klassischerweise eine Egress-NetworkPolicy, die Port 53 blockiert. Ein subtilerer Dauerbrenner sind intermittierende Timeouts unter Last: zu wenige CoreDNS-Replicas für die Clustergröße, oder das bekannte Conntrack-Race bei parallelen UDP-Anfragen, das man mit NodeLocal DNSCache entschärft. „DNS ist langsam” ist in Kubernetes ein eigenes Krankheitsbild, kein Randthema.
Stufe 2: Service-Schicht — zeigen die Endpoints auf lebende Pods?
DNS liefert eine ClusterIP, aber die Verbindung scheitert trotzdem? Dann prüfst du die Kette Service-Selector → Endpoints → Readiness:
kubectl describe svc backend -n payments # Selector + Ports
kubectl get endpointslices -n payments -l kubernetes.io/service-name=backend
kubectl get pods -n payments -l app=backend -o wide # laufen Pods mit dem Label? Ready?
Leere EndpointSlices haben genau zwei Ursachen: Kein Pod trägt die Labels aus dem Selector (Tippfehler, vergessenes Label im Pod-Template), oder die Pods sind nicht ready (Readiness-Probe rot). Volle Endpoints plus „connection refused” deuten dagegen auf einen targetPort, der nicht zum Container-Port passt — oder auf eine App, die auf 127.0.0.1 statt 0.0.0.0 lauscht.
Der entscheidende Differenzialtest: die Pod-IP direkt ansprechen, am Service vorbei. curl http://<pod-ip>:8080 aus dem netshoot-Pod. Geht das, ist die Anwendung gesund und der Fehler liegt in der Service-Definition oder bei kube-proxy. Geht es nicht, ist es kein Service-Problem — weiter zu Stufe 3.
Stufe 3: Paketweg — Policies und tiefes Graben
Timeout bei direkter Pod-IP heißt: Pakete werden unterwegs verworfen. Hauptverdächtiger ist eine NetworkPolicy — prüfe beide Seiten und beide Richtungen:
kubectl get networkpolicy -A
kubectl describe networkpolicy -n payments
Denk an die Semantik aus Lektion 13: Sobald irgendeine Policy den Ziel-Pod für Ingress selektiert, muss dein Quell-Pod explizit erlaubt sein — und beim Quell-Pod gilt dasselbe für Egress. Stimmen die Namespace-Labels, auf die der namespaceSelector matcht? Das prüft man chronisch zu spät.
Wenn keine Policy verantwortlich ist, hilft nur Hinschauen auf Paketebene. tcpdump im netshoot-Pod (oder als Ephemeral Container am betroffenen Pod) zeigt, ob SYN-Pakete ankommen und ob Antworten zurückgehen:
kubectl debug -it <ziel-pod> --image=nicolaka/netshoot --target=<container> -- \
tcpdump -ni any port 8080
SYN kommt an, keine Antwort → Problem im Ziel-Pod. Gar nichts kommt an → CNI-/Routing-Ebene, jetzt sind Node-Routen und CNI-Pod-Logs dran. Wer Cilium fährt, hat es komfortabler: Hubble zeigt pro Flow an, ob er FORWARDED oder DROPPED wurde — inklusive der Policy, die gedroppt hat (hubble observe --verdict DROPPED). Das ersetzt in vielen Fällen das tcpdump-Stochern komplett.
Praxis
Baue dir ein kaputtes Szenario und debugge es entlang der drei Stufen (kind oder minikube, CNI mit Policy-Support):
kubectl create deployment backend --image=nginx
kubectl expose deployment backend --port=80 --target-port=80
# Fehler 1 einbauen: Selector zerstören
kubectl patch svc backend -p '{"spec":{"selector":{"app":"backend-typo"}}}'
kubectl run tmp --rm -it --image=nicolaka/netshoot -- \
curl -m 2 http://backend # schlägt fehl
kubectl get endpointslices -l kubernetes.io/service-name=backend # leer → gefunden
kubectl patch svc backend -p '{"spec":{"selector":{"app":"backend"}}}'
# Fehler 2 einbauen: Policy ohne DNS-Ausnahme
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
spec:
podSelector: {}
policyTypes: ["Egress"]
EOF
kubectl run tmp --rm -it --image=nicolaka/netshoot -- nslookup backend
# Timeout → DNS-Egress blockiert; Policy wieder löschen:
kubectl delete networkpolicy deny-all-egress
Beide Defekte erzeugen dasselbe Oberflächen-Symptom („backend nicht erreichbar”), haben aber völlig verschiedene Signaturen — genau das Unterscheiden trainierst du hier.
Typische Stolperfallen
Debug-Image ohne Werkzeuge: Distroless- und Scratch-Images haben weder Shell noch curl. Statt zu verzweifeln: kubectl debug mit netshoot als Ephemeral Container an den laufenden Pod hängen.
Im falschen Kontext getestet: Vom Laptop aus ist die ClusterIP nie erreichbar — das ist kein Bug. Cluster-DNS und ClusterIPs existieren nur innerhalb des Clusters; immer aus einem Pod heraus testen.
Nur eine Richtung geprüft: Egress-Policy beim Sender und Ingress-Policy beim Empfänger müssen beide passen. Wer nur die Zielseite prüft, übersieht die Hälfte der Fälle.
DNS-Caching verschleiert den Fix: Manche Anwendungen (JVM!) cachen DNS-Antworten intern. Nach einem Fix scheint das Problem zu bleiben, bis der Pod neu startet — erst frisch testen, dann weitergraben.
Interview-Vorbereitung
Auf das Szenario „Pod erreicht einen Service nicht — wie gehst du vor?” antwortest du als Stufenplan: Symptom präzisieren (resolve-Fehler, refused oder timeout?) → DNS aus einem netshoot-Pod testen, resolv.conf und CoreDNS prüfen → Service-Schicht: Selector, EndpointSlices, Readiness, targetPort; Differenzialtest direkt gegen die Pod-IP → Paketweg: NetworkPolicies beider Seiten, dann tcpdump bzw. Hubble. Dass du laut benennst, welche Hypothese jeder Test prüft, ist der eigentliche Punktebringer.
Typische Follow-ups:
- „DNS-Auflösung dauert sporadisch 5 Sekunden — woran denkst du?” — UDP-Conntrack-Race bei parallelen A/AAAA-Anfragen bzw. überlastetes CoreDNS; Abhilfe: NodeLocal DNSCache, mehr Replicas.
- „Endpoints sind leer, Pods laufen — warum?” — Label-Mismatch zum Selector oder Readiness-Probe schlägt fehl;
describe podzeigt beides. - „Connection refused vs. timeout — was sagt dir das?” — Refused: Pakete kommen an, kein Listener (Port/App-Problem). Timeout: Pakete werden verworfen (Policy, Routing).
- „Wie debuggst du einen Pod ohne Shell?” —
kubectl debugmit Ephemeral Container und einem Tool-Image wie netshoot.
Zusammenfassung
Netzwerk-Debugging in Kubernetes ist eine Reihenfolge, kein Bauchgefühl: DNS (netshoot, nslookup, CoreDNS-Logs) → Service-Schicht (Selector, EndpointSlices, Readiness, targetPort, Direkttest auf die Pod-IP) → Paketweg (NetworkPolicies in beide Richtungen, tcpdump, Hubble). Die Fehlermeldung selbst — resolve-Fehler, refused, timeout — sagt dir, auf welcher Stufe du einsteigst.
Damit ist der Networking-Block abgeschlossen. Als Nächstes wechseln wir die Baustelle: Was passiert mit Daten, wenn ein Pod stirbt? PersistentVolumes, PVCs und StorageClasses sind das Thema der nächsten Lektion.