StatefulSets & Persistente Speicherung in Kubernetes

Bereitstellen von zustandsbehafteten Anwendungen mit geordneter Skalierung und persistenter Daten

Kubernetes StatefulSets sind die optimale Lösung für die Verwaltung von zustandsbehafteten Anwendungen, die stabile Identitäten, dauerhafte Speicherung und geordnete Bereitstellungsmuster benötigen – essenziell für Datenbanken, verteilte Systeme und Caching-Schichten.

Wenn Sie neu bei Kubernetes sind oder einen Cluster einrichten, sollten Sie Kubernetes-Distributionen wie k3s oder MicroK8s für die Entwicklung in Betracht ziehen oder Kubernetes mit Kubespray installieren für produktionsreife Cluster.

Präsentation im Café Dieses schöne Bild wurde von der KI-Modell Flux 1 dev erstellt.

Was sind StatefulSets?

StatefulSets sind ein Kubernetes-Arbeitslast-API-Objekt, das speziell für die Verwaltung von zustandsbehafteten Anwendungen entwickelt wurde. Im Gegensatz zu Deployments, die alle Pods als austauschbar behandeln, erhalten StatefulSets eine eindeutige Identität für jeden Pod mit Garantien hinsichtlich Reihenfolge und Eindeutigkeit.

Wichtige Merkmale:

  • Stabile Netzwerk-Identifikatoren: Jeder Pod erhält einen vorhersehbaren Hostnamen, der über Neustarts hinweg besteht
  • Dauerhafter Speicher: Dedizierte PersistentVolumeClaims, die Pods durch Neuplanungen folgen
  • Geordnete Bereitstellung: Pods werden sequenziell erstellt (0, 1, 2…) und in umgekehrter Reihenfolge beendet
  • Geordnete Updates: Rollierende Updates erfolgen in Reihenfolge, um die Anwendungsstabilität zu gewährleisten

StatefulSets sind entscheidend für Anwendungen wie PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis und etcd – für jede Arbeitslast, bei der Pod-Identität und Datenspeicherung wichtig sind.

Verständnis des dauerhaften Speichers in Kubernetes

Kubernetes bietet eine ausgefeilte Speicherabstraktionsschicht, die die Speicherverwaltung vom Pod-Lebenszyklus entkoppelt:

Speicherkomponenten

PersistentVolume (PV): Ein Speicherstück im Cluster, das von einem Administrator bereitgestellt oder dynamisch über eine StorageClass erstellt wird. PVs existieren unabhängig von Pods.

PersistentVolumeClaim (PVC): Eine Speicheranforderung durch einen Pod. PVCs binden an verfügbare PVs, die ihren Anforderungen (Größe, Zugriffsmodus, Speicherklasse) entsprechen.

StorageClass: Definiert verschiedene “Klassen” von Speicher mit verschiedenen Provisionern (AWS EBS, GCE PD, Azure Disk, NFS usw.) und Parametern wie Replikation, Leistungsebenen und Backup-Richtlinien.

Zugriffsmodi

  • ReadWriteOnce (RWO): Volume wird als Schreib-Lese von einem einzelnen Knoten gemountet
  • ReadOnlyMany (ROX): Volume wird als schreibgeschützt von vielen Knoten gemountet
  • ReadWriteMany (RWX): Volume wird als Schreib-Lese von vielen Knoten gemountet (erfordert spezielle Speicher-Backends)

StatefulSet-Speicherarchitektur

StatefulSets verwenden volumeClaimTemplates, um automatisch PersistentVolumeClaims für jede Pod-Replik zu erstellen. Dies unterscheidet sich grundlegend von Deployments:

Wie volumeClaimTemplates funktionieren

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Wenn Sie diesen StatefulSet erstellen:

  1. Kubernetes erstellt den Pod mysql-0 und den PVC data-mysql-0
  2. Dann erstellt es den Pod mysql-1 und den PVC data-mysql-1
  3. Schließlich erstellt es den Pod mysql-2 und den PVC data-mysql-2

Jeder Pod erhält seinen eigenen dedizierten 10GB dauerhaften Speicher. Wenn mysql-1 gelöscht oder neu geplant wird, erstellt Kubernetes es neu und bindet den gleichen data-mysql-1 PVC, wodurch alle Daten erhalten bleiben.

Erstellung eines Headless Service für StatefulSets

StatefulSets benötigen einen Headless Service, um stabile Netzwerkidentitäten bereitzustellen:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Dies macht es zu einem headless Service
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Dies erstellt DNS-Einträge für jeden Pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Anwendungen können sich direkt mit spezifischen Pod-Instanzen über diese stabilen DNS-Namen verbinden.

StatefulSet-Bereitstellungsmuster

Für Teams, die komplexe Kubernetes-Bereitstellungen verwalten, bieten Helm-Charts eine leistungsstarke Möglichkeit, StatefulSets mit Vorlagen, Versionierung und Abhängigkeitsverwaltung zu paketieren und bereitzustellen. Helm vereinfacht die Verwaltung von StatefulSet-Konfigurationen in verschiedenen Umgebungen.

Geordnetes Skalieren

Beim Hochskalieren von 3 auf 5 Replikate:

kubectl scale statefulset mysql --replicas=5

Kubernetes erstellt Pods in Reihenfolge: mysql-3 → wartet auf Bereit → mysql-4

Beim Herunterskalieren von 5 auf 3:

kubectl scale statefulset mysql --replicas=3

Kubernetes beendet in umgekehrter Reihenfolge: mysql-4 → wartet auf Beendigung → mysql-3

Rollierende Updates

StatefulSets unterstützen zwei Update-Strategien:

