☸️ Kubernetes Lektion 8/20 ~9 Min. Fortgeschritten

StatefulSets und DaemonSets

Stateful und Node-lokale Workloads.

📝 Meine Notizen

Lernziele

Nach dieser Lektion kannst du erklären, welche drei Garantien ein StatefulSet gegenüber einem Deployment zusätzlich gibt — stabile Namen, stabiles Storage, geordnetes Deployment — und warum verteilte Systeme wie Datenbanken genau diese brauchen. Du verstehst die Rolle des Headless Service für per-Pod-DNS, kennst den Einsatzzweck von DaemonSets für Node-lokale Agents und kannst Jobs und CronJobs für Batch-Workloads abgrenzen.

Das Problem: Manche Anwendungen sind eben doch Haustiere

Deployments (Lektion 7) leben von der Austauschbarkeit: Jeder Pod ist namenlos (web-7d4b9-x2kfp), jeder kann jeden ersetzen, Reihenfolge egal. Für eine PostgreSQL-Replikation, einen Kafka- oder etcd-Cluster bricht dieses Modell dreifach zusammen:

  1. Identität: Der Primary heißt für seine Replicas nicht „irgendeiner von dreien” — die Mitglieder müssen sich gegenseitig stabil adressieren können, auch nach Neustarts.
  2. Storage: Stirbt der Pod mit den Daten und der Ersatz bekommt ein leeres Volume, war die Replikation umsonst. Das Volume muss am logischen Mitglied kleben, nicht am Pod-Exemplar.
  3. Reihenfolge: Bootstrap verteilter Systeme ist oft sequenziell — erst Mitglied 0 initialisiert den Cluster, dann treten 1 und 2 bei. Zehn gleichzeitig startende Pods erzeugen Chaos.

Das StatefulSet liefert genau diese drei Garantien. Nicht mehr — es macht aus Kubernetes keinen Datenbank-Operator — aber auch nicht weniger.

Die drei Garantien im Detail

Stabile Namen und DNS: StatefulSet-Pods heißen deterministisch name-0, name-1, name-2 — über jeden Neustart hinweg. Damit sie auch erreichbar stabil sind, braucht das StatefulSet einen Headless Service (clusterIP: None): Statt einer Load-Balancing-IP legt er DNS-Einträge pro Pod an — db-0.db-headless.default.svc.cluster.local zeigt immer auf den aktuellen Pod db-0, egal welche IP er gerade hat. Eine Replikations-Config kann so fest auf db-0 als initialen Primary verweisen. (Warum eine normale Service-IP hier nichts nützt, vertieft die Services-Lektion: Load Balancing über austauschbare Backends ist für Cluster-Mitglieder genau das falsche Modell.)

Stabiles Storage über volumeClaimTemplates: Statt eines gemeinsamen Volumes erzeugt das StatefulSet aus dem volumeClaimTemplates-Block pro Pod einen eigenen PersistentVolumeClaim: data-db-0, data-db-1, … Wird db-1 neu erstellt — auch auf einem anderen Node — bindet er wieder an seinen PVC data-db-1 und findet seine Daten vor. Wichtige Eigenschaft mit zwei Gesichtern: Die PVCs überleben sogar das Löschen des StatefulSets. Das schützt vor Datenverlust durch ein versehentliches Delete — und überrascht jeden, der das StatefulSet „zum Zurücksetzen” löscht und sich wundert, dass die alten Daten wieder da sind.

Geordnetes Deployment und Scaling: Per Default (podManagementPolicy: OrderedReady) startet das StatefulSet die Pods strikt nacheinander — db-1 erst, wenn db-0 Running und Ready ist. Herunterskaliert wird in umgekehrter Reihenfolge, von der höchsten Ordnungszahl abwärts. Rolling Updates laufen ebenfalls rückwärts geordnet, ohne maxSurge — es existiert nie ein „überzähliges” Mitglied. Der Trade-off ist offensichtlich: alles sequenziell, also langsamer als bei Deployments; podManagementPolicy: Parallel schaltet die Ordnung ab, wenn die Anwendung sie nicht braucht.

