Pods, Deployments und ReplicaSets gehören zu den ersten Kubernetes-Begriffen, denen man begegnet. Gleichzeitig verschwimmen sie schnell. Alle drei scheinen irgendwie mit „meine Anwendung läuft“ zu tun zu haben. Warum braucht Kubernetes also drei Objekte?

Die kurze Fassung:

  • Ein Pod ist die kleinste ausführbare Einheit, die Kubernetes für einen Workload erstellt.
  • Ein ReplicaSet sorgt dafür, dass eine bestimmte Anzahl passender Pods läuft.
  • Ein Deployment verwaltet ReplicaSets, damit Updates und Rollbacks von Anwendungen sicherer werden.

Das stimmt, ist aber für einen schlechten Debugging-Tag zu knapp. Die wichtigere Frage ist nicht nur, was jedes Objekt ist. Wichtig ist, warum es existiert, welches Objekt man ändern sollte und wie man die Kette liest, wenn etwas schiefgeht.

Beim Pod anfangen

Ein Pod ist die kleinste deploybare Einheit in Kubernetes. Meist enthält er einen Anwendungscontainer, manchmal zusätzlich einen Sidecar-Container. Container im selben Pod teilen sich einen Netzwerk-Namespace und können Volumes gemeinsam nutzen. Sie werden gemeinsam auf demselben Node platziert.

Dieser letzte Satz ist wichtig: Kubernetes plant nicht einzelne Container ein. Kubernetes plant Pods ein.

Bei einer einfachen Webanwendung denkt man vielleicht: „Meine Anwendung läuft in einem Container.“ Kubernetes sieht: „Ein Pod führt einen oder mehrere Container aus.“ Der Pod bekommt eine IP-Adresse, Status, Events, Volumes, Umgebungsvariablen und Container-Zustände.

So schaut man hinein:

kubectl get pods -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace>

kubectl describe pod ist oft der beste erste Tiefblick. Dort stehen Image-Pull-Probleme, Scheduling-Probleme, Probe-Fehler, Neustarts, gemountete Volumes und Events.

Ein Pod ist aber absichtlich verletzlich. Er kann gelöscht werden. Sein Node kann ausfallen. Sein Container kann abstürzen. Erstellt Kubernetes Ersatz, ist das ein neuer Pod mit neuem Namen und meist neuer IP-Adresse.

Deshalb sollte man Befehle wie diesen richtig einordnen:

kubectl run my-app --image=nginx

Für Experimente ist das nützlich. Es zeigt aber nicht das Produktionsmuster. Ein manuell erstellter Pod hat keinen übergeordneten Controller, der die beabsichtigte Form der Anwendung dauerhaft erhält. Verschwindet er, gibt es möglicherweise kein Objekt, dessen Aufgabe es ist, ihn zurückzubringen.

Ein häufiger Irrtum: „Ich ändere den Pod, um die Anwendung zu ändern.“ Meistens nicht. In echten Workloads wird der Pod von einem Controller erstellt. Man ändert die Vorlage des Controllers — nicht den einzelnen Pod, der gerade zufällig läuft.

Warum es ReplicaSets gibt

Wenn ein einzelner Pod verletzlich ist, braucht man etwas, das genügend Pods am Leben hält. Genau das macht ein ReplicaSet.

Ein ReplicaSet sagt: Für diese Pod-Vorlage und diesen Label-Selector sollen N passende Pods existieren.

Wenn ein ReplicaSet zum Beispiel drei Replikate will und nur zwei passende Pods vorhanden sind, erstellt es einen weiteren. Sind vier passende Pods vorhanden, kann es einen entfernen. Wichtig ist: Das ReplicaSet beobachtet Pods über Labels, nicht über schöne Namen.

Die Beziehung sieht man hier:

kubectl get rs -n <namespace>
kubectl describe rs <replicaset-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels
kubectl get pods -n <namespace> -l app=<label>

Hier werden Label-Selectoren mehr als Dekoration. Wenn der Selector nicht zu den Pod-Labels passt, verwaltet das ReplicaSet diese Pods nicht. Wenn er versehentlich Pods auswählt, die nicht dazugehören, wird die Lage schnell unübersichtlich.

Ein ReplicaSet enthält eine Pod-Vorlage. Diese Vorlage beschreibt, wie neue Pods aussehen sollen: Image, Ports, Umgebungsvariablen, Probes, Volumes, Labels und mehr.

Warum nutzt man dann nicht einfach ReplicaSets direkt?

Man kann das tun, sollte es meistens aber nicht. ReplicaSets halten eine Anzahl. Sie sind keine angenehme Schnittstelle für Anwendungs-Rollouts. Sie bieten nicht denselben Arbeitsablauf für Updates wie Deployments.

Warum es Deployments gibt

Ein Deployment ist das Objekt, das man für eine zustandslose Anwendung in der Regel erstellen sollte.

