Alle Rezepte
Kubernetes Troubleshooting · Pods bis Control Plane · Entscheidungsbaum

Fehlersuche nach Plan:
vom Symptom zur Ursache

Acht Abschnitte, sechs Ressourcentypen, immer dasselbe Muster: ein Einstiegsbefehl, eine Verzweigung nach Status, pro Zweig der Diagnosebefehl und der Fix. Wer den Baum kennt, rät nicht mehr, sondern schließt aus. Befehle sind gegenüber der Vorlage auf den aktuellen Stand gebracht.

0MethodeEinstieg, Reihenfolge, Grundregeln 1PodsPending, ImagePull, CrashLoop 2NodesNotReady, Pressure, kubelet 3ServicesSelector, EndpointSlices, DNS 4Ingress404, TLS, Hostname 5PVC / StoragePending, Mount, Terminating 6Control Planeapiserver, etcd, CoreDNS 7AblaufTriage, Fix, Verifikation
0

Methode: erst einordnen, dann graben

Fehlersuche unter Druck scheitert selten am fehlenden Wissen, sondern an der Reihenfolge. Der Einstieg ist immer derselbe: Status abfragen, Symptom einem Zweig zuordnen, erst dann Logs öffnen.

Einstiegsbefehl je Ressource

SymptomEinstiegAbschnitt
Anwendung startet nichtkubectl get pods1 · Pods
Pods verschwinden oder starten nirgendskubectl get nodes2 · Nodes
Service antwortet nichtkubectl get svc, EndpointSlices3 · Services
Von außen nicht erreichbarkubectl get ingress4 · Ingress
Pod hängt beim Volumekubectl get pvc5 · PVC
kubectl selbst zicktkubectl get --raw '/readyz?verbose'6 · Control Plane

Fünf Grundregeln

  1. Einfach anfangenget und describe zeigen den Zustand oft schon vollständig.
  2. Den Logs folgenLogs und Events erzählen, was passiert ist – meist auch, was zu tun ist.
  3. Konfiguration vor KomplexitätDie Mehrheit der Störungen sind Fehlkonfigurationen, keine Bugs: YAML, Labels, Selectors, Limits.
  4. Tiefer graben, wenn nötigtop, port-forward, exec und debug für Laufzeitverhalten.
  5. VorbeugenRequests/Limits, Probes und Monitoring verhindern die Wiederholung.

Die immer gleiche Kette

  • get – welcher Status steht da wirklich
  • describe – Events und Conditions am Objekt
  • events – Zeitleiste im Namespace
  • logs – nur wenn der Container überhaupt gestartet ist
  • exec / debug – zuletzt, wenn die Sicht von innen fehlt
Triage in der Notaufnahme – erst wird gesichtet und einsortiert, dann behandelt. Wer sofort das Röntgengerät anwirft, verliert Zeit am falschen Patienten.
Achtung: Ein grüner Befehl ist kein grüner Zustand. kubectl apply bestätigt nur die Annahme des Manifests, Running sagt nichts über die Fachfunktion, und get svc zeigt einen Service auch dann, wenn kein einziger Pod dahintersteht. Jeder der folgenden Abschnitte hat genau so einen Fall.
1

Pods: fünf Zweige nach Status

Der häufigste Einstieg. Die Spalte STATUS entscheidet, welcher Zweig überhaupt sinnvoll ist – und schließt gleichzeitig die anderen vier aus.

Einstieg und Verzweigung

Ohne diesen ersten Blick ist jede weitere Suche Raten. Wichtig sind beide Spalten: STATUS und READY.

kubectl get pods -n <namespace> -o wide
StatusBedeutungZweig
Pendingnicht platziert oder Volume fehltA
ImagePullBackOff / ErrImagePullImage kommt nicht auf den NodeB
CrashLoopBackOffContainer startet und stirbt wiederholtC
Running, aber READY 0/1Probe schlägt fehl oder Anwendung antwortet nichtD
Error / UnknownNode oder kubelet meldet nicht sauberE
Der Status ist das Fieberthermometer – er sagt nicht, was fehlt, aber er sagt zuverlässig, in welchem Zimmer man zu suchen anfängt.

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

