Deployments sind die Standardantwort für Anwendungen in Kubernetes. Für eine zustandslose API, ein Web-Frontend oder einen Worker, der aus einer Queue liest, ist dieser Default richtig. Pods kommen und gehen. Namen ändern sich. IP-Adressen ändern sich. Solange hinter einem Service etwas Gesundes läuft, ist den Nutzern meist egal, welcher Pod eine Anfrage bearbeitet hat.
Zustandsbehaftete Workloads brechen diese Annahme.
Eine Datenbank-Replika interessiert sich dafür, welches Datenverzeichnis gemountet ist. Ein Kafka-Broker erwartet vielleicht eine stabile Host-Identität, damit andere Broker wissen, wie sie ihn erreichen. Ein verteiltes System verlangt manchmal, dass Mitglieder in Reihenfolge starten — Node 0 initialisiert, bevor Node 1 beitritt. Eine Anwendung schreibt lokale Dateien, die Pod-Neustarts überleben müssen, aber an derselben logischen Instanz hängen bleiben sollen.
Mit einem plain Deployment und einem geteilten PVC, oder mit Pods ohne stabile Netzwerk-Identität und zufälligen Suffix-Namen, scheitert das auf subtile Weise. Daten landen auf der falschen Platte. Cluster-Mitglieder finden sich nicht. Skalierung erzeugt doppelte Identitäten statt neue.
StatefulSets existieren genau für diese Klasse von Problemen. Sie sind nicht „Deployments, nur schicker”. Sie sind ein Controller mit anderen Garantien.
Wann Deployments bei State scheitern
Ein Deployment erzeugt Pods mit zufälligen Suffix-Namen: web-7d4f8c9b6-xk2lm. Das passt, wenn jeder Pod austauschbar ist. Es ist schmerzhaft, wenn Pod web-2 immer dieselbe Replika mit derselben Platte meinen soll.
Typische Fehlermuster bei stateful Apps mit Deployments:
Geteiltes Storage aus Versehen. Zwei Pods mounten dasselbe ReadWriteOnce-Volume. Nur ein Node kann es anbinden. Der zweite Pod bleibt pending oder scheitert beim Mount.
Lokale Identitäts-Annahmen. Software erwartet einen Hostnamen wie db-2 oder eine persistente Node-ID auf der Platte. Deployment-Pod-Namen ändern sich bei jedem Recreate — Peer-Listen und Config driften.
Ungeordnetes Startup. Jede Replika startet gleichzeitig. Manche geclusterte Datenbanken vertragen das. Viele nicht. Replika 1 versucht beizutreten, bevor Replika 0 Storage initialisiert hat.
Überraschungen beim Scale-down. Deployment-Scale-down entfernt Pods, ohne zu garantieren, welcher verschwindet. Braucht jede Replika ihre eigene Platte, braucht man pro Replika gebundenes Storage.
Das heißt nicht „nie Deployments nutzen”. Es heißt: Deployments optimieren auf Austauschbarkeit. Stateful Apps brauchen oft Identität und Reihenfolge.
Was ein StatefulSet garantiert
Ein StatefulSet liefert:
Stabile Pod-Namen. Pods heißen <statefulset-name>-0, <statefulset-name>-1 und so weiter. Wird app-db-1 gelöscht und neu erstellt, heißt der Ersatz weiterhin app-db-1.
Stabile DNS-Namen. Mit einem Headless Service bekommt jeder Pod einen vorhersehbaren DNS-Eintrag wie app-db-1.app-db.default.svc.cluster.local.
Geordnetes Deployment und Skalierung. Standardmäßig entstehen Pods in der Reihenfolge 0, 1, 2 und werden in umgekehrter Reihenfolge beendet. Updates rollen oft Pod für Pod.
Stabiles Storage pro Pod. Mit volumeClaimTemplates bekommt jeder Pod einen eigenen PersistentVolumeClaim. Der Claim-Name enthält die Pod-Identität und überlebt Pod-Recreation.
Diese Garantien kosten Flexibilität. StatefulSets rollen langsamer aus und sind schwerer zu durchschauen als Deployments. Man nutzt sie, wenn die Garantien zählen — nicht weil die App „etwas State” hat.
Ein minimaler StatefulSet
Hier ein kleines Beispiel mit drei Replikas:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: app-db
namespace: demo
spec:
serviceName: app-db
replicas: 3
selector:
matchLabels:
app: app-db
template:
metadata:
labels:
app: app-db
spec:
containers:
- name: db
image: my-registry.example/fake-db:1.0.0
ports:
- containerPort: 5432
name: db
volumeMounts:
- name: data
mountPath: /var/lib/db
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
Anwenden und prüfen:
kubectl apply -f statefulset.yaml
kubectl get statefulset app-db -n demo
kubectl get pods -n demo -l app=app-db
kubectl get pvc -n demo
Es sollten Pods app-db-0, app-db-1 und app-db-2 der Reihe nach erscheinen. Dazu PVCs wie data-app-db-0, data-app-db-1 und data-app-db-2.
serviceName muss zum Namen eines Headless Service passen. Der StatefulSet-Controller nutzt ihn, um stabile Netzwerk-Identität pro Pod zu erzeugen.
Stabile Identität in der Praxis
Stabile Identität zeigt sich an drei Stellen, die Einsteiger verbinden sollten:
Pod-Name. Andere Mitglieder referenzieren app-db-0, nicht einen Zufalls-Hash.
DNS. Clients im Cluster können einzelne Pods über den Headless Service auflösen.
PVC-Name. Storage folgt der Pod-Ordinalzahl. Wird app-db-0 neu erstellt, bindet es wieder data-app-db-0.
Deshalb passen StatefulSets natürlich zu ReadWriteOnce-Storage. Jede Replika bekommt ihre eigene Platte. Scale-up erzeugt neue Claims. Scale-down entfernt Pods, löscht PVCs aber nicht automatisch. Kubernetes vermeidet absichtlich, Daten zu löschen, nur weil die Replika-Anzahl sinkt.
DNS von einem anderen Pod prüfen:
kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- \
nslookup app-db-1.app-db.demo.svc.cluster.local
Schlägt die Auflösung fehl, fehlt der Headless Service oder er ist falsch benannt — bevor man die Datenbank selbst debuggt.
Headless Services
Ein normaler ClusterIP-Service load-balanciert über Pods. Ein Headless Service (clusterIP: None) liefert keine virtuelle IP fürs Load-Balancing. Stattdessen veröffentlicht er DNS-A- oder AAAA-Records für die backing Pods.
apiVersion: v1
kind: Service
metadata:
name: app-db
namespace: demo
spec:
clusterIP: None
selector:
app: app-db
ports:
- port: 5432
name: db
Clients, die „irgendeine gesunde Replika” brauchen, nutzen weiterhin einen normalen Service oder verbinden über einen anwendungsseitigen Router. Clients, die eine bestimmte Replika brauchen, nutzen den Pod-DNS-Namen.
Bei vielen geclusterten Produkten gibt es beides:
- Headless Service für Peer-Discovery und stabile Member-Adressen
- normaler Service für Client-Traffic zum aktuellen Primary oder zu einer ready Replika
Den Headless Service nicht überspringen, weil „die App schon einen Service hat”. Zeigt serviceName am StatefulSet nicht auf einen Headless Service, funktioniert stabiles Pod-DNS nicht wie vorgesehen.
Geordnetes Rollout und Updates
StatefulSets erzeugen Pods nacheinander. Pod 1 entsteht nicht, bevor Pod 0 Running und Ready ist. Das reduziert Race Conditions beim ersten Bootstrap.
Scale-up:
kubectl scale statefulset app-db --replicas=4 -n demo
kubectl get pods -n demo -l app=app-db -w
app-db-3 wartet, bis frühere Ordinals ready sind.
Scale-down entfernt zuerst die höchsten Ordinals. Von 4 auf 2 skalieren heißt: Pods 3 und 2 verschwinden vor 1 und 0. Das ist sicherer, wenn höhere Ordinals Erweiterungen sind statt der Gründungsmitglieder.
Updates nutzen eine Rolling-Strategy über updateStrategy:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
Mit partition: 0 updaten alle Pods. Mit partition: 2 updaten nur Pods mit Ordinal größer oder gleich 2. Nützlich für Canary-Tests auf der höchsten Replika, bevor der Rest folgt.
Rollout-Status prüfen:
kubectl rollout status statefulset/app-db -n demo
kubectl get pods -n demo -l app=app-db
kubectl describe statefulset app-db -n demo
Bleibt Pod 1 pending, während Pod 0 gesund ist, zuerst Events auf Pod 1 lesen. Geordnetes Startup heißt: ein hängendes niedriges Ordinal blockiert alles darüber.
volumeClaimTemplates
volumeClaimTemplates sind wie PVC-Manifeste im StatefulSet eingebettet. Kubernetes erzeugt pro Pod einen Claim aus der Vorlage.
Wichtiges Verhalten:
Jeder Claim hat einen eindeutigen Namen aus Template-Name und Pod-Ordinal.
Pod-Recreation nutzt denselben Claim wieder.
StatefulSet löschen entfernt die Claims nicht automatisch — das muss man separat steuern.
Scale-down lässt ungenutzte PVCs absichtlich zurück.
Storage prüfen:
kubectl get pvc -n demo
kubectl describe pvc data-app-db-0 -n demo
kubectl get pod app-db-0 -n demo -o yaml | grep -A10 volumeMounts
Access Modes und Storage Class bewusst wählen. Die meisten StatefulSet-Platten pro Replika nutzen ReadWriteOnce. ReadWriteMany geht nur, wenn Storage Class und Anwendung gemeinsames Dateisystem unterstützen.
Häufiger Irrtum: „Ich mounte einen PVC in einem Deployment und skaliere auf drei.” Mit typischem Block-Storage scheitert das. StatefulSets existieren unter anderem, weil jede Ordinalzahl einen eigenen Claim braucht.
StatefulSet vs Deployment
Deployment nutzen, wenn:
- Pods austauschbar sind
- keine persistente Identität pro Pod nötig ist
- schnelle Rollouts und einfache Skalierung gewünscht sind
- Clients die App über einen Service erreichen, der auf beliebige ready Pods balanciert
StatefulSet nutzen, wenn:
- jede Replika stabile Netzwerk-Identität braucht
- jede Replika ein eigenes persistentes Volume braucht
- Startup-Reihenfolge zählt
- die Software vorhersehbare Hostnamen oder Ordinals erwartet
Etwas anderes nutzen, wenn:
- ein Managed-Database-Operator die Cluster-Bildung übernimmt
- die App State nur in externem Object Storage oder einer Remote-Datenbank hält
- elastische Skalierung ohne jede Identität nötig ist
Viele Teams halten State in Managed Services und Deployments in der App-Schicht. Das ist valide. StatefulSets sind für Software, die im Cluster geclustert läuft und von Kubernetes erwartet, dass Identität erhalten bleibt.
StatefulSet-Probleme debuggen
Wenn ein StatefulSet schiefgeht, prüfe ich Identität, Reihenfolge und Storage, bevor ich das Application-Image beschuldige.
Schritt 1: StatefulSet-Status und Events
kubectl get statefulset app-db -n demo
kubectl describe statefulset app-db -n demo
kubectl get pods -n demo -l app=app-db
Achte darauf, wie viele Replikas ready sind versus desired. Häufiges Muster: 2/3, weil eine Ordinal hängt.
Schritt 2: Das niedrigste not-ready Pod
kubectl describe pod app-db-1 -n demo
kubectl logs app-db-1 -n demo
kubectl logs app-db-1 -n demo --previous
Wegen der Reihenfolge entblockt oft das Fixen des niedrigsten kaputten Ordinals den Rest.
Schritt 3: PVC- und Mount-Status
kubectl get pvc -n demo
kubectl describe pvc data-app-db-1 -n demo
kubectl get events -n demo --sort-by=.lastTimestamp
Pending PVCs, Attach-Fehler und falsche Storage-Class-Namen stehen hier.
Schritt 4: Headless Service und DNS
kubectl get svc app-db -n demo
kubectl describe svc app-db -n demo
ClusterIP muss None sein; Selectors müssen Pod-Labels treffen. Peer-Discovery-Fehler führen oft hierher.
Schritt 5: Update-Strategy und Partition
Wenn nur manche Pods updated haben, updateStrategy.rollingUpdate.partition prüfen. Eine Partition ungleich null lässt niedrigere Ordinals absichtlich auf alter Spec.
Praktische Vorsicht
StatefulSets machen Daten dauerhaft — und damit auch Fehler dauerhaft.
Scale-down löscht keine PVCs. Alte Platten kosten Geld und verwirren beim späteren Scale-up, wenn die App leere Platten erwartet.
Image-Updates rollen standardmäßig Pod für Pod, aber Readiness auf Anwendungsebene zählt weiter. Ein Pod kann für Kubernetes Ready sein, während die Cluster-Mitgliedschaft kaputt ist.
Backups bleiben eigene Verantwortung. Persistent Volumes überleben Pod-Neustarts. Sie ersetzen keine Backup- und Restore-Tests.
In lokalen Labs sind StatefulSets starke Lernwerkzeuge. Für produktive geclusterte Datenbanken bevorzugen viele Teams Operatoren oder Managed Services — es sei denn, es gibt einen starken Grund, den vollen Cluster selbst zu betreiben.
Abschluss
StatefulSets sind Kubernetes’ Antwort darauf, dass austauschbare Pods die falsche Abstraktion sind.
Deployments optimieren auf „halte N Kopien am Laufen.” StatefulSets optimieren auf „halte N identifizierbare Mitglieder am Laufen — jeweils mit eigenem Namen, DNS und Volume, in vorhersehbarer Reihenfolge gestartet.”
Behandelt die App jede Instanz als anonym, bleibt man bei Deployments. Kümmert sich die App darum, ob sie Node 0 oder Node 2 ist, finden Peers sich über stabile Hostnamen, oder muss jede Replika das eigene Volume über Neustarts behalten, ist StatefulSet der nächste Controller zum Lernen.
Wenn stabile Identität, Headless Services und volumeClaimTemplates im mentalen Modell zusammenpassen, lesen sich StatefulSets nicht mehr wie mysteriöses YAML, sondern wie Infrastruktur mit expliziten Garantien.