Überblick: wer am Deployment beteiligt ist
Kubernetes besteht aus vielen Komponenten, die miteinander reden müssen, damit am Ende ein Container läuft. Wer diesen Weg kennt, sucht Fehler nicht im falschen Log.
Die Beteiligten und ihre eine Aufgabe
| Komponente | Läuft auf | Aufgabe im Flow |
|---|---|---|
| kube-apiserver | Control Plane | einzige Tür zum Cluster, vermittelt jede Nachricht |
| etcd | Control Plane | speichert Soll- und Ist-Zustand |
| kube-controller-manager | Control Plane | vergleicht Soll und Ist, fordert Änderungen an |
| kube-scheduler | Control Plane | entscheidet, auf welchem Worker ein Pod landet |
| kubelet | jeder Worker | baut den Container und besorgt Secrets und Volumes |
| kube-proxy | jeder Worker | macht den Container über das Netz erreichbar |
| Container Engine | jeder Worker | erzeugt den Container tatsächlich |
Der Einstieg ist immer derselbe: Über kubectl geht ein Request mit einem YAML-Manifest an den kube-apiserver. Das Manifest enthält alles, was der Cluster wissen muss, um den Container aufzubauen.
Der API Call Flow in zehn Schritten
Ein einzelner Container soll deployt werden. Vom abgeschickten Request bis zur Bestätigung in etcd sind zehn Nachrichten unterwegs – die meisten davon Hin- und Rückfragen über den apiserver.
Der Flow als Kreis
In der Mitte der apiserver, ringsum die übrigen Komponenten – angeordnet in der Reihenfolge, in der sie im Ablauf zum ersten Mal drankommen. Die Zeit läuft im Uhrzeigersinn: von kubectl oben über etcd, controller-manager und scheduler bis zu kubelet und kube-proxy, und am Ende zurück zu etcd. Jeder Pfeil ist eine Nachricht, und jede Nachricht beginnt oder endet im Zentrum. Durchgezogen heißt zum apiserver, gestrichelt vom apiserver weg; die Nummern entsprechen den zehn Schritten in der Liste darunter.
Zwei Dinge werden dadurch sichtbar, die eine Aufzählung verschluckt: Keine einzige Linie verbindet zwei Komponenten direkt – wer mit wem redet, ist immer der apiserver. Und die Rückfragen haben ein Muster: Der controller-manager fragt (3) und bekommt Antwort (4), der scheduler wird gefragt (6) und antwortet (6). Schritt 9 ist der einzige Pfeil, der das Zentrum nicht berührt: Dort arbeitet das kubelet lokal mit der Container Engine. Und der Kreis schließt sich sichtbar – der letzte Pfeil landet wieder bei etcd, wo Schritt 1 begonnen hat.
Die Bestätigung an den Client geht schon nach Schritt 1 zurück, während im restlichen Kreis noch neun Nachrichten folgen. Genau das ist der Grund, warum ein grünes created nichts über den laufenden Container aussagt.
Ablauf nach dem Absenden des Manifests
- Request annehmenDer apiserver nimmt den Request entgegen und legt das Manifest in etcd ab.
- Controller informierenDer controller-manager bekommt vom apiserver die Information, dass ein neues Manifest existiert.
- Ist-Zustand erfragenDer controller-manager fragt zurück, ob der Container schon deployt ist und ob der Status dem Wunsch entspricht.
- Antwort: existiert nichtDer apiserver meldet, dass es den Container noch nicht gibt.
- Erstellung beauftragenDer controller-manager gibt dem apiserver den Befehl, den Container zu erstellen.
- Worker auswählenDer apiserver fragt den scheduler, auf welchem Worker der Container laufen kann; der scheduler antwortet mit dem Ziel.
- Kubelet beauftragenDer apiserver schickt die nötigen Teile des Manifests an das kubelet des ausgewählten Workers.
- Netz vorbereitenZusätzlich informiert der apiserver jeden kube-proxy, dass der Container auf diesem Worker verfügbar gemacht wird.
- Container bauenDas kubelet lässt die Container Engine den Container erzeugen und besorgt alle angeforderten Ressourcen wie Secrets oder Volumes.
- Erfolg zurückmeldenDas kubelet meldet das erfolgreiche Deployment an den apiserver, der die Information in etcd schreibt.
kubectl apply quittiert mit created, sobald Schritt 1 durch ist – das Manifest liegt in etcd, mehr nicht. Die Schritte 2 bis 10 laufen asynchron und ohne Rückkanal zur Shell. Ein Pod kann stundenlang Pending bleiben, ohne dass der Befehl je einen Fehler ausgegeben hätte. Grüne Ausgabe heißt nur „angenommen", nie „läuft".Speichenarchitektur: alles über den Hub
In jedem der zehn Schritte taucht der apiserver auf. Das ist kein Zufall, sondern das Bauprinzip: Kubernetes nutzt eine Speichenarchitektur, das Hub-and-Spoke-Pattern.
Hub
Ein zentraler Punkt, durch den alle Anfragen und Nachrichten fließen. Er vermittelt und steuert den Datenverkehr zwischen den Endpunkten. In Kubernetes ist das der kube-apiserver.
Spokes
Die Speichen sind die Endpunkte am Hub. Jede ist für eine bestimmte Funktion zuständig und interagiert mit den anderen ausschließlich über den Hub – scheduler, controller-manager, kubelet, kube-proxy.
Was das Modell bringt
- Ein Kontakt pro Komponente – ein neuer Spoke muss nur den Hub kennen, nicht alle anderen Dienste
- Verteilen ist Hub-Sache – kommt ein Dienst hinzu, nimmt der Hub dessen Nachrichten entgegen und bringt sie zu den anderen
- Überwachung an einer Stelle – alle Transaktionen laufen über den Hub, das erleichtert die Fehleranalyse
- Zugriffskontrolle an einer Stelle – Authentifizierung, Autorisierung und Auditing sitzen dort, wo ohnehin alles vorbeikommt
Direkte Kommunikation und ihre Grenze
Direkt von Dienst zu Dienst wäre erst einmal schneller – ein Vermittler weniger. Bei vielen kleinen Diensten kippt der Vorteil trotzdem, und zwar rein rechnerisch.
Verbindungswege bei direkter Kommunikation
Jeder Dienst muss jedem vorhandenen Dienst bekannt gemacht werden. Die Zahl der Wege wächst quadratisch, während sie im Hub-Modell mit jedem Dienst nur um eine Anbindung steigt.
| Dienste | Wege direkt | Wege über den Hub |
|---|---|---|
| 3 | 3 | 3 |
| 5 | 10 | 5 |
| 10 | 45 | 10 |
| 20 | 190 | 20 |
Schon bei fünf Diensten sind es zehn Verbindungswege – jeder zusätzliche Dienst erhöht die Komplexität weiter. In der Speichenarchitektur spricht jeder Spoke nur mit dem Hub, und der kümmert sich um das Verteilen.
Separation of Concerns: eine Aufgabe pro Komponente
Die Menge an Nachrichten wirkt zunächst erschreckend kompliziert. Sie ist die direkte Folge davon, dass jede Komponente nur eine Sache tut – und genau das macht die Fehlersuche berechenbar.
Symptom, Zuständigkeit, Fundort
| Frage | Zuständig | Wo nachsehen |
|---|---|---|
| Ist mein Wunsch überhaupt angekommen? | apiserver, etcd | Objekt zurücklesen |
| Warum passiert nichts weiter? | controller-manager | Events, Controller-Logs |
| Warum bleibt der Pod Pending? | scheduler | Event FailedScheduling |
| Warum startet der Container nicht? | kubelet | describe pod, Kubelet-Log am Node |
| Warum ist nichts erreichbar? | kube-proxy | EndpointSlices des Service |
Zuständigkeit prüfen statt raten
Der schnellste Test, ob Schritt 6 schon durch ist: Ein Pod ohne Node-Zuweisung hat den scheduler noch nicht passiert – dann ist jede Suche im Kubelet-Log verschwendete Zeit.
# leer = noch keine Scheduling-Entscheidung getroffen
kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.nodeName}'
Steht dort ein Node, liegt die Zuständigkeit ab hier beim kubelet dieses Workers – und der Rest der Analyse findet dort statt.
kubectl describe pod <pod> -n <namespace>
etcd: nur einer darf schreiben
Die zentrale Kommunikation stellt sicher, dass nicht jeder Dienst selbst in etcd schreibt. Das trägt zur Konsistenz bei und reduziert Fehler – gerade weil etcd die kritischste Komponente im Cluster ist.
Was das Schreibmonopol bewirkt
- Konsistenz – ein Schreiber, eine Reihenfolge, keine widersprüchlichen Zustände
- Validierung an einer Stelle – was der apiserver ablehnt, landet nie im Speicher
- Nachvollziehbarkeit – jede Zustandsänderung hat denselben Weg und dieselbe Protokollstelle
- Kleinere Angriffsfläche – Komponenten brauchen kein etcd-Zugriffsrecht, nur ein apiserver-Recht
Running melden und keine Komponente rot wird. Wer nur auf Ready-Status schaut, sieht die Ursache nie.Gesundheit der Control Plane abfragen
Vor der Suche in einzelnen Logs lohnt der Blick auf die Health-Endpunkte des apiservers: Sie listen die Teilprüfungen einzeln auf, inklusive etcd.
kubectl get --raw '/livez?verbose'
kubectl get --raw '/readyz?verbose'
Auf OpenShift laufen die etcd-Instanzen als statische Pods im eigenen Namespace – dort stehen auch die Logs mit den Latenzwarnungen.
oc get pods -n openshift-etcd -o wide
Den Flow selbst mitlesen
Der Ablauf ist nicht nur Theorie: Fast jeder Schritt hinterlässt eine Spur, die sich mit Bordmitteln sichtbar machen lässt.
Spur je Schritt
| Schritt | Sichtbar als |
|---|---|
| 1 · in etcd abgelegt | Objekt lässt sich zurücklesen, resourceVersion gesetzt |
| 2–5 · Controller aktiv | Events wie SuccessfulCreate |
| 6 · Worker gewählt | Event Scheduled, .spec.nodeName gefüllt |
| 7–9 · Kubelet baut | Events Pulling, Created, Started |
| 8 · Netz bereit | EndpointSlice enthält die Pod-IP |
| 10 · Rückmeldung | .status.phase wechselt auf Running |
Der Ablauf als Zeitleiste
Events sind die kompakteste Sicht auf die Schritte 2 bis 10, weil sie mit Zeitstempel zeigen, welche Komponente wann etwas gemeldet hat.
kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
Wer sehen will, dass wirklich jeder Aufruf beim apiserver landet, lässt sich die HTTP-Requests von kubectl ausgeben – Schritt 1 des Flows, live.
kubectl get pods -n <namespace> -v=6
Für Schritt 8 zählt nicht der Service, sondern ob die Pod-IP tatsächlich in der Endpunktliste steht. Ein Service ohne passende EndpointSlice ist ein Wegweiser ins Leere.
kubectl get endpointslices -n <namespace> -l kubernetes.io/service-name=<service>
.status.containerStatuses[].restartCount und ein zentrales Log-Backend, nicht die Events.Kompletter Durchlauf in vier Phasen
Dieselben zehn Schritte, gruppiert nach Zuständigkeit – so lässt sich im Fehlerfall die Phase eingrenzen, bevor man Logs öffnet.
Die vier Phasen auf einen Blick
- Phase A · AnnehmenSchritte 1–2: apiserver validiert, schreibt nach etcd, informiert den controller-manager.
- Phase B · AbgleichenSchritte 3–5: controller-manager vergleicht Soll und Ist und beauftragt die Erstellung.
- Phase C · PlatzierenSchritt 6: scheduler wählt den Worker aus.
- Phase D · Bauen und meldenSchritte 7–10: kubelet baut, kube-proxy bereitet das Netz vor, der Erfolg geht zurück in etcd.
Phase A · Annehmen
Schritt 1 · Manifest kommt an und wird gespeichert
Der apiserver nimmt den Request an, prüft Authentifizierung, Autorisierung und Schema und legt das Manifest in etcd ab. Ohne diesen Schritt existiert der Wunsch nirgends – wer ihn überspringt gedanklich, sucht später Container, die nie beauftragt wurden.
# zurücklesen: was tatsächlich gespeichert wurde, nicht was gemeint war
kubectl get pod <pod> -n <namespace> -o yaml
Schritt 2 · Controller erfährt vom neuen Manifest
Der controller-manager wird nicht selbst aktiv, weil er etcd liest – er bekommt die Information vom apiserver. Das ist der Grund, warum ein überlasteter apiserver den gesamten Cluster träge macht, obwohl die Daten längst gespeichert sind.
Phase B · Abgleichen
Schritte 3 bis 5 · Soll gegen Ist
Der controller-manager fragt beim apiserver nach, ob der Container schon existiert und ob der aktuelle Status dem gewünschten entspricht. Die Antwort lautet hier: gibt es noch nicht. Daraufhin erteilt er den Auftrag zur Erstellung. Dieser Abgleich ist kein einmaliger Vorgang, sondern das Grundmuster: Kubernetes fragt permanent „Soll gleich Ist?" und handelt nur bei Abweichung.
# Spuren der Controller-Arbeit
kubectl get events -n <namespace> --field-selector reason=SuccessfulCreate
Phase C · Platzieren
Schritt 6 · Scheduler wählt den Worker
Der apiserver fragt den scheduler, auf welchem Worker der Container laufen kann, und bekommt eine Antwort. Findet der scheduler keinen passenden Node, bleibt der Pod ohne Zuweisung stehen – der Zustand ist nicht fehlerhaft, sondern unentschieden.
# Grund der Nichtplatzierung, z. B. Ressourcen oder Taints
kubectl get events -n <namespace> --field-selector reason=FailedScheduling
Phase D · Bauen und melden
Schritte 7 und 8 · Kubelet und kube-proxy bekommen ihren Teil
Der apiserver schickt die notwendigen Informationen des Manifests an das kubelet des ausgewählten Workers. Zusätzlich geht eine Netzwerkinformation an jeden kube-proxy, damit der Container auf diesem Worker verfügbar gemacht wird. Beide Wege sind unabhängig: Der Container kann laufen, während die Erreichbarkeit noch fehlt.
Schritt 9 · Container wird erzeugt
Das kubelet sorgt dafür, dass der Container in der Container Engine entsteht und alle im Manifest angeforderten Ressourcen wie Secrets oder Volumes bereitstehen. Fehlt ein Secret, scheitert nicht das Manifest, sondern der Start – sichtbar erst hier, nicht beim Anlegen.
kubectl describe pod <pod> -n <namespace>
Schritt 10 · Rückmeldung und Abschluss
Das kubelet meldet das erfolgreiche Deployment an den apiserver, der die Information in etcd speichert. Erst damit ist Ist gleich Soll – und der Kreis schließt sich genau dort, wo er begonnen hat.
kubectl get pod <pod> -n <namespace> -o jsonpath='{.status.phase}'