Pod kubectl get pods Pending Langfassung öffnen Requests passen auf keinen Node nodeSelector oder Affinity ohne Treffer Taint ohne Toleration PVC noch nicht gebunden ImagePullBackOff Langfassung öffnen Name oder Tag falsch Pull-Secret fehlt oder falscher Namespace Registry vom Node nicht erreichbar CrashLoopBackOff Langfassung öffnen Anwendungsfehler, Config oder Env OOMKilled am Memory-Limit Exit 137 = SIGKILL, 143 = SIGTERM Running, READY 0/1 Langfassung öffnen Readiness-Probe erfüllt nicht Pfad, Port oder Startzeit der Probe Error oder Unknown Langfassung öffnen Node meldet nicht mehr weiter in Abschnitt 2
Zweig A · Pending: der Pod findet keinen Platz

Pending heißt fast immer: Der scheduler hat keine Entscheidung getroffen. Der Grund steht als Event am Pod, nicht im Log – ein Log gibt es noch gar nicht, weil kein Container existiert.

kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --field-selector reason=FailedScheduling

Zu wenig CPU oder Memory: Entscheidend ist nicht die Auslastung, sondern die Summe der Requests. Ein Node kann bei 20 % CPU-Last voll belegt sein.

# tatsächliche Last (benötigt metrics-server)
kubectl top nodes
# reservierte Requests pro Node
kubectl describe node <node> | grep -A 8 "Allocated resources"

Lösung: Requests senken oder Kapazität schaffen (Node hinzufügen, andere Workloads verschieben).

nodeSelector oder Affinity passt nicht: Der Pod verlangt Labels, die kein Node trägt.

kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.nodeSelector}'
kubectl get nodes --show-labels

Lösung: Node-Labels ergänzen oder den Selector korrigieren.

Taints ohne passende Toleration: Der Node wehrt den Pod aktiv ab.

kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

Lösung: Toleration am Pod ergänzen oder den Taint entfernen, wenn er nicht gewollt war.

Volume fehlt: Wenn describe auf ein ungebundenes PVC zeigt, liegt die Ursache in Abschnitt 5, nicht beim scheduler.

Zweig B · ImagePullBackOff und ErrImagePull

Die genaue Fehlermeldung der Registry steht in den Events des Pods – sie unterscheidet die drei möglichen Ursachen.

kubectl describe pod <pod> -n <namespace>

„not found": Name oder Tag stimmen nicht. Prüfen und im Deployment korrigieren, nicht am laufenden Pod.

„unauthorized": Pull-Secret fehlt, ist falsch oder liegt im falschen Namespace.

kubectl get secrets -n <namespace>
kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.imagePullSecrets}'

„connection refused" oder Timeout: Die Registry ist vom Node aus nicht erreichbar – Netz, Proxy, DNS oder Firewall. Der Test gehört auf den Node, nicht auf den Laptop.

# Debug-Pod auf dem Node, Host-Dateisystem liegt unter /host
kubectl debug node/<node> -it --image=busybox
wget -S --spider https://<registry-host>/v2/
# auf OpenShift stattdessen:
oc debug node/<node>
Achtung: Pull-Secrets sind namespace-lokal. Ein korrekt angelegtes Secret im falschen Namespace erzeugt keine Fehlermeldung beim Anlegen – der Pod scheitert später mit „unauthorized", und die Suche läuft in Richtung Registry-Berechtigungen, obwohl nur das Secret am falschen Ort liegt.
Zweig C · CrashLoopBackOff: der Container stirbt immer wieder

Hier gibt es bereits ein Log – und zwar zwei: das des aktuellen und das des vorherigen Versuchs. Das interessante ist meist das vorherige, weil der aktuelle Container oft noch gar nicht gestartet ist.

kubectl logs <pod> -n <namespace>
kubectl logs <pod> -n <namespace> --previous

Anwendungsfehler: Environment-Variablen, ConfigMaps, Abhängigkeiten prüfen und im Deployment beziehungsweise in ConfigMap/Secret korrigieren.

