Ein Kubernetes StatefulSet ist die Workload-Ressource für Container, die einen Zustand über den eigenen Neustart hinaus behalten müssen, etwa Datenbanken oder Message-Broker. Anders als ein Deployment gibt es jedem Pod eine feste, wiederkehrende Identität und startet, aktualisiert und beendet die Pods in einer strikten Reihenfolge. Ein Deployment behandelt seine Pods dagegen als austauschbar und kümmert sich nicht darum, welcher zuerst kommt.
Ich betreibe produktionsreife Kubernetes-Cluster mit k3s auf Hetzner Cloud und habe das StatefulSet im sechsten Kapitel meines Kubernetes-Praxisbuchs bei Rheinwerk beschrieben. Wo die Grenze zwischen Deployment und StatefulSet verläuft, sehe ich in Projekten regelmäßig falsch gezogen, meist weil eine Anwendung als zustandslos gilt, obwohl sie es nicht ist. Alle meine Beiträge zu Kubernetes findest du gesammelt auf der Seite Kubernetes.
Kubernetes StatefulSet vs. Deployment: der Unterschied, der zählt
Ein Deployment eignet sich für Anwendungen, bei denen jeder Pod austauschbar ist: ein Webserver, eine API, ein Worker. Fällt einer aus, ersetzt ihn ein neuer, ohne Rücksicht auf Reihenfolge oder Namen. Ein StatefulSet ist für den gegenteiligen Fall gedacht: für Anwendungen, bei denen jeder Pod eine eigene Identität und häufig auch eigene Daten trägt, etwa die Knoten eines Datenbank-Clusters oder eines Message-Brokers.
Der Unterschied zeigt sich zuerst am Namen. Ein Pod aus einem Deployment bekommt einen zufälligen Suffix wie web-6d8f9b7c9d-x2kpl, bei jedem Neustart einen neuen. Ein Pod aus einem StatefulSet trägt einen fortlaufenden Index, beginnend bei 0, und behält ihn über Neustarts und sogar über eine Neuerstellung hinweg: web-0, web-1, web-2.
|
Deployment |
StatefulSet |
| Pod-Name |
zufälliger Suffix, wechselt bei jedem Neustart |
fortlaufender Index, bleibt stabil |
| Start- und Löschreihenfolge |
alle Pods gleichzeitig, keine feste Reihenfolge |
ein Pod nach dem anderen, erst wenn der vorherige bereit ist |
| Netzwerkname |
wechselt mit jedem neuen Pod |
bleibt über die Lebenszeit stabil |
| Volume je Pod |
von allen Replicas geteilt oder gar keins |
eigenes Volume je Pod über volumeClaimTemplates |
| Typischer Einsatz |
zustandslose Web- und API-Dienste |
Datenbanken, Message-Broker, verteilte Systeme mit Identität |
Mehr zum Rollout-Verhalten eines Deployments und wie es Pods austauscht, liest du in Kubernetes Deployment: Rollout erklärt.
Stabile Identität: wie StatefulSets Pods benennen und ordnen
Der stabile Name allein würde noch nicht reichen, wenn die Pods sich im Netzwerk nicht wiederfinden könnten. Deshalb verlangt ein StatefulSet einen Headless Service, erkennbar an clusterIP: None und referenziert über serviceName im StatefulSet-Manifest. Über diesen Service bekommt jeder Pod einen eigenen, vorhersagbaren DNS-Namen nach dem Muster <pod-name>.<service-name>.<namespace>.svc.cluster.local. Ein Broker-Knoten kann so einen anderen Knoten direkt über seinen Namen ansprechen, ganz ohne Load-Balancer dazwischen.
Diese Vorhersagbarkeit ist der eigentliche Grund, warum verteilte Systeme wie RabbitMQ, Kafka oder Elasticsearch StatefulSets brauchen: Beim Start müssen sich die Knoten gegenseitig finden und einen Cluster bilden, und das gelingt nur, wenn ihre Namen und Adressen stabil sind. Ein Deployment könnte das nicht leisten, weil jeder Neustart einen neuen, zufälligen Namen erzeugt.
Betreibst du dein Cluster in einer Cloud wie Hetzner oder AWS, kommt noch eine zweite Einschränkung dazu: Blockspeicher ist dort oft an eine bestimmte Zone gebunden. Kubernetes muss dann bei jedem Neustart darauf achten, den Pod wieder in einer Zone zu platzieren, in der auch sein Volume liegt. Der Scheduler löst das automatisch, solange die Volume-Zuordnung korrekt eingerichtet ist, doch es erklärt, warum ein StatefulSet-Pod manchmal länger auf seinen Platz wartet als ein Deployment-Pod.
Kubernetes StatefulSet Beispiel mit volumeClaimTemplates
Am konkretesten wird der Unterschied an einem Manifest. Das folgende Beispiel rollt drei RabbitMQ-Knoten aus. Jeder Pod bekommt über volumeClaimTemplates sein eigenes Volume, ganz ohne dass du selbst ein PersistentVolumeClaim anlegen musst.
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
spec:
clusterIP: None
selector:
app: rabbitmq
ports:
- port: 5672
name: amqp
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rabbitmq
spec:
serviceName: "rabbitmq"
replicas: 3
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:management
ports:
- containerPort: 5672
name: amqp
volumeMounts:
- name: rabbitmq-data
mountPath: /var/lib/rabbitmq
volumeClaimTemplates:
- metadata:
name: rabbitmq-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 5Gi
Der Headless Service oben sorgt für die stabilen DNS-Namen, das StatefulSet darunter für Reihenfolge und Identität. Wichtig ist der Abschnitt volumeClaimTemplates: Er sieht aus wie ein normales PersistentVolumeClaim-Manifest, wird aber für jeden Pod einzeln instanziiert. Skalierst du von drei auf fünf Replicas, entstehen automatisch zwei neue Volumes, eines für rabbitmq-3 und eines für rabbitmq-4. Wie PV, PVC und StorageClass im Detail zusammenspielen, erkläre ich ausführlich im Beitrag Persistent Volumes und PVC in Kubernetes.
Rollst du das Manifest aus, siehst du in kubectl get pods genau die Reihenfolge, die ein StatefulSet ausmacht: rabbitmq-0 wird erzeugt und muss bereit sein, bevor rabbitmq-1 startet, und erst danach kommt rabbitmq-2. Dass sich die drei Knoten danach auch zu einem RabbitMQ-Cluster zusammenschließen, verlangt zusätzlich Konfiguration in der Anwendung, etwa Peer Discovery und einen gemeinsamen Erlang-Cookie. Das StatefulSet liefert dafür die Voraussetzung: stabile Namen, die sich die Knoten gegenseitig merken können.
Skalieren und Updates: Pod Management Policy und Partitionen
Zwei Stellschrauben bestimmen, wie behutsam ein StatefulSet mit seinen Pods umgeht. Die erste ist die podManagementPolicy. Im Standardwert OrderedReady startet und stoppt das StatefulSet die Pods strikt nacheinander, so wie im Beispiel oben. Setzt du sie auf Parallel, verhält sich das StatefulSet beim Skalieren wie ein Deployment und startet alle Pods gleichzeitig. Für die Updates selbst ändert das nichts, die laufen immer sequenziell.
Die zweite Stellschraube ist die updateStrategy. Der Standard RollingUpdate tauscht die Pods einen nach dem anderen aus, beginnend beim höchsten Index. Mit der Zusatzoption partition legst du fest, ab welchem Index überhaupt aktualisiert wird. Setzt du bei fünf Replicas die Partition auf 3, aktualisiert das StatefulSet nur rabbitmq-3 und rabbitmq-4, während rabbitmq-0 bis rabbitmq-2 unverändert bleiben. Das ist ein einfacher Weg, ein Update zuerst an wenigen Knoten zu testen, bevor du die Partition schrittweise auf 0 senkst und den Rest nachzieht.
Was mit dem Volume selbst passiert, wenn du herunterskalierst oder das StatefulSet ganz löschst, hängt von der PVC-Retention-Policy ab. Im Grundzustand bleibt jedes Volume bestehen, auch wenn sein Pod verschwindet, damit du beim Hochskalieren die alten Daten zurückbekommst. Die feineren Regeln dazu, inklusive der Frage, wann Kubernetes ein Volume wirklich löscht, behandle ich im Beitrag zu Persistent Volumes und PVC in Kubernetes.
Datenbank im Cluster: StatefulSet selbst bauen oder Operator nutzen
Die Frage, die mir am häufigsten gestellt wird, lautet nicht „Wie schreibe ich ein StatefulSet", sondern „Sollte ich meine Datenbank überhaupt selbst in Kubernetes betreiben". Ein handgeschriebenes StatefulSet wie das RabbitMQ-Beispiel oben reicht für einen einzelnen Broker oder eine einzelne Instanz aus. Sobald Replikation, automatischer Failover oder konsistente Backups dazukommen, wird das StatefulSet allein schnell zu wenig.
Für genau diesen Fall gibt es Operatoren, die eine Datenbank als eigene Kubernetes-Ressource verwalten. Für PostgreSQL hat sich beispielsweise CloudNativePG etabliert: Du beschreibst nur noch, wie viele Instanzen du willst und welche Storage-Klasse verwendet werden soll, der Operator kümmert sich um Pods, Volumes, Failover und konsistente Backups. Er verwaltet die Instanzen dabei bewusst über einen eigenen Controller, statt ein StatefulSet dazwischenzuschalten. Die Projektdokumentation nennt dafür konkrete Gründe: Ein StatefulSet kann PVCs nicht vergrößern, und es kennt den Zusammenhang zwischen mehreren Volumes einer Instanz nicht. Löscht jemand das PVC für die WAL-Dateien, würde ein StatefulSet es einfach neu anlegen und damit eine kaputte PostgreSQL-Instanz erzeugen. Der eigene Controller hält den Clusterzustand stattdessen im API-Server und steuert Failover und Switchover selbst (CloudNativePG-Dokumentation). In meinen eigenen Clustern greife ich für Datenbanken inzwischen fast immer zu einem Operator und schreibe ein StatefulSet nur noch für Fälle, in denen kein passender Operator existiert oder die Anforderungen wirklich einfach sind.
Wie du die Volumes eines StatefulSets unabhängig vom Operator sicherst, steht im Beitrag Kubernetes Backup: etcd und Volumes sichern.
Häufige Fragen
Wann brauche ich ein StatefulSet statt eines Deployments?
Immer dann, wenn deine Anwendung Zustand über den Neustart eines Pods hinaus behalten muss und die einzelnen Instanzen sich voneinander unterscheiden, etwa weil sie eigene Daten oder eine feste Rolle im Cluster haben. Datenbanken, Message-Broker und andere verteilte Systeme mit Knoten-Identität sind die klassischen Fälle. Für zustandslose Anwendungen bleibt das Deployment die richtige Wahl.
Was passiert mit dem Volume, wenn ein Pod im StatefulSet stirbt?
Solange nur der Pod ausfällt, nicht aber das StatefulSet skaliert oder gelöscht wird, bleibt sein Volume bestehen. Kubernetes erzeugt einen neuen Pod mit demselben Index und derselben Volume-Zuordnung, sodass die Daten erhalten bleiben. Erst beim gezielten Herunterskalieren oder Löschen greifen die Retention-Regeln, die ich im Beitrag zu Persistent Volumes im Detail beschreibe.
Was ist der Unterschied zwischen PV und PVC?
Kurz gesagt: Ein Persistent Volume (PV) ist der tatsächliche Speicher, ein Persistent Volume Claim (PVC) ist die Anforderung eines Pods an diesen Speicher. Ausführlich mit Beispielen erkläre ich das im Beitrag Persistent Volumes und PVC in Kubernetes.
Sollte ich eine Datenbank direkt in Kubernetes betreiben?
Das kommt auf dein Team und deine Anforderungen an. Ein StatefulSet allein deckt Replikation, Failover und Backups nicht automatisch ab, ein passender Operator schon eher. Wer diesen Aufwand nicht selbst tragen will, ist mit einer verwalteten Datenbank außerhalb des Clusters oft besser bedient.
Kann ich die Pod Management Policy nachträglich ändern?
Nein. Die podManagementPolicy lässt sich nur beim Erzeugen des StatefulSets setzen, nicht nachträglich ändern. Um sie zu wechseln, musst du das StatefulSet löschen und neu anlegen, was bei produktiven Anwendungen sorgfältig geplant sein will.
Wie du weitermachst
Wenn du weißt, dass deine Anwendung ein StatefulSet braucht, ist der nächste Schritt fast immer die Frage nach dem Storage dahinter: welche StorageClass, welcher Access Mode, welches Verhalten beim Löschen. Das beantworte ich im Beitrag Persistent Volumes und PVC in Kubernetes. Willst du stattdessen prüfen, ob eine Anwendung überhaupt ein StatefulSet braucht oder mit einem einfachen Deployment auskommt, hilft dir Kubernetes Deployment: Rollout erklärt.
Mein Vorschlag für heute: Rolle das RabbitMQ-Manifest von oben in einem Testcluster aus und beobachte mit kubectl get pods -w, in welcher Reihenfolge die drei Knoten entstehen. Danach lösche testweise einen Pod in der Mitte und sieh dir an, wie das StatefulSet reagiert.
Ausführlich mit allen Beispielen, von der Pod Management Policy bis zur PVC-Retention-Policy, steht das in Kapitel 6 meines Kubernetes-Praxisbuchs bei Rheinwerk. Alle Informationen zum Buch findest du auf meiner Kubernetes-Seite.