Kubernetes zu debuggen fühlt sich am Anfang fremd an, weil man selten genau das Objekt repariert, das man selbst angelegt hat.
Man schreibt ein Deployment. Das Deployment erstellt ein ReplicaSet. Das ReplicaSet erstellt Pods. Ein Service leitet Traffic zu Pods, die über Labels ausgewählt werden. Der Scheduler wählt einen Node. Das Kubelet startet Container. Probes entscheiden, ob ein Pod Traffic bekommen soll. Während man noch die Fehlermeldung liest, versucht ein Controller weiter, den gewünschten Zustand herzustellen.
Das sind viele bewegliche Teile. Die gute Nachricht: Kubernetes hinterlässt Spuren. Sie sind nicht immer freundlich formuliert, aber meist vorhanden — Statusfelder, Events, Logs, Rollout-Historie, Endpoint-Listen und Conditions. Debugging heißt zu einem großen Teil, die nächste sinnvolle Spur zu lesen.
Das hier ist der Weg, den ich früher gern konsequenter genutzt hätte: langsam, etwas langweilig und deutlich besser, als Pods zu löschen, bis sich irgendetwas ändert.
Zuerst das mentale Modell
Kubernetes ist kein Skript-Ausführer. Es ist ein Steuerungssystem.
Man beschreibt einen gewünschten Zustand: drei Replikas dieses Container-Images, auf diesem Port, mit diesen Umgebungsvariablen. Controller vergleichen laufend Soll und Ist und versuchen, die Lücke zu schließen. Geht etwas schief, sucht man die Stelle, an der die Lücke entsteht.
Bei Workload-Problemen sieht die Kette oft so aus:
Deployment -> ReplicaSet -> Pod -> Container -> Prozess
Service -> EndpointSlice -> Pod-IPs
Ingress -> Service -> Pods
Nicht im Container anfangen, wenn noch unklar ist, ob der Container sauber gestartet ist. Nicht beim Ingress anfangen, wenn der Service keine Endpoints hat. Nicht DNS verdächtigen, wenn der Pod nicht Ready ist. Anfänger verlieren viel Zeit, weil sie zur interessantesten Ebene springen statt zur nächsten beobachtbaren.
Ich stelle mir zuerst drei Fragen:
- Was hat sich geändert?
- Was ist das kleinste kaputte Objekt, das ich benennen kann?
- Was weiß Kubernetes bereits darüber?
Die dritte Frage zählt. Kubernetes weiß oft mehr als die erste Vermutung.
Den Namespace zuerst bestätigen
Der langweiligste Kubernetes-Fehler ist auch einer der häufigsten: der falsche Namespace.
kubectl get namespaces
kubectl get pods -n my-app
kubectl config view --minify --output 'jsonpath={..namespace}'; echo
Ist im aktuellen Kontext kein Namespace gesetzt, nutzt kubectl meist default. Viele produktive Workloads liegen dort nicht. Beim Debugging bin ich lieber explizit:
kubectl get deploy,rs,pod,svc -n my-app
Der Befehl gibt eine erste Karte. Gibt es Deployments? Wurden ReplicaSets erzeugt? Sind Pods da? Gibt es einen Service? Passen gewünschte und verfügbare Replikas ungefähr zusammen?
Wenn die Antwort lautet, das Objekt sei nicht sichtbar: stoppen. Namespace, Schreibweise, Helm Release, GitOps-Sync und den richtigen Cluster prüfen.
Rollout lesen, bevor man Logs liest
Bei Deployments lautet die erste nützliche Frage: Ist der Rollout durch?
kubectl rollout status deployment/web -n my-app
kubectl get deployment web -n my-app
kubectl describe deployment web -n my-app
kubectl describe ist gesprächig — am Anfang ist das hilfreich. Unten stehen Conditions. Dort tauchen Hinweise wie ProgressDeadlineExceeded, MinimumReplicasUnavailable oder ReplicaSetUpdated auf. Das ist noch keine vollständige Diagnose, zeigt aber das nächste Objekt.
Danach ReplicaSet und Pods:
kubectl get rs -n my-app
kubectl get pods -n my-app -l app=web -o wide
-o wide ergänzt Node-Namen und Pod-IPs. Das hilft, wenn alle fehlerhaften Pods auf demselben Node liegen oder wenn man später Service-Endpunkte mit Pod-IPs vergleicht.
Ein häufiger Irrtum: „Das Deployment läuft, also ist die Anwendung in Ordnung.“ Ein Deployment kann existieren, während jeder Pod abstürzt. Entscheidend sind verfügbare Replikas und der Ready-Zustand der Pods — nicht nur die Existenz des Deployment-Objekts.
Pod-Phasen sind Überschriften, keine Erklärung
kubectl get pods zeigt Status wie Pending, Running, CrashLoopBackOff, ImagePullBackOff, ErrImagePull, CreateContainerConfigError oder Completed. Das sind Überschriften. Der Text steht in describe.
kubectl describe pod web-abc123 -n my-app
Drei Bereiche sind besonders wichtig:
Conditions. Sie erklären Scheduling, Initialisierung, Readiness und Container-Zustand.
Container state. Dort sieht man, ob der Container wartet, läuft oder beendet wurde. Bei beendeten Containern sind Exit Code, Grund, Startzeit und Endzeit interessant.
Events. Oft der schnellste Weg zur Ursache. Fehlgeschlagenes Scheduling, Image-Pull-Probleme, Mount-Fehler, Probe-Fehler und fehlende Secrets stehen meist hier.
Typische Beispiele:
Failed to pull image "example/web:v12": not found
Der Image-Tag ist falsch, die Registry ist nicht erreichbar oder Zugangsdaten fehlen.
0/3 nodes are available: 3 Insufficient cpu.
Der Pod ist nicht „kaputt“. Der Cluster kann ihn mit den angeforderten Ressourcen gerade nicht einplanen.
Error: secret "web-config" not found
Der Container startete nicht, weil Konfiguration aus der Pod-Spezifikation in diesem Namespace fehlt.
Darum ist Pod-Löschen selten ein guter erster Schritt. Verweist die Spezifikation weiterhin auf ein fehlendes Secret, scheitert der nächste Pod genauso.
Logs beantworten Anwendungsfragen
Logs helfen, sobald klar ist, dass der Container gestartet ist. Sie beantworten: Was hat der Prozess gesagt? Nicht jede Kubernetes-Frage.
kubectl logs deployment/web -n my-app
kubectl logs pod/web-abc123 -n my-app
kubectl logs pod/web-abc123 -n my-app -c app
kubectl logs pod/web-abc123 -n my-app --previous
--previous ist bei Crash-Loops wichtig. Der aktuelle Container ist vielleicht gerade neu gestartet und hat noch keine Logs. Die vorige Instanz enthält dann den Stacktrace.
Bei Pods mit mehreren Containern -c angeben. Sidecars verwirren schnell, weil kubectl logs pod/name nicht unbedingt den Container zeigt, den man untersuchen will.
Logs helfen bei Problemen in der Anwendung: fehlende Umgebungsvariablen, gescheiterte Migrationen, verweigerte Verbindungen, falsche Zugangsdaten, Panics oder Startup-Fehler. Events helfen bei Kubernetes-Problemen: Scheduling, Mounts, Image Pulls und Probes.
CrashLoopBackOff verstehen
CrashLoopBackOff heißt nicht, dass Kubernetes abgestürzt ist. Kubernetes hat den Container gestartet, der Prozess ist beendet, und Kubernetes wartet vor dem nächsten Versuch. Die Wartezeit wächst, damit keine enge Restart-Schleife entsteht.
Die letzte Beendigung prüft man so:
kubectl describe pod web-abc123 -n my-app
kubectl logs web-abc123 -n my-app --previous
Exit Code 0 kann für einen Dauerlauf-Dienst trotzdem falsch sein. Der Prozess endete erfolgreich — ein Deployment erwartet aber, dass er weiterläuft. Vielleicht startet das Image nur einen einmaligen Befehl. Vielleicht überschreibt command im Manifest den eigentlichen Serverstart.
Exit Code 1 deutet oft auf einen Anwendungsfehler. Exit Code 137 bedeutet häufig, dass der Prozess beendet wurde, oft wegen überschrittenem Memory-Limit. Prüfen kann man das mit:
kubectl top pod web-abc123 -n my-app
kubectl describe pod web-abc123 -n my-app
kubectl top braucht metrics-server. Ist er nicht installiert, bleiben Events, Container-Status und Plattform-Metriken.
Probes können falsche Rätsel erzeugen
Readiness- und Liveness-Probes sind nützlich — und eine häufige selbst gebaute Fehlerquelle.
Readiness beantwortet: Soll dieser Pod Traffic bekommen?
Liveness beantwortet: Soll dieser Container neu gestartet werden?
Startup-Probes geben langsamen Anwendungen Zeit zum Booten, bevor Liveness überhaupt urteilt.
Ein einfaches Muster:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: app
image: ghcr.io/example/web:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Schlägt Readiness fehl, kann der Pod Running, aber nicht Ready sein. Der Service schickt dann keinen Traffic dorthin. Schlägt Liveness fehl, startet Kubernetes den Container neu. Eine zu aggressive Liveness-Probe kann eine langsame Anwendung immer wieder beenden, bevor sie fertig gestartet ist.
Das zeigen Events deutlich:
kubectl describe pod web-abc123 -n my-app
Bei wiederholten Probe-Fehlern sollte man nicht sofort blind die Delays erhöhen. Zuerst Pfad, Port, Schema und Abhängigkeiten prüfen — ob der Endpunkt etwa auf eine langsame Datenbank wartet.
Services scheitern leise bei falschen Selectors
Ein Service bietet stabiles Networking vor wechselnden Pods. Er weiß nicht magisch, welche Pods dazugehören. Er nutzt Selectors.
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080
Der Selector muss zu den Pod-Labels passen:
kubectl get pods -n my-app --show-labels
kubectl get svc web -n my-app -o yaml
kubectl get endpoints web -n my-app
kubectl get endpointslice -n my-app -l kubernetes.io/service-name=web
Hat der Service keine Endpoints, gibt es kein Ziel für Traffic. Häufige Ursachen: falsche Labels, nicht bereite Pods oder ein Service, der aus Versehen eine andere Version auswählt.
Das ist eine typische Anfängerfalle, weil der Service selbst gesund aussieht. Kubernetes erstellt problemlos einen Service, dessen Selector nichts findet.
kubectl exec gezielt nutzen
kubectl exec ist nützlich — ich behandle es als präzises Werkzeug, nicht als Dauerzustand.
kubectl exec -n my-app deploy/web -- printenv
kubectl exec -n my-app deploy/web -- wget -qO- http://localhost:8080/ready
Manche Images sind minimal und enthalten weder sh noch curl noch wget. Gut für Sicherheit, nervig beim Debugging. In neueren Clustern können ephemere Debug-Container helfen:
kubectl debug -n my-app pod/web-abc123 -it --image=busybox:1.36 --target=app
Ob das erlaubt ist, hängt von der Cluster-Policy ab. In abgesicherten Umgebungen kann es deaktiviert sein — dann Absicht, kein Bug.
Eine Reihenfolge für den Anfang
Wenn ich feststecke, gehe ich zu dieser Reihenfolge zurück:
- Cluster und Namespace bestätigen.
kubectl get deploy,rs,pod,svc -n <namespace>.- Rollout-Status des Deployments prüfen.
- Den fehlerhaften Pod beschreiben.
- Events lesen, bevor etwas geändert wird.
- Logs lesen — bei Restarts auch
--previous. - Service-Endpunkte prüfen, wenn kein Traffic ankommt.
- Probes, Config-Referenzen, Image-Pull-Status und Ressourcenlimits prüfen.
- Eine Sache ändern, dann beobachten.
Der letzte Punkt ist keine Motivationsfloskel. Er ist praktisch. Ändert man Image-Tag, Probe-Pfad, Ressourcenlimits und Service-Selector gleichzeitig, behebt man vielleicht das Problem und lernt nichts. Schlimmer: Man baut das nächste Problem ein und verdeckt das erste.
Kubernetes-Debugging wird leichter, wenn man den Cluster nicht als Blackbox behandelt. Er wirkt eher wie ein System, das ständig Berichte ablegt — lang, manchmal sperrig, nicht immer in der gewünschten Reihenfolge. Für viele Workload-Probleme am Anfang reichen sie trotzdem, um aus Panik den nächsten sinnvollen Befehl zu finden.