OOMKilled: Der Container hat sein Memory-Limit gerissen. Der Beweis steht im letzten Terminierungsgrund, nicht in den Logs.

kubectl get pod <pod> -n <namespace> \
  -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
kubectl describe pod <pod> -n <namespace> | grep -A 5 "Limits:"

Lösung: Memory-Limit anheben oder den Speicherverbrauch der Anwendung begrenzen (Heap-Größe, Worker-Anzahl).

Exit Code 137 oder 143: 137 ist 128+9 (SIGKILL), 143 ist 128+15 (SIGTERM). Das sind Signale, keine Diagnosen – sie sagen, dass jemand beendet hat, nicht wer.

Achtung: Exit Code 137 wird reflexhaft als OOM gelesen. Genauso häufig stammt er von einer fehlgeschlagenen Liveness-Probe: Das kubelet killt den Container hart, der Speicher war nie das Problem. Der Unterschied steht in lastState.terminated.reason – OOMKilled oder Error – und in den Events. Wer stattdessen das Memory-Limit erhöht, verschiebt den Neustart nur nach hinten.
Zweige D und E · Running ohne Funktion, Error, Unknown

READY 0/1 bei Running: Der Container läuft, die Readiness-Probe ist nicht erfüllt. Pfad, Port oder Startzeit der Probe passen nicht zur Anwendung.

kubectl describe pod <pod> -n <namespace> | grep -A 3 Readiness

READY 1/1, aber fachlich kaputt: Am Service vorbei direkt auf den Pod testen. Antwortet er hier korrekt, liegt der Fehler im Service oder Ingress – nicht in der Anwendung.

kubectl port-forward pod/<pod> 8080:<container-port> -n <namespace>
curl -sv localhost:8080

Error oder Unknown: Meist meldet der Node nicht mehr. Erst die Objektsicht, dann weiter in Abschnitt 2.

kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod>
2

Nodes: NotReady oder unauffällig überlastet

Zwei Zweige: Der Node meldet sich gar nicht mehr sauber, oder er meldet Ready und die Pods scheitern trotzdem. Der zweite Fall ist der unangenehmere.

Einstieg

Die Spalte STATUS teilt den Baum, -o wide liefert gleich Kernel und Runtime-Version für den Fall, dass ein einzelner Node aus der Reihe fällt.

kubectl get nodes -o wide
  • NotReady – Zweig A: Conditions und kubelet
  • Ready, aber Pods scheitern – Zweig B: Ressourcen und Belegung

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

Node kubectl get nodes NotReady Langfassung öffnen DiskPressure, Platte voll MemoryPressure kubelet gestoppt oder in Schleife Ready, Pods scheitern Langfassung öffnen Summe der Requests ausgeschöpft Pod-Limit des Nodes erreicht
Zweig A · Node NotReady

Die Conditions am Node benennen die Ursache direkt – Speicher, Platte, PID-Druck oder fehlende Netzwerkkonfiguration.

kubectl describe node <node> | grep -A 10 Conditions

DiskPressure oder MemoryPressure: Der Blick gehört auf den Node selbst. Ohne SSH führt der Weg über einen Debug-Pod mit Zugriff auf das Host-Dateisystem.

kubectl debug node/<node> -it --image=busybox
# im Debug-Pod liegt das Host-Dateisystem unter /host
chroot /host df -h
chroot /host free -h

Lösung: Platte aufräumen (Images, Logs), Workloads verschieben, im Zweifel kubectl drain und den Node warten.

kubelet läuft nicht: Auf dem Node prüfen und die Logs mitlesen.

chroot /host systemctl status kubelet
chroot /host journalctl -u kubelet -f

Lösung: kubelet neu starten. Kommt es danach erneut hoch und fällt wieder aus, liegt die Ursache eine Ebene tiefer – Zertifikate, Container-Runtime oder Netzwerk-Plugin.

Zweig B · Node Ready, Pods scheitern trotzdem

Ready heißt nur, dass das kubelet meldet. Ob noch Platz für neue Pods ist, steht in der Belegung – und die berechnet sich aus Requests, nicht aus Auslastung.