OnDelete: Manuelle Updates – Pods werden nur aktualisiert, wenn Sie sie löschen RollingUpdate: Automatische sequenzielle Updates in umgekehrter Ordnungsreihenfolge

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Nur Pods mit Ordnungszahl >= 2 aktualisieren

Der Parameter partition ermöglicht Canary-Bereitstellungen – Sie können zunächst hoch nummerierte Pods aktualisieren und testen, bevor Sie auf alle Replikate ausrollen.

Speicher-Best Practices

Dynamische Bereitstellung

Verwenden Sie immer StorageClasses für die dynamische Volumenbereitstellung:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Ermöglicht das Vergrößern von PVCs ohne Neu-Erstellung reclaimPolicy: Retain behält PV-Daten nach PVC-Löschung, Delete entfernt sie automatisch

PVC-Retentionsrichtlinien

Kubernetes 1.23+ unterstützt persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # PVCs behalten, wenn StatefulSet gelöscht wird
    whenScaled: Delete     # PVCs löschen, wenn herunterskaliert wird

Optionen:

  • Retain: PVCs behalten (Standardverhalten, sicherste Option)
  • Delete: PVCs automatisch löschen (nützlich für Entwicklungsumgebungen)

Backup-Strategien

Volume Snapshots: Verwenden Sie VolumeSnapshot-Ressourcen, um Backups zum Zeitpunkt der Erstellung zu erstellen

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Anwendungsbezogene Backups: Verwenden Sie Tools wie mysqldump, pg_dump oder Velero für datenbankspezifische Backups

Verteilte Replikation: Konfigurieren Sie die Replikation auf Anwendungsebene (MySQL-Replikation, PostgreSQL-Streaming-Replikation) als erste Verteidigungslinie

Echte Anwendungsfälle

Datenbank-Cluster (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Verteilter Cache (Redis)

Für Redis-Cluster benötigen Sie sowohl StatefulSet als auch sorgfältige Konfiguration:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Nachrichtenwarteschlange (Kafka)

Kafka benötigt sowohl dauerhaften Speicher für Protokolle als auch stabile Netzwerkidentitäten für die Broker-Koordination:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Überwachung und Fehlerbehebung

Für eine umfassende Referenz der in diesem Abschnitt verwendeten Kubernetes-Befehle siehe den Kubernetes Cheatsheet.

StatefulSet-Status prüfen

# StatefulSet-Details anzeigen
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Pod-Erstellungsreihenfolge und -status prüfen
kubectl get pods -l app=mysql -w

# PVC-Status anzeigen
kubectl get pvc
kubectl describe pvc data-mysql-0

Häufige Probleme

Pod bleibt im Status “Pending”: Prüfen Sie den PVC-Status und die Speicherverfügbarkeit

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Speicher voll: Erweitern Sie den PVC, falls die StorageClass dies zulässt

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod wird nicht beendet: Prüfen Sie auf Anwendungslevel-Sperren oder Volume-Unmount-Probleme

kubectl delete pod mysql-0 --grace-period=0 --force

Zu überwachende Metriken

  • Speichernutzung: Überwachen Sie die PVC-Kapazität und den Nutzungsprozentsatz
  • I/O-Leistung: Verfolgen Sie IOPS, Durchsatz und Latenz
  • Pod-Neustarts: Häufige Neustarts können auf Speicherprobleme hinweisen
  • PVC-Bindungszeit: Langsame Bindung deutet auf Bereitstellungsprobleme hin

Migrationsstrategien

Beim Migrieren zu StatefulSets stellen Sie sicher, dass Ihr Kubernetes-Cluster richtig konfiguriert ist. Für Homelab- oder kleine Cluster-Setups überprüfen Sie unseren umfassenden Vergleich von Kubernetes-Distributionen, um die richtige Plattform für Ihre Workload-Anforderungen zu wählen.

Von Deployment zu StatefulSet

  1. StatefulSet erstellen mit volumeClaimTemplates
  2. Deployment herunterskalieren sanft
  3. Daten wiederherstellen aus Backups zu StatefulSet-Pods
  4. DNS/Dienst-Referenzen aktualisieren
  5. Altes Deployment und PVCs löschen

Backup vor der Migration

# Snapshots bestehender PVCs erstellen
kubectl get pvc -o yaml > pvc-backup.yaml

# Volumesnapshots erstellen
kubectl apply -f volume-snapshot.yaml

Sicherheitsüberlegungen

Speicherverschlüsselung

Aktivieren Sie die Verschlüsselung im Ruhezustand mit StorageClass-Parametern:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Zugriffskontrolle

Verwenden Sie RBAC, um zu beschränken, wer StatefulSets und PVCs erstellen/ändern darf:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Netzwerkrichtlinien

Einschränken der Pod-zu-Pod-Kommunikation:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Leistungsoptimierung

Speicherleistungsklassen

Wählen Sie geeignete StorageClasses basierend auf der Workload:

  • Hohe IOPS: Datenbanken mit starkem zufälligen Lesen/Schreiben (gp3, io2)
  • Hoher Durchsatz: Log-Aggregation, Analysen (st1, sc1)
  • Ausgewogen: Allgemeine Anwendungen (gp3)

Pod-Verteilung

Verwenden Sie Pod-Anti-Affinität, um StatefulSet-Pods über Verfügbarkeitszonen zu verteilen:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Ressourcenanforderungen und -grenzen

Legen Sie geeignete Ressourcen für eine konsistente Leistung fest:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Fortgeschrittene Muster

Stateful-Anwendung mit Init-Containern

Verwenden Sie Init-Container für die Datenbankinitialisierung:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Multi-Container-Pods für Sidecars

Fügen Sie Backup-Sidecars oder Überwachungsagenten hinzu:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... mysql-Konfiguration ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps für dynamische Konfiguration

Trennen Sie die Konfiguration von der StatefulSet-Definition:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config