Es sagt: Führe diese Anwendung mit dieser Pod-Vorlage aus, halte diese Anzahl Replikate und verwalte Änderungen über die Zeit.

Der Deployment-Controller erstellt ein ReplicaSet. Dieses ReplicaSet erstellt Pods. Ändert man die Pod-Vorlage im Deployment, erstellt Kubernetes ein neues ReplicaSet für die neue Version und verschiebt Replikate schrittweise vom alten ReplicaSet zum neuen.

Das ist der Rollout.

kubectl get deploy,rs,pods -n <namespace>
kubectl rollout status deployment/<name> -n <namespace>
kubectl rollout history deployment/<name> -n <namespace>

Die Objektkette sieht meistens so aus:

Deployment
  -> ReplicaSet
      -> Pod
          -> Container

Das Deployment ist die Quelle der Wahrheit, die man normalerweise ändert. Das ReplicaSet ist ein Implementierungsdetail, das man beim Debugging prüft. Der Pod ist die Laufzeiteinheit, die man untersucht, wenn etwas abstürzt oder nicht bereit wird.

Ein häufiger Irrtum: „Deployment, ReplicaSet und Pod sind drei Alternativen.“ Im Normalfall sind sie keine Alternativen. Sie sind Schichten. Die höhere Schicht besitzt die niedrigere.

Ein konkretes Beispiel

Hier ist ein kleines Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-web
  template:
    metadata:
      labels:
        app: hello-web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80

Anwenden:

kubectl apply -f hello-web.yaml -n <namespace>
kubectl get deploy,rs,pods -n <namespace>

Das Deployment hat einen Selector:

selector:
  matchLabels:
    app: hello-web

Die Pod-Vorlage hat passende Labels:

template:
  metadata:
    labels:
      app: hello-web

Diese Übereinstimmung ist nicht optional. Das Deployment nutzt sie, um zu erkennen, welche Pods zur gewünschten Menge gehören. Kubernetes verlangt, dass der Selector zu den Template-Labels passt, weil der Controller sonst nicht wüsste, was er besitzt.

Ändert man später das Image:

kubectl set image deployment/hello-web web=nginx:1.28 -n <namespace>
kubectl rollout status deployment/hello-web -n <namespace>
kubectl get rs -n <namespace>

Dann sollte ein neues ReplicaSet für die neue Pod-Vorlage erscheinen. Das ältere ReplicaSet bleibt möglicherweise mit null Replikaten erhalten. Das ist nützlich, weil Rollout-Historie und Rollback Informationen über frühere Versionen brauchen.

Rollback ist dann eine Deployment-Operation:

kubectl rollout history deployment/hello-web -n <namespace>
kubectl rollout undo deployment/hello-web -n <namespace>

Man baut Pods also nicht von Hand neu. Man sagt dem Deployment-Controller, dass der gewünschte Zustand wieder auf eine frühere Vorlage zeigen soll.

Was passiert, wenn ein Pod abstürzt

Angenommen, ein Pod stürzt ab, weil die Anwendung beendet wird. Oft kommt dann die Frage, ob das Deployment ihn neu startet.

Die genaue Antwort hat mehrere Ebenen.

Das Kubelet auf dem Node startet Container in einem Pod gemäß Restart Policy neu. Bei normalen Pods aus einem Deployment ist diese Policy praktisch Always. Wenn der Container immer wieder abstürzt, sieht man vielleicht CrashLoopBackOff.

Wenn der ganze Pod verschwindet, bemerkt das ReplicaSet, dass die Anzahl passender Pods zu niedrig ist, und erstellt einen neuen Pod.

Das Deployment liegt darüber. Es interessiert sich dafür, ob der Rollout vorankommt und ob die gewünschte Anzahl Replikate verfügbar wird.

Nützliche Prüfungen:

kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous
kubectl describe deployment <name> -n <namespace>

Bei Laufzeitproblemen liest man von unten nach oben:

  • Container-Logs erklären Anwendungsabstürze.
  • Pod-Events erklären Scheduling-, Image-Pull-, Mount- und Probe-Probleme.
  • ReplicaSet-Status zeigt, ob genügend Pods existieren.
  • Deployment-Status zeigt den Fortschritt des Rollouts.

Beim Verständnis der Absicht liest man von oben nach unten:

  • Das Deployment sagt, was gewünscht ist.
  • Das ReplicaSet sagt, welche Pod-Vorlage erhalten wird.
  • Die Pods zeigen, was tatsächlich läuft.

Skalieren: das Deployment ändern

Wenn eine zustandslose Anwendung mehr Kopien bekommen soll, skaliert man das Deployment:

kubectl scale deployment/hello-web --replicas=4 -n <namespace>
kubectl get deploy,rs,pods -n <namespace>

Das Deployment ändert die gewünschte Anzahl Replikate. Das ReplicaSet passt die Pod-Anzahl an. Neue Pods werden auf Nodes geplant, wenn der Cluster genug Kapazität hat.