kubectl describe node <node> | grep -A 8 "Allocated resources"
kubectl top node <node>

Auch das Pod-Limit pro Node ist eine harte Grenze, die nichts mit CPU oder Memory zu tun hat.

kubectl get node <node> -o jsonpath='{.status.allocatable.pods}'

Lösung: Pods abziehen, Requests korrigieren oder Kapazität ergänzen.

Achtung: Ein Node kann Ready melden und trotzdem seit Stunden keine Pods mehr annehmen, weil kubectl top 30 % Auslastung zeigt, die Summe der Requests aber bei 100 % liegt. Die beiden Zahlen messen Verschiedenes: Auslastung ist Ist, Allocated ist reserviert. Wer nur auf top schaut, sucht die Ursache im Cluster-Netz statt im Sizing.
Requests sind reservierte Tische, Auslastung sind besetzte Stühle – ein Restaurant mit lauter Reservierungen ist voll, auch wenn halb leer aussieht.
3

Services: Selector, Endpunkte, Erreichbarkeit

Ein Service ist nur eine Regel mit einem Label-Selector. Ob dahinter etwas steht, sagt nicht das Service-Objekt, sondern die Liste seiner Endpunkte.

Einstieg

EndpointSlices sind der aktuelle Weg, die Zuordnung von Pods zum Service zu prüfen. Die alte Endpoints-API gilt als veraltet und schneidet bei sehr vielen Pods still ab.

kubectl get svc <service> -n <namespace>
kubectl get endpointslices -n <namespace> \
  -l kubernetes.io/service-name=<service> -o wide
  • Keine Endpunkte – Zweig A: Selector oder Pod-Zustand
  • Endpunkte vorhanden, trotzdem keine Antwort – Zweig B: Netz, Ports, DNS
Der Service ist das Klingelschild, die EndpointSlice die Namensliste dahinter – ein Schild ohne Eintrag klingelt ins Leere, ohne dass jemand einen Fehler meldet.

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

Service get svc + EndpointSlices Keine Endpunkte Langfassung öffnen Selector passt nicht zu Pod-Labels Pods sind nicht Ready Endpunkte da, keine Antwort Langfassung öffnen DNS löst nicht auf, siehe CoreDNS NetworkPolicy blockt targetPort ungleich Container-Port Typ ClusterIP, von außen nicht erreichbar
Zweig A · Keine Endpunkte

Entweder passt der Selector nicht zu den Pod-Labels, oder die Pods sind nicht Ready – nur Ready-Pods landen in der Endpunktliste.

kubectl describe svc <service> -n <namespace>
kubectl get pods -n <namespace> --show-labels
kubectl get pods -n <namespace> -l <key>=<value>

Liefert der letzte Befehl Pods, aber die EndpointSlice bleibt leer, ist der Selector richtig und die Readiness das Problem – zurück zu Abschnitt 1, Zweig D.

Lösung: Labels im Deployment oder den Selector im Service korrigieren. Der Selector eines bestehenden Service lässt sich ändern, der Selector eines Deployments nicht – dort ist ein Neuanlegen nötig.

Zweig B · Endpunkte da, Service antwortet nicht

Der Test gehört in den Cluster hinein, weil DNS und ClusterIP von außen gar nicht existieren. Ein kurzlebiger Debug-Pod reicht.

# Image mit Netzwerkwerkzeugen; in getrennten Umgebungen gespiegelt vorhalten
kubectl run debug --rm -it --restart=Never -n <namespace> \
  --image=nicolaka/netshoot -- sh

In der Shell zuerst die Namensauflösung, dann die Verbindung prüfen. telnet fehlt in den meisten Images – nc oder wget tun dasselbe.

nslookup <service>.<namespace>.svc.cluster.local
nc -zv <service>.<namespace> <port>
wget -qO- http://<service>.<namespace>:<port>/

Schlägt schon die Auflösung fehl, liegt es an CoreDNS – siehe Abschnitt 6. Antwortet DNS, aber die Verbindung nicht, sind NetworkPolicies oder ein falscher targetPort die üblichen Ursachen.