Eine ehrliche Einordnung gehört in jede Antwort: Das StatefulSet weiß nichts von PostgreSQL-Failover oder Kafka-Partitionen. Die Betriebslogik verteilter Systeme liefern Operatoren (CloudNativePG, Strimzi & Co.), die StatefulSets als Baustein nutzen — oder man nimmt gleich den Managed Service. „StatefulSet ≠ Datenbankbetrieb gelöst” ist ein Satz, der in Interviews Reife signalisiert.

DaemonSets: einer pro Node, ohne Zählen

Eine ganz andere Workload-Form: Manche Software gehört nicht n-mal in den Cluster, sondern genau einmal auf jeden Node — Log-Agents (Fluent Bit), Monitoring-Agents (node-exporter), CNI-Plugins, CSI-Node-Treiber. Mit einem Deployment wäre das ein Hack (Replicas = Node-Anzahl manuell nachführen, Anti-Affinity erzwingen).

Das DaemonSet macht daraus eine Garantie: ein Pod pro Node, automatisch auch auf jedem neu hinzukommenden Node, ohne dass jemand skaliert. Mit nodeSelector oder Affinity lässt sich die Menge einschränken („nur GPU-Nodes”), und über Tolerations laufen DaemonSet-Pods bei Bedarf auch auf tainted Nodes — Infrastruktur-Agents wie das CNI müssen sogar auf NotReady-Nodes können, weil ohne sie der Node nie ready würde. Wer in Lektion 4 den Node Problem Detector gesehen hat: genau so ein Fall.

Jobs und CronJobs: Workloads, die fertig werden sollen

Deployments, StatefulSets und DaemonSets halten Pods dauerhaft am Laufen. Batch-Arbeit — Migration, Report, Backup — soll dagegen enden. Der Job erstellt Pods und gilt als erfolgreich, wenn die konfigurierte Anzahl completions mit Exit 0 durchgelaufen ist; backoffLimit steuert Wiederholungen bei Fehlern, parallelism die Gleichzeitigkeit, und die restartPolicy ist hier folgerichtig OnFailure oder Never (Lektion 6) — nie Always. Der CronJob legt Jobs nach Zeitplan an (schedule: "0 3 * * *"); concurrencyPolicy regelt, was passiert, wenn der vorige Lauf noch läuft (Allow, Forbid, Replace).

Damit ist der Workload-Werkzeugkasten komplett: Deployment für austauschbare Dauerläufer, StatefulSet für identitätsbehaftete, DaemonSet für Node-gebundene, Job/CronJob für endliche Arbeit.

Praxis: Identität und Ordnung beobachten

kind create cluster --name statelab

