Ein Kubernetes Pod ist die kleinste Einheit, die Kubernetes planen und ausrollen kann. Ein Pod fasst einen oder mehrere Container zusammen, die gemeinsam auf derselben Node landen und sich dort eine IP-Adresse und bei Bedarf Volumes teilen. Geplant, erzeugt und gelöscht wird immer der ganze Pod, nie ein einzelner Container darin.
Wer den Pod verstanden hat, versteht den Rest von Kubernetes deutlich leichter. Deployments, Jobs, DaemonSets und StatefulSets sind am Ende nur verschiedene Arten, Pods zu erzeugen und am Leben zu halten.
Ein Satz vorweg, weil er später jede Fehlersuche abkürzt: Die Phase eines Pods und der Zustand seiner Container sind zwei verschiedene Dinge. Running heißt, dass der Pod läuft, nicht dass die Anwendung darin gesund ist.
Ich betreibe Kubernetes-Cluster in Produktion und habe dem Pod im dritten Kapitel meines Kubernetes-Buchs bei Rheinwerk einen eigenen Abschnitt gewidmet. Vieles davon steht hier in Kurzform, zusammen mit den Fehlern, die mir im eigenen Betrieb und in Kundenprojekten begegnet sind. Alle meine Beiträge zu Kubernetes findest du gesammelt auf der Seite Kubernetes.
Kubernetes Pod vs. Container: was ist der Unterschied?
Ein Container ist ein laufender Prozess mit eigenem Dateisystem, isoliert durch Namespaces und cgroups des Linux-Kernels. Ein Pod ist die Hülle, die Kubernetes um einen oder mehrere solcher Container legt. Du kannst in Kubernetes keinen Container direkt starten, du beschreibst immer einen Pod.
Der Unterschied wird greifbar, wenn du dir anschaust, was die Container eines Pods gemeinsam haben. Sie teilen sich eine IP-Adresse und den Netzwerk-Namespace, erreichen sich gegenseitig über localhost und dürfen deshalb nicht denselben Port belegen. Sie können dieselben Volumes einbinden und darüber Dateien austauschen. Und sie werden gemeinsam auf eine Node geplant und gemeinsam mit dem Pod gelöscht. Der einzelne Container ist damit trotzdem nicht aus dem Spiel: Fällt einer aus, startet das Kubelet ihn gemäß Restart Policy innerhalb desselben Pods neu, ohne die anderen anzufassen (Kubernetes-Dokumentation zum Pod-Lebenszyklus).
Technisch sorgt dafür ein Container, den dir kubectl nie anzeigt: der Pause-Container. Er startet als Erstes, hält die IP-Adresse und die Linux-Namespaces des Pods und lebt so lange wie der Pod selbst. Deine Anwendungscontainer hängen sich an diese Namespaces an. Das hat einen angenehmen Nebeneffekt: Stürzt dein Anwendungscontainer ab und wird neu gestartet, behält der Pod seine IP-Adresse.
|
Container |
Pod |
| Was es ist |
ein isolierter Prozess aus einem Image |
eine Gruppe aus einem oder mehreren Containern |
| Wer es verwaltet |
die Container-Runtime (containerd, CRI-O) |
Kubernetes über das Kubelet |
| Netzwerk |
eigene Netzwerkeinstellungen je Container |
eine IP-Adresse für alle Container im Pod |
| Speicher |
eigenes Dateisystem |
gemeinsame Volumes möglich |
| Platzierung |
dort, wo du ihn startest |
alle Container auf derselben Node |
Ob unter dem Pod containerd oder CRI-O arbeitet, spielt für dich als Entwickler kaum eine Rolle: Das Kubelet spricht über das Container Runtime Interface (CRI) mit der Runtime, und die übernimmt Image, Dateisystem und Start. Wie die Komponenten zusammenspielen, beschreibe ich in Kubernetes-Architektur: die Komponenten.
Ein Container pro Pod oder mehrere? Sidecar, Ambassador, Adapter
Die meisten Pods, die ich in Produktion sehe, enthalten genau einen Anwendungscontainer. Das ist auch der richtige Standard: Ein Container je Pod lässt sich unabhängig skalieren, unabhängig ausrollen und sauber entkoppeln. Mehrere Container gehören nur dann in einen Pod, wenn sie ohne einander keinen Sinn ergeben.
Drei Fragen helfen bei der Entscheidung. Erstens: Müssen die Container sich eine Ressource teilen, etwa ein Dateisystem, in das der eine schreibt und aus dem der andere liest? Dann gehören sie zusammen. Zweitens: Haben sie unterschiedliche Anforderungen an die Skalierung? Ein Webserver und seine Datenbank skalieren nie im Gleichschritt, deshalb bekommen sie getrennte Pods. Drittens: Könnten die Container problemlos auf verschiedenen Maschinen laufen? Wenn ja, trenne sie.
Für die Helfer, die tatsächlich in denselben Pod gehören, haben sich drei Muster eingebürgert:
| Muster |
Aufgabe |
Typischer Fall |
| Sidecar |
ergänzt die Hauptanwendung um eine Funktion |
Log-Sammler, Metriken-Exporter |
| Ambassador |
Proxy nach außen, die Anwendung spricht nur mit localhost |
Authentifizierung an fremden APIs, TLS-Verbindungen |
| Adapter |
formt eingehende Daten passend um |
Protokoll- oder Formatumwandlung für Legacy-Anwendungen |
Ein Beispiel aus meinen Clustern: Prometheus erwartet Metriken in seinem eigenen Format. Bringt eine Anwendung keine mit, hängt ein Exporter als Sidecar im selben Pod und übersetzt. Die Anwendung merkt davon nichts, und der Exporter wird mit ihr zusammen ausgerollt und beendet. Sobald ein Helfer aber die Skalierung der Hauptanwendung einschränkt, gehört er in einen eigenen Pod.
Wichtig für den Entwurf: Alle regulären Container eines Pods starten gleichzeitig. Du kannst dich nicht darauf verlassen, dass der Sidecar vor der Anwendung bereit ist. Wer eine feste Reihenfolge braucht, greift zu Init Containern, dazu gleich mehr.
Kubernetes Pod YAML: dein erstes Manifest
Ein Pod wird wie jedes Kubernetes-Objekt deklarativ beschrieben. Das folgende Manifest startet einen Nginx-Webserver und daneben einen kleinen Sidecar, der die Zugriffslogs des Webservers mitliest. Beide Container binden dasselbe Volume ein, ein emptyDir, das mit dem Pod entsteht und mit ihm verschwindet.
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
app: my-nginx
spec:
containers:
- name: web
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
- name: log-collector
image: busybox
command:
- sh
- -c
- "until [ -f /var/log/nginx/access.log ]; do sleep 1; done; tail -n +1 -f /var/log/nginx/access.log"
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
volumes:
- name: log-volume
emptyDir: {}
Die wichtigsten Felder: apiVersion und kind sagen Kubernetes, welches Objekt du meinst. metadata.name ist der Name des Pods in seinem Namespace, die labels brauchst du später für Services und Deployments. Unter spec.containers steht die Liste der Container mit Image, Ports und Volume-Mounts, unter spec.volumes die Volumes, die die Container einbinden dürfen. Der Sidecar wartet, bis Nginx die Logdatei angelegt hat, und gibt sie dann fortlaufend auf seiner Standardausgabe aus. Ein Detail zum Nginx-Image: Normalerweise leitet es seine Logs auf die Standardausgabe um. Weil das Volume den Ordner /var/log/nginx überdeckt, entsteht dort wieder eine echte Datei, und genau die liest der Sidecar.
Speichere das Manifest als my-nginx.yaml und bringe es mit kubectl in den Cluster:
kubectl apply -f my-nginx.yaml
kubectl get pod my-nginx
kubectl port-forward pod/my-nginx 8080:80
kubectl logs my-nginx -c log-collector
Nach dem apply zeigt get pod die Spalten READY, STATUS und RESTARTS. Steht dort 2/2 und Running, laufen beide Container. Mit dem Port-Forwarding erreichst du den Webserver unter localhost:8080, und jeder Aufruf taucht anschließend im Log des Sidecars auf. Weitere Befehle für den Alltag findest du in kubectl Befehle: die wichtigsten im Alltag.
Ein ehrlicher Hinweis: In Produktion legst du Pods praktisch nie so direkt an. Ein einzelner Pod wird bei einem Node-Ausfall nicht ersetzt und lässt sich nicht ohne Unterbrechung aktualisieren. Dafür gibt es das Deployment, das Pods für dich erzeugt und ersetzt. Zum Lernen ist das nackte Pod-Manifest trotzdem der richtige Einstieg, weil das Deployment intern genau dieselbe Pod-Spezifikation enthält.
Init Container: Vorbereitung vor dem Start
Je mehr Voraussetzungen eine Anwendung hat, desto unangenehmer wird ihr Start: Die Datenbank ist noch nicht erreichbar, eine Konfigurationsdatei fehlt, ein Verzeichnis hat die falschen Rechte. Statt diese Prüfungen in die Anwendung einzubauen, nutzt du den Init Container.
Init Container laufen vor den regulären Containern des Pods, strikt nacheinander. Jeder muss erfolgreich beenden, bevor der nächste startet, und erst wenn alle durch sind, startet deine Anwendung. Typische Aufgaben sind das Warten auf eine Datenbank, das Vorbereiten des Dateisystems oder ein Schritt mit Werkzeugen, die im Anwendungs-Image nichts verloren haben. Ein Init Container darf außerdem eigene Secrets und Rechte bekommen, die die Hauptanwendung nicht braucht. Das ist Least Privilege in der Praxis.
So sieht der Anfang der spec aus, wenn du dem Pod von oben einen Init Container voranstellst, der die Logdatei bereits anlegt:
spec:
initContainers:
- name: init-logs
image: busybox
command: ["sh", "-c", "touch /var/log/nginx/access.log"]
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
containers:
- name: web
image: nginx
Schlägt ein Init Container fehl, startet Kubernetes ihn in der Standardeinstellung so lange neu, bis er erfolgreich ist. Der Pod bleibt in dieser Zeit in der Phase Pending, und kubectl get pod zeigt dir in der STATUS-Spalte, beim wievielten Init Container er hängt. Bei mir sind Init Container das Mittel der Wahl, um Datenbank-Migrationen vor dem Start der Anwendung abzuwarten, statt die Anwendung selbst mit Retry-Logik vollzustopfen.
Kubernetes Pod Status: Phasen und Container-Zustände lesen
Die STATUS-Spalte von kubectl get pod ist dein erster Blick auf die Gesundheit eines Pods. Dahinter stecken zwei Ebenen: die Phase des Pods und der Zustand jedes einzelnen Containers darin. Wer beide auseinanderhalten kann, findet Fehler deutlich schneller.
| Phase |
Bedeutung |
| Pending |
Der Pod ist angelegt, aber noch nicht alle Container laufen. Kubernetes sucht eine Node, lädt Images, bindet Volumes ein oder wartet auf Init Container. |
| Running |
Der Pod ist einer Node zugewiesen, alle Container sind erzeugt und mindestens einer läuft oder startet gerade. |
| Succeeded |
Alle Container haben sich erfolgreich beendet und werden nicht neu gestartet. Typisch für Jobs. |
| Failed |
Alle Container sind beendet und mindestens einer davon mit Fehler, also mit einem Exit-Code ungleich 0. |
| Unknown |
Kubernetes kann den Zustand nicht ermitteln, meist weil die Node nicht mehr erreichbar ist. |
Die Phase allein sagt nicht alles: Ein Pod in Pending kann seit Minuten vergeblich ein Image ziehen, ein Pod in Running in einer Schleife aus Absturz und Neustart stecken. Darum lohnt der Blick auf die Container-Zustände: Waiting (der Container wartet, zum Beispiel auf ein Image oder ein Secret), Running (er läuft) und Terminated (er ist beendet, erfolgreich oder nicht). Was du in der STATUS-Spalte als CrashLoopBackOff, ImagePullBackOff oder Terminating siehst, sind keine Phasen, sondern Gründe aus diesen Zuständen.
Der wichtigste Befehl für die Fehlersuche ist kubectl describe pod <name>. Unten in der Ausgabe stehen die Events, weiter oben je Container der aktuelle Zustand und der Last State mit Exit-Code. Gerade dieser Last State verrät oft erst, warum ein Container immer wieder stirbt. Die vollständige Beschreibung des Lebenszyklus findest du in der Kubernetes-Dokumentation zum Pod Lifecycle.
Pod beenden: Restart Policy und Graceful Shutdown
Wie Kubernetes mit einem beendeten Container umgeht, steuert die restartPolicy auf Pod-Ebene. Sie gilt für alle Container im Pod, auch für Init Container. Always ist der Standard und startet jeden beendeten Container neu, egal aus welchem Grund. OnFailure startet nur bei einem Exit-Code ungleich 0 neu, was für Batch-Aufgaben passt. Never startet nie neu und gibt dir die volle Kontrolle.
In Kubernetes werden Pods ständig beendet und neu erzeugt: beim Skalieren, beim Ausrollen einer neuen Version, beim Umzug auf eine andere Node. Deine Anwendung muss deshalb sauber herunterfahren können. Das Kubelet schickt dazu ein SIGTERM an den Prozess mit der PID 1 in jedem Container und wartet die Grace Period ab, standardmäßig 30 Sekunden. Ist ein preStop-Hook definiert, läuft er vor dem Signal. Reagiert die Anwendung nicht rechtzeitig, folgt ein SIGKILL, und der Container wird hart beendet.
Zwei Dinge musst du dafür in deiner Anwendung sicherstellen: Sie läuft als PID 1 im Container, und sie fängt das Signal ab, um laufende Transaktionen abzuschließen, Datenbankverbindungen zu schließen und mit Exit-Code 0 zu enden. Braucht sie dafür länger als 30 Sekunden, setzt du terminationGracePeriodSeconds in der Pod-Spec hoch. Mit kubectl delete pod <name> --grace-period=0 --force kannst du die Wartezeit im Notfall übergehen, verlierst dann aber jede Garantie auf einen sauberen Abschluss. Die Hooks sind in der Dokumentation zu Container Lifecycle Hooks beschrieben.
Häufige Fragen
Was ist ein Pod in Kubernetes, in einem Satz?
Ein Pod ist die kleinste Einheit, die Kubernetes planen und ausrollen kann: ein oder mehrere Container, die sich IP-Adresse und Volumes teilen und gemeinsam auf einer Node laufen. Alles andere in Kubernetes, vom Deployment bis zum Job, erzeugt und verwaltet am Ende Pods.
Warum verwaltet Kubernetes Pods und nicht einzelne Container?
Weil zusammengehörige Container gemeinsame Ressourcen brauchen: dieselbe IP-Adresse, dieselben Volumes, dieselbe Node. Der Pod stellt diese gemeinsame Umgebung bereit und lässt sich als Ganzes planen, skalieren und ersetzen. Eine Anwendung mit Sidecar, Init Container und Volume wäre als lose Sammlung einzelner Container kaum zu steuern.
Wie viele Container gehören in einen Pod?
Im Normalfall einer. Mehrere Container gehören nur dann in denselben Pod, wenn sie sich eine Ressource teilen müssen, gleich skalieren und nicht sinnvoll auf verschiedenen Maschinen laufen könnten. Ein Log-Sammler oder ein Metriken-Exporter neben der Anwendung erfüllt das, eine Datenbank neben dem Webserver nicht.
Was tue ich, wenn ein Pod dauerhaft in Pending hängt?
Zuerst kubectl describe pod <name> ausführen und die Events unten lesen. Meist steht dort der Grund: keine Node mit genug Ressourcen, ein Image, das nicht gezogen werden kann, ein Volume, das sich nicht einbinden lässt, oder ein Init Container, der nicht fertig wird. Die Phase Pending ist nur das Symptom, die Events zeigen die Ursache.
Wie du weitermachst
Der nächste logische Schritt ist das Deployment: Es packt deine Pod-Spezifikation ein, hält die gewünschte Anzahl an Pods am Leben und rollt neue Versionen ohne Unterbrechung aus. Wie das funktioniert, steht in Kubernetes Deployment: Rollout erklärt. Für den täglichen Umgang mit Pods, Logs und Shells im Container hilft dir die Übersicht in kubectl Befehle: die wichtigsten im Alltag.
Mein Vorschlag für heute: Nimm das Manifest von oben, tausche das Nginx-Image gegen eine eigene Anwendung und beobachte mit kubectl describe, wie der Pod durch seine Phasen läuft. Das offizielle Konzeptkapitel zu Pods in der Kubernetes-Dokumentation ergänzt die Details, die hier keinen Platz hatten.
Ausführlich mit allen Beispielen, vom Dockerfile über den Logsammler bis zur Restart Policy, steht das in Kapitel 3 meines Buchs „Kubernetes: Das Praxisbuch für Entwickler und DevOps-Teams" (Rheinwerk). Alle Informationen zum Buch findest du auf meiner Kubernetes-Seite.