Ein häufiger Irrtum: „Skalieren heißt, den vorhandenen Pod größer zu machen.“ Horizontales Skalieren bedeutet mehr Pods. Vertikale Größe ist eine andere Frage: CPU- und Speicher-Requests und Limits.

Wenn Skalieren nicht funktioniert, prüft man Events und Kapazität:

kubectl describe deployment hello-web -n <namespace>
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
kubectl get pods -n <namespace> -o wide
kubectl get nodes

Bleiben Pods Pending, kann es ein Scheduling-Problem sein. Laufen sie, werden aber nicht Ready, liegt es eher an Probes, Anwendungsstart oder Abhängigkeiten.

Aktualisieren: die Pod-Vorlage ändern

Ein Deployment-Rollout passiert, wenn sich die Pod-Vorlage ändert. Eine Änderung an replicas skaliert. Eine Änderung an Labels außerhalb der Vorlage erzeugt nicht unbedingt einen neuen Rollout. Änderungen an Image, Umgebungsvariablen, Probes oder Volume Mounts ändern meist die Vorlage und erzeugen ein neues ReplicaSet.

Das erklärt eine häufige Überraschung:

Ich habe etwas am Deployment geändert, aber es sind keine neuen Pods erschienen.

Dann lohnt die Frage, ob die Änderung .spec.template betroffen hat. Wenn nicht, braucht Kubernetes möglicherweise kein neues ReplicaSet.

Das Deployment kann man so ansehen:

kubectl get deployment hello-web -n <namespace> -o yaml
kubectl rollout history deployment/hello-web -n <namespace>
kubectl describe deployment hello-web -n <namespace>

Für Produktionsänderungen bevorzuge ich Updates in der Versionsverwaltung und das Ausrollen über den normalen Teamweg. kubectl set image ist gut zum Lernen und manchmal im Notfall hilfreich. Git-Historie oder Helm Values machen die echte Quelle der Wahrheit aber meist klarer.

Löschen: die Ebene kennen

Einen Pod zu löschen, der von einem ReplicaSet verwaltet wird, ist meist nur vorübergehend:

kubectl delete pod <pod-name> -n <namespace>

Das ReplicaSet bemerkt den fehlenden Pod und erstellt Ersatz.

Das Deployment zu löschen ist etwas anderes:

kubectl delete deployment hello-web -n <namespace>

Standardmäßig räumt Kubernetes auch die ReplicaSets und Pods auf, die zu diesem Deployment gehören. Der gewünschte Zustand an der Spitze wurde entfernt — also gibt es keinen Besitzer mehr, der die unteren Objekte am Leben hält.

Genau deshalb sind Besitzverhältnisse so wichtig. Vor dem Löschen sollte man prüfen, wer ein Objekt besitzt:

kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A5 ownerReferences
kubectl get rs <replicaset-name> -n <namespace> -o yaml | grep -A5 ownerReferences

Denken beim Debugging

Wenn ein Workload auf Basis eines Deployments kaputt ist, hilft mir diese Reihenfolge:

kubectl get deploy,rs,pods -n <namespace>
kubectl describe deployment <name> -n <namespace>
kubectl describe rs <replicaset-name> -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

Dann sortiert man das Problem in grobe Kategorien:

  • Falscher gewünschter Zustand: falsches Image, falsche Umgebungsvariable, fehlender Port, schlechter Selector.
  • Pods können nicht erstellt werden: Quota, Admission Policy, falscher Secret-Verweis, Image-Pull-Problem.
  • Pods können nicht geplant werden: keine Node-Kapazität, Taints, Affinity, Volume-Bedingungen.
  • Pods laufen, werden aber nicht bereit: Readiness-Probe, Anwendungsstart, Dependency-Fehler.
  • Pods stürzen ab: Anwendungsfehler, fehlende Konfiguration, Berechtigungsproblem, falscher Command.

Diese Einordnung hält Debugging praktisch. Sonst starrt man schnell auf einen abstürzenden Pod und übersieht, dass der eigentliche Fehler in einer schlechten Deployment-Vorlage oder einem fehlenden Secret liegt.

Schlussgedanke

Pods, ReplicaSets und Deployments sind keine Vokabelprüfung. Sie bilden eine Verantwortungskette.

Der Pod führt Container aus. Das ReplicaSet hält die richtige Anzahl passender Pods am Leben. Das Deployment verwaltet ReplicaSets, damit Updates, Rollbacks und Skalierung einen stabilen Ort haben.

Für Einsteiger ist die wichtigste Gewohnheit, immer nach der Ebene zu fragen. Wenn ein Prozess abstürzt, untersucht man den Pod. Wenn zu wenige Pods existieren, prüft man ReplicaSet und Events. Wenn sich Version oder Replikatzahl ändern sollen, ändert man das Deployment.

Sobald diese Schichtung klar ist, wirkt Kubernetes weniger wie ein Haufen Objekte und mehr wie ein System mit verständlichen Zuständigkeiten.