# Headless Service + StatefulSet
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: demo-headless
spec:
  clusterIP: None
  selector: { app: demo }
  ports: [{ port: 80 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demo
spec:
  serviceName: demo-headless
  replicas: 3
  selector:
    matchLabels: { app: demo }
  template:
    metadata:
      labels: { app: demo }
    spec:
      containers:
        - name: web
          image: nginx:1.27
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests: { storage: 100Mi }
EOF

# Geordneter Start: demo-0 → demo-1 → demo-2, nie parallel
kubectl get pods -l app=demo --watch &
sleep 45; kill %1

# Pro Pod ein eigener PVC
kubectl get pvc

# Stabile Identität: Pod löschen — Ersatz hat denselben Namen und denselben PVC
kubectl delete pod demo-1
kubectl get pods -l app=demo   # demo-1 kommt als demo-1 zurück

# Per-Pod-DNS über den Headless Service
kubectl run dnscheck --rm -it --image=busybox:1.36 --restart=Never -- \
  nslookup demo-0.demo-headless.default.svc.cluster.local

Vergleiche das bewusst mit Lektion 7: Beim Deployment bekam der Ersatz-Pod einen neuen Zufallsnamen — hier kommt exakt demo-1 zurück. Aufräumen: kind delete cluster --name statelab (beachte: in echten Clustern blieben die PVCs bestehen).

Typische Stolperfallen

StatefulSet aus Gewohnheit: Eine stateless API braucht keine stabilen Namen — das StatefulSet macht ihre Rollouts nur langsamer (sequenziell, kein Surge). Die Frage ist nicht „hat die App State?”, sondern „braucht sie Identität, eigenes Storage pro Replica oder Startreihenfolge?”. Eine App, die ihren State in eine externe DB schreibt, gehört in ein Deployment.

Headless Service vergessen oder falsch verdrahtet: serviceName im StatefulSet muss auf einen existierenden Headless Service zeigen, sonst gibt es die per-Pod-DNS-Namen schlicht nicht — und die Cluster-Mitglieder finden sich nie.

PVC-Lebenszyklus falsch erwartet: StatefulSet gelöscht heißt nicht Daten gelöscht — die PVCs bleiben. Beim erneuten Anlegen binden die Pods an die alten Volumes. Für echtes Aufräumen die PVCs explizit löschen (und die Reclaim Policy der StorageClass kennen — Thema der Storage-Lektionen).

DaemonSet ohne Tolerations für Infrastruktur: Ein Monitoring- oder CNI-Agent, der tainted Nodes meidet, fehlt genau dort, wo es interessant wird. Infrastruktur-DaemonSets brauchen passende Tolerations — die offiziellen Charts bringen sie mit, Eigenbauten vergessen sie gern.

Interview-Vorbereitung

Die Standardfrage „Deployment vs. StatefulSet?” beantwortest du über die drei Garantien: stabile, geordnete Identität (name-0 …, per-Pod-DNS via Headless Service), Storage pro Replica über volumeClaimTemplates (PVC folgt dem logischen Mitglied und überlebt Löschungen), geordnetes Deployment/Scaling/Update. Dann die Abgrenzung: nötig für Systeme, deren Mitglieder sich kennen müssen (Datenbanken, Kafka, etcd) — unnötig für alles, was seinen State auslagert. Und die Grenze: Betriebslogik liefern Operatoren, nicht das StatefulSet.

Typische Follow-ups:

  • „Wozu der Headless Service?” — clusterIP: None erzeugt DNS pro Pod statt einer LB-IP; Cluster-Mitglieder brauchen direkte, stabile Adressen, kein Load Balancing.
  • „Was passiert mit den PVCs beim Löschen des StatefulSets?” — Sie bleiben bestehen (Datenschutz vor Versehen); Wiederanlegen bindet an die alten Daten.
  • „Wann DaemonSet statt Deployment?” — Wenn die Einheit „pro Node” ist statt „n Replicas”: Log-/Monitoring-Agents, CNI, CSI-Node-Plugins; skaliert automatisch mit Nodes.
  • „Job vs. CronJob vs. Deployment für ein Skript?” — Endliche Arbeit gehört in einen Job (completions, backoffLimit), wiederkehrende in einen CronJob; ein Deployment würde das beendete Skript per Always endlos neu starten.

Zusammenfassung

StatefulSets geben identitätsbehafteten Workloads, was Deployments bewusst weglassen: deterministische Namen mit per-Pod-DNS über einen Headless Service, ein eigenes, überlebendes Volume pro Mitglied via volumeClaimTemplates und strikt geordnetes Starten, Skalieren und Updaten — als Baustein, auf dem Operatoren den echten Datenbankbetrieb aufsetzen. DaemonSets stellen Node-lokale Agents genau einmal pro Node sicher, Jobs und CronJobs decken endliche und wiederkehrende Batch-Arbeit ab.

Alle diese Workloads brauchen Konfiguration und Zugangsdaten — und beides gehört nicht ins Image. Die nächste Lektion zeigt, wie ConfigMaps und Secrets das lösen und wo die Sicherheitsgrenzen von Kubernetes-Secrets wirklich liegen.