kubectl get networkpolicies -n <namespace>
kubectl get svc <service> -n <namespace> \
  -o jsonpath='{.spec.ports[*].targetPort}'

Von außen: Ein Service vom Typ ClusterIP ist von außen grundsätzlich nicht erreichbar – das ist kein Fehler, sondern die Definition.

kubectl get svc <service> -n <namespace> -o jsonpath='{.spec.type}'
Achtung: port und targetPort werden gern verwechselt. Stimmt der targetPort nicht mit dem Container-Port überein, ist der Service trotzdem grün, die EndpointSlice gefüllt und die Pods Ready – die Verbindung läuft nur in einen Timeout. Kein einziger Statuswert zeigt diesen Fehler an; er fällt erst beim Verbindungstest auf.
4

Ingress: 404, Zertifikat oder Name

Ein Ingress ist nur eine Regel für einen Controller. Ohne passenden Controller passiert schlicht nichts – und zwar geräuschlos.

Einstieg

Zuerst prüfen, ob das Objekt überhaupt bedient wird: Eine leere Spalte ADDRESS heißt, dass kein Controller die Regel übernommen hat.

kubectl get ingress -n <namespace>
kubectl describe ingress <ingress> -n <namespace>
SymptomWahrscheinliche UrsacheZweig
404 Not FoundPfad, Servicename oder Port stimmen nichtA
Zertifikatswarnung, TLS-FehlerSecret fehlt, falscher Typ oder falscher HostB
Host löst nicht aufDNS zeigt nicht auf den IngressC
Der Ingress ist der Pförtner mit Besucherliste – steht der Name nicht exakt auf der Liste, wird abgewiesen, auch wenn die Person im Gebäude längst am Platz sitzt.

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

Ingress kubectl get ingress ADDRESS bleibt leer Langfassung öffnen kein ingressClassName gesetzt keine Default-IngressClass vorhanden 404 Not Found Langfassung öffnen Pfad oder pathType passt nicht Servicename oder Port falsch Service ohne Endpunkte TLS-Fehler Langfassung öffnen Secret fehlt oder falscher Typ Host nicht im SAN des Zertifikats Zertifikat abgelaufen Host löst nicht auf Langfassung öffnen DNS-Eintrag fehlt zeigt nicht auf den Controller
Zweig A · 404 Not Found

Der Ingress verweist auf Service und Port. Beides muss existieren und muss Endpunkte haben – sonst antwortet der Controller mit dem Default-Backend.

kubectl get svc <service> -n <namespace>
kubectl get endpointslices -n <namespace> -l kubernetes.io/service-name=<service>

Häufigster Konfigurationsfehler ist der pathType: Exact trifft nur exakt, Prefix trifft Unterpfade. Wer /api als Exact anlegt, bekommt bei /api/v1 ein 404.

kubectl get ingress <ingress> -n <namespace> -o yaml

Lösung: Pfad, pathType, Servicename und Port angleichen.

Zweig B · TLS-Zertifikat

Das im Ingress referenzierte Secret muss im selben Namespace liegen, vom Typ kubernetes.io/tls sein und den angefragten Hostnamen abdecken.

kubectl get secret <tls-secret> -n <namespace> \
  -o jsonpath='{.type}'

Ob das Zertifikat wirklich passt, zeigt nur der Blick auf den Inhalt – Gültigkeit und die Namen im SAN-Feld.

kubectl get secret <tls-secret> -n <namespace> \
  -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates -text | grep -A 1 "Subject Alternative Name"

Lösung: Secret neu erzeugen oder erneuern. Bei cert-manager stattdessen Certificate und Order prüfen, statt das Secret von Hand zu überschreiben – es wird sonst wieder ersetzt.

Zweig C · Host oder DNS löst nicht auf

Getestet wird von dort, wo der Nutzer steht – nicht aus dem Cluster. Die Antwort muss auf die Adresse des Ingress-Controllers zeigen.

nslookup <hostname>
dig +short <hostname>

Zum Abgleich die Adresse, die der Controller veröffentlicht:

kubectl get ingress <ingress> -n <namespace> \
  -o jsonpath='{.status.loadBalancer.ingress[*]}'

