Troubleshooting-Methodik
Systematisches Debuggen in Kubernetes.
Lernziele
Nach dieser Lektion hast du eine wiederholbare Diagnose-Reihenfolge für kaputte Workloads: describe und Events lesen, Logs (auch der Vorgänger-Instanz), hineingehen mit exec, und wenn das Image keine Shell hat, kubectl debug mit Ephemeral Containers. Du kannst die häufigsten Pod-Zustände — Pending, ImagePullBackOff, CrashLoopBackOff, OOMKilled — jeweils ihrer Ursachenklasse zuordnen und im Interview ein Live-Debugging laut denkend strukturieren.
Das Problem: Symptome raten statt Zustand lesen
Kubernetes sagt dir fast immer, was los ist — nur nicht dort, wo Anfänger suchen. Wer bei einem roten Pod sofort in die Anwendungslogs springt, übersieht, dass die Hälfte aller Probleme vor dem Anwendungsstart passiert: Der Pod wurde nie geplant, das Image nie gezogen, der Container sofort wieder beendet. Für jede dieser Phasen gibt es eine eigene Informationsquelle, und die Kunst ist, sie in der richtigen Reihenfolge abzufragen statt wild zu raten.
Die Phasen entsprechen dem Pod-Lebenszyklus: Scheduling (Pending?) → Image-Beschaffung (ImagePullBackOff?) → Container-Start und -Leben (CrashLoopBackOff, OOMKilled?) → Anwendung läuft, verhält sich aber falsch (jetzt erst: App-Logs, exec, Netzwerk). Der Pod-Status in kubectl get pods ist die Weiche, die dir sagt, in welcher Phase du gerade debuggst.
Schritt 1: describe und Events — die Cluster-Sicht
kubectl describe pod <name> ist immer der erste Befehl. Der Events-Abschnitt am Ende erzählt die Geschichte des Pods chronologisch aus Sicht der Controller: Warum der Scheduler keinen Node fand („0/3 nodes are available: 3 Insufficient memory”), warum der Image-Pull scheitert („pull access denied”), warum die Liveness-Probe den Container killt. Für Pending-Pods steht hier die komplette Filtering-Begründung des Schedulers (Lektion 17), für Probe-Probleme jede fehlgeschlagene Prüfung.
Bei describe gehören auch die Felder State, Last State und Restart Count der Container zum Pflichtblick: Last State: Terminated, Reason: OOMKilled, Exit Code: 137 ist eine vollständige Diagnose in einer Zeile — der Container hat sein Memory-Limit gerissen und wurde vom Kernel beendet.
Events gibt es auch jenseits des einzelnen Pods: kubectl get events --sort-by=.lastTimestamp -n <namespace> zeigt die jüngsten Vorfälle des ganzen Namespace — ideal als Einstieg, wenn du noch gar nicht weißt, welches Objekt klemmt. Zwei Einschränkungen, die man kennen muss: Events sind flüchtig (default etwa eine Stunde, danach weg — bei länger zurückliegenden Incidents brauchst du externes Event-Logging) und Node-Probleme stehen bei kubectl describe node (Conditions wie MemoryPressure, DiskPressure), nicht beim Pod.
Schritt 2: Logs — und warum —previous Gold wert ist
Erst wenn der Container überhaupt startet, lohnen sich Logs: kubectl logs <pod> (bei mehreren Containern -c <container>, für alle Replicas eines Deployments kubectl logs deploy/<name> oder per Label-Selector -l app=...).
Der entscheidende Kniff beim CrashLoopBackOff: Der aktuell laufende Container ist frisch gestartet und hat noch nichts Interessantes geloggt — die Absturzursache steht in den Logs der vorherigen Instanz. kubectl logs <pod> --previous holt genau die. CrashLoopBackOff selbst ist übrigens keine Fehlerursache, sondern ein Muster: Container startet, stirbt, kubelet wartet mit exponentiell wachsendem Backoff, startet neu. Die Ursache steht in --previous-Logs (App crasht selbst: Config fehlt, DB nicht erreichbar) oder in describe (Liveness-Probe killt einen eigentlich gesunden, nur langsamen Start — dann ist die Probe das Problem, und eine Startup-Probe die Lösung).
Exit Codes helfen beim Einordnen: 0 = sauber beendet (bei einem Server verdächtig — Prozess sollte nie enden), 1 = Anwendungsfehler, 137 = SIGKILL (meist OOM), 143 = SIGTERM (normale Terminierung).
Schritt 3: Hineingehen — exec und kubectl debug
Wenn der Container läuft, aber sich falsch verhält, gehst du hinein: kubectl exec -it <pod> -- sh und dann vor Ort prüfen — sieht die Anwendung ihre Config (env, gemountete Dateien)? Erreicht sie ihre Abhängigkeiten? Stimmen Dateiberechtigungen?
Moderne, schlanke Images (distroless, scratch) haben aber weder sh noch Tools — exec läuft ins Leere. Dafür gibt es kubectl debug mit Ephemeral Containers: Es hängt einen zusätzlichen Tool-Container an den laufenden Pod, der dessen Namespaces teilt, ohne den Pod neu zu starten:
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>
Mit --target teilt der Debug-Container den Prozess-Namespace des Zielcontainers — du siehst dessen Prozesse und kannst über /proc/<pid>/root sogar in dessen Dateisystem schauen. Zwei weitere Spielarten desselben Befehls: kubectl debug <pod> --copy-to=debug-kopie --set-image=... erstellt eine veränderte Kopie des Pods (etwa mit anderem Entrypoint, wenn der Original-Container sofort stirbt), und kubectl debug node/<node> -it --image=busybox öffnet einen privilegierten Pod auf dem Node mit dessen Root-FS unter /host — der saubere Weg auf einen Node ohne SSH.
Damit steht die Leiter komplett: describe/Events → logs (—previous) → exec → debug — von der Cluster-Sicht immer tiefer in den Container, jede Stufe nur, wenn die vorherige die Frage nicht beantwortet hat.
Praxis
Drei klassische Defekte bauen und entlang der Leiter diagnostizieren:
kind create cluster
# Fall 1: ImagePullBackOff
kubectl run broken-image --image=nginx:gibt-es-nicht
kubectl get pod broken-image # ErrImagePull / ImagePullBackOff
kubectl describe pod broken-image | grep -A5 Events # "manifest unknown" — Tag-Tippfehler
# Fall 2: CrashLoopBackOff mit Ursache in --previous
kubectl run crasher --image=busybox:1.36 -- sh -c \
"echo 'FATAL: config /etc/app/app.conf fehlt'; exit 1"
kubectl get pod crasher # CrashLoopBackOff (nach kurzem Warten)
kubectl logs crasher --previous # die FATAL-Zeile der toten Instanz
# Fall 3: OOMKilled
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: oom
spec:
containers:
- name: eater
image: polinux/stress
command: ["stress", "--vm", "1", "--vm-bytes", "200M", "--vm-hang", "0"]
resources:
limits:
memory: "100Mi"
EOF
kubectl get pod oom -w # Running → OOMKilled → CrashLoopBackOff
kubectl describe pod oom | grep -B2 -A4 "Last State" # Reason: OOMKilled, Exit Code: 137
# Aufräumen
kubectl delete pod broken-image crasher oom
Alle drei Fälle zeigen denselben Status-Endzustand (Back-off/CrashLoop), aber drei verschiedene Diagnosewege — Events für den Pull, --previous für den Crash, Last State für OOM. Genau diese Zuordnung macht dich im Incident schnell.
Typische Stolperfallen
Logs vor describe gelesen: Bei Pending und ImagePullBackOff gibt es keine Logs — der Container lief nie. Wer dort anfängt, verliert Zeit; der Pod-Status entscheidet über den Einstiegspunkt.
CrashLoop ohne --previous debuggt: Die frische Instanz hat die Absturzursache nicht geloggt. Ohne --previous jagst du ein Phantom.
Liveness-Probe als Täter übersehen: Restart Count steigt, App-Logs sehen gesund aus → describe zeigt „Liveness probe failed”. Der Killer ist die Probe (zu knappes Timeout, zu früher Start), nicht die Anwendung.
Events sind schon weg: Eine Stunde nach dem Vorfall ist die Event-Geschichte gelöscht. Bei der Incident-Analyse am Nachmittag hilft nur ein Event-Exporter oder die Logs der Controller — einplanen, bevor man sie braucht.
Interview-Vorbereitung
Live-Troubleshooting-Aufgaben („Pod ist rot — was tust du?”) bewerten den Weg, nicht nur das Ziel. Antworte als Leiter und sprich die Hypothesen aus: Status lesen (get pods — welche Phase?) → describe mit Events (Scheduling? Image? Probes? Last State/Exit Code?) → Logs, bei Restarts --previous → exec bzw. kubectl debug für Container ohne Shell → bei Verdacht auf Umfeld: Node-Conditions, Namespace-Events, Netzwerk (Lektion 14). Wer dazu Exit Code 137 = OOM und den Unterschied „App crasht selbst vs. Probe killt sie” parat hat, wirkt erfahren.
Typische Follow-ups:
- „Pod ist Pending — die drei häufigsten Ursachen?” — Unerfüllbare Requests (kein Node mit genug freien Ressourcen), nicht tolerierte Taints/Affinity ohne Match, ungebundener PVC; alles steht in den describe-Events.
- „CrashLoopBackOff — was genau passiert da?” — kubelet startet den sterbenden Container mit exponentiellem Backoff neu; Ursache via
logs --previousund describe (Probes? Exit Code?). - „Wie debuggst du ein Distroless-Image?” —
kubectl debugmit Ephemeral Container und Tool-Image,--targetfür gemeinsamen Prozess-Namespace; kein Pod-Neustart nötig. - „Wie kommst du auf einen Node ohne SSH?” —
kubectl debug node/<name>startet einen privilegierten Pod mit dem Node-Dateisystem unter/host.
Zusammenfassung
Systematisches Kubernetes-Debugging ist eine Leiter mit fester Reihenfolge: Pod-Status als Weiche, describe und Events für alles vor und um den Container-Start (Scheduling, Image, Probes, OOM via Last State), logs --previous für Crash-Ursachen, exec für lebende Container und kubectl debug mit Ephemeral Containers für alles, was keine Shell hat — bis hinunter auf den Node. Die Klassiker Pending, ImagePullBackOff, CrashLoopBackOff und OOMKilled haben jeweils einen festen Diagnosepfad, den du blind gehen können solltest.
Damit hast du das technische Rüstzeug des Pfads beisammen. Die letzte Lektion macht daraus Prüfungs- und Interview-Performance: CKA-Format, kubectl-Tempo und die Standardfragen im Schnelldurchlauf.