Lösung: DNS-Eintrag korrigieren. Bei Wildcard-Domains prüfen, ob der Eintrag den konkreten Host wirklich abdeckt.

Achtung: Fehlt ingressClassName und ist keine Default-IngressClass gesetzt, fühlt sich kein Controller zuständig. Das Objekt existiert, kubectl get ingress zeigt es an, es gibt keine Fehlermeldung und keine Events – nur die Spalte ADDRESS bleibt leer und alle Anfragen laufen ins Nichts. Der erste Blick bei jedem Ingress-Problem gehört deshalb dieser Spalte.
5

PVC und Storage: Pending, Mount, Terminating

Ein PVC ist ein Anspruch, kein Speicher. Zwischen Anspruch und tatsächlichem Volume liegen StorageClass, Provisioner und – im Fehlerfall – ein Finalizer.

Einstieg

kubectl get pvc -n <namespace>
  • Pending – Zweig A: kein passendes Volume
  • Bound, aber Pod mountet nicht – Zweig B: Attach oder Rechte
  • Terminating – Zweig C: Finalizer
PVC ist der Schließfachschein, PV das Schließfach – der Schein allein bringt nichts, wenn kein Fach der geforderten Größe frei ist.

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

PVC kubectl get pvc Pending Langfassung öffnen keine oder falsche StorageClass Provisioner läuft nicht WaitForFirstConsumer, normal bis ein Pod anfordert Bound, Mount scheitert Langfassung öffnen ReadWriteOnce noch am alten Node Rechte am Dateisystem, fsGroup VolumeAttachment hängt Terminating Langfassung öffnen Finalizer, Pod nutzt das Volume noch
Zweig A · PVC bleibt Pending

Der Grund steht als Event am PVC. Typisch sind: keine StorageClass angegeben und keine Default-Klasse gesetzt, ein Provisioner, der nicht läuft, oder eine angeforderte Größe, für die kein PV existiert.

kubectl describe pvc <pvc> -n <namespace>
kubectl get storageclass

Lösung: passende StorageClass setzen, Default-Klasse definieren oder bei statischem Provisioning ein PV anlegen, das Größe und Access-Mode erfüllt.

Achtung: Nicht jedes Pending ist ein Fehler. Bei StorageClasses mit volumeBindingMode: WaitForFirstConsumer ist Pending der vorgesehene Zustand, bis ein Pod das PVC anfordert – so wird das Volume in der richtigen Zone erzeugt. Wer hier „repariert" und auf Immediate umstellt, produziert Volumes in Zonen, in denen später kein Pod mehr laufen kann.
Zweig B · Bound, aber der Pod mountet nicht

Der Fehler steht nicht am PVC, sondern in den Events des Pods – dort meldet das kubelet, woran das Mounten scheitert.

kubectl describe pod <pod> -n <namespace>

Häufig: Der Access-Mode passt nicht zur Situation. Ein Volume mit ReadWriteOnce kann nur von Pods auf einem einzigen Node genutzt werden – bei einem Rolling Update wartet der neue Pod, bis der alte das Volume freigibt.

kubectl get pvc <pvc> -n <namespace> -o jsonpath='{.spec.accessModes}'
kubectl get volumeattachments | grep <pv-name>

Lösung: Volume-Attachment lösen lassen (alten Pod beenden), Rechte am Dateisystem über fsGroup setzen oder für parallelen Zugriff auf ReadWriteMany mit passendem Backend wechseln.

Zweig C · PVC hängt in Terminating

Ein Finalizer verhindert das Löschen, solange das Volume noch benutzt wird. Das ist eine Schutzfunktion, kein Defekt.

kubectl get pvc <pvc> -n <namespace> -o jsonpath='{.metadata.finalizers}'
kubectl get pods -n <namespace> \
  -o custom-columns=POD:.metadata.name,PVC:.spec.volumes[*].persistentVolumeClaim.claimName

Lösung: die nutzenden Pods beenden. Der Finalizer löst sich dann von selbst.

Achtung: Den Finalizer von Hand zu entfernen, löscht das PVC sofort – und wirkt deshalb wie die Lösung. Das dahinterliegende Volume im Storage-Backend bleibt aber häufig zurück und taucht in keiner Kubernetes-Ansicht mehr auf. Nach ein paar Runden fehlt Kapazität, die niemand mehr zuordnen kann. Erst die Nutzer beenden, den Handgriff nur als letztes Mittel und mit Notiz, welches Volume betroffen war.
6

Control Plane: apiserver, etcd, CoreDNS

Wenn kubectl selbst hängt, langsam wird oder widersprüchliche Antworten liefert, liegt der Fehler nicht mehr im Workload.

Einstieg

Die Health-Endpunkte des apiservers listen jede Teilprüfung einzeln auf, etcd inbegriffen. Sie sind der Ersatz für den früher üblichen Blick auf die Componentstatuses.

kubectl get --raw '/livez?verbose'
kubectl get --raw '/readyz?verbose'
kubectl get pods -n kube-system

Auf OpenShift kommt die Sicht über die Cluster-Operatoren dazu – sie sagt in einer Zeile, welcher Teil der Plattform degradiert ist.

oc get clusteroperators
Hinweis: kubectl get componentstatuses stammt aus der Vorlage, ist seit Kubernetes 1.19 als veraltet markiert und liefert auf verwalteten Clustern häufig gar nichts oder irreführende Werte. Die livez- und readyz-Endpunkte ersetzen es vollständig.

Zweige auf einen Blick

Links die Ressource, in der Mitte der Zweig nach Status, rechts die Ursachen, die dieser Zweig abdeckt. Ein Klick auf einen Zweig öffnet die Langfassung mit den Befehlen.

Control Plane get --raw /readyz?verbose apiserver Langfassung öffnen Zertifikat abgelaufen Speicherdruck oder volle Partition langsam statt tot etcd Langfassung öffnen Mitglied ausgefallen Schreiblatenz der Datenträger CoreDNS Langfassung öffnen Pods nicht Ready Auflösung aus einem Pod testen
Zweig A · apiserver

Erreichbarkeit und Antwortzeit prüfen, bevor Komponenten neu gestartet werden. Ein langsamer apiserver sieht aus wie ein defektes Netzwerk.

kubectl cluster-info
time kubectl get --raw '/healthz'

Bei selbstverwalteten Clustern läuft der apiserver als statischer Pod; sein Log liegt beim kubelet des Control-Plane-Nodes.

kubectl logs -n kube-system -l component=kube-apiserver --tail=200

Lösung: Ursache im Log suchen – Zertifikatsablauf, Speicherdruck und volle Audit-Log-Partitionen sind die üblichen Kandidaten. Ein Neustart hilft nur, wenn die Ursache behoben ist.

Zweig B · etcd

Erst der Zustand der Pods, dann die Sicht aus etcd selbst. Die Gesundheit aller Mitglieder ist wichtiger als die eines einzelnen.

kubectl get pods -n kube-system -l component=etcd -o wide
kubectl exec -n kube-system <etcd-pod> -- \
  etcdctl endpoint health --cluster --write-out=table

Auf OpenShift liegen die etcd-Pods in einem eigenen Namespace und der Zugriff läuft über den Debug-Wrapper.

oc get pods -n openshift-etcd -l app=etcd
oc rsh -n openshift-etcd <etcd-pod> etcdctl endpoint status --write-out=table

Lösung: Bei einem einzelnen ausgefallenen Mitglied das Mitglied ersetzen, nicht den ganzen Cluster neu starten. Bei Latenzproblemen sind die Datenträger die Ursache, nicht etcd selbst.

Zweig C · CoreDNS

DNS-Probleme äußern sich als Service-Probleme. Erst die Pods, dann eine Auflösung aus dem Cluster heraus testen.

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100

Die eigentliche Prüfung gehört in einen Pod – der Weg dorthin steht in Abschnitt 3, Zweig B.

Achtung: etcd fällt selten hart aus, es wird langsam. Steigt die Schreiblatenz der Datenträger, quittiert der apiserver weiterhin alles, nur zäh – das Symptom ist ein „kubectl hängt manchmal", während sämtliche Pods Running melden und keine Komponente rot wird. Wer nur auf Ready-Status schaut, findet die Ursache nie.
7

Der Ablauf einer Störung, chronologisch

Dieselben sechs Abschnitte, diesmal als Reihenfolge: von der Meldung bis zur Vorbeugung. Die Phasen verhindern, dass man mitten im Baum die Ebene wechselt.

Die vier Phasen auf einen Blick

  1. Phase A · EinordnenSchritte 1–2: Welche Ebene ist betroffen, welcher Status steht da.
  2. Phase B · EingrenzenSchritte 3–4: Zweig wählen, Diagnosebefehl des Zweigs ausführen.
  3. Phase C · BehebenSchritt 5: an der Quelle korrigieren, nicht am laufenden Objekt.
  4. Phase D · Verifizieren und vorbeugenSchritte 6–7: Wirkung nachweisen, Wiederholung verhindern.

Phase A · Einordnen

Schritt 1 · Ebene bestimmen

Die Frage lautet nicht „was ist kaputt", sondern „auf welcher Ebene fängt es an". Ein nicht erreichbarer Service kann an Pods, Service, Ingress oder DNS liegen – der Test von innen nach außen entscheidet, welcher Abschnitt zuständig ist.

# von innen nach außen: Pod, dann Service, dann Ingress
kubectl get pods,svc,ingress -n <namespace>
Schritt 2 · Status wörtlich nehmen

Pending, ImagePullBackOff, CrashLoopBackOff, NotReady, Terminating: Diese Wörter sind keine Beschreibung des Unwohlseins, sondern Zweigadressen. Wer den Status überliest und direkt zu den Logs springt, öffnet in der Hälfte der Fälle ein Log, das es noch gar nicht gibt.

Phase B · Eingrenzen

Schritt 3 · Events vor Logs

Events zeigen, was Kubernetes selbst getan oder verweigert hat – Scheduling, Pull, Mount, Probe. Logs zeigen, was die Anwendung getan hat. Vor dem ersten Containerstart existieren nur Events.

kubectl describe <ressource> <name> -n <namespace>
kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
Schritt 4 · Den Zweig zu Ende gehen

Jeder Zweig endet mit einer konkreten Ursache: falsches Label, fehlendes Secret, zu kleines Limit, falscher Port. Solange nur ein Verdacht besteht, ist der Zweig nicht zu Ende – ein Fix auf Verdacht erzeugt zwei Fehler statt einem.

Phase C · Beheben

Schritt 5 · An der Quelle korrigieren

Änderungen gehören in das Manifest, aus dem das Objekt entsteht – Deployment, ConfigMap, Helm-Werte, Git-Repository. Ein kubectl edit am Pod überlebt den nächsten Neustart nicht, in einer GitOps-Umgebung nicht einmal die nächste Synchronisation.

# nach dem Rollout: läuft die neue Generation wirklich?
kubectl rollout status deployment/<name> -n <namespace>

Phase D · Verifizieren und vorbeugen

Schritt 6 · Wirkung nachweisen

Nachweis heißt: derselbe Test, der den Fehler gezeigt hat, ist jetzt grün – nicht „der Pod ist Running". Bei Netzwerkfehlern also erneut der Verbindungstest, bei OOM ein Blick auf die Neustartzähler über eine gewisse Zeit.

kubectl get pod <pod> -n <namespace> \
  -o jsonpath='{.status.containerStatuses[*].restartCount}'
Schritt 7 · Wiederholung verhindern

Die meisten Vorfälle in diesem Baum haben eine vorbeugende Maßnahme: Requests und Limits gegen Verdrängung und OOM, Readiness- und Liveness-Probes gegen stillen Ausfall, Monitoring auf Neustartzähler und Pending-Pods gegen späte Entdeckung. Ohne diesen Schritt wird derselbe Zweig in zwei Wochen erneut durchlaufen.

Der Baum ist eine Ausschlussliste, kein Suchpfad – jeder Schritt streicht Möglichkeiten weg. Wer Zweige überspringt, spart keine Zeit, sondern verliert die Gewissheit, was es nicht war.