StatefulSets & Persistent Storage in Kubernetes

Stateful apps implementeren met geordende schaalbaarheid & persistente gegevens

Kubernetes StatefulSets zijn de voorkeur oplossing voor het beheren van toepassingen met staat, die stabiele identiteiten, persistente opslag en geordende implementatiemodellen vereisen—essentieel voor databases, gedistribueerde systemen en cachinglagen.

Als je nieuw bent in Kubernetes of een cluster instelt, overweeg dan het verkennen van Kubernetes distributies zoals k3s of MicroK8s voor ontwikkeling, of het installeren van Kubernetes met Kubespray voor productieklare clusters.

presentatie in de koffiezaak Deze mooie afbeelding is gegenereerd door AI model Flux 1 dev.

Wat zijn StatefulSets?

StatefulSets zijn een Kubernetes werkbelasting API-object dat specifiek is ontworpen voor het beheren van toepassingen met staat. In tegenstelling tot Deployments die alle pods als vervangbaar beschouwen, behouden StatefulSets een unieke identiteit voor elke pod met garanties over volgorde en uniekheid.

Belangrijke kenmerken:

  • Stabiele netwerkidentiteiten: Elke pod krijgt een voorspelbare hostnaam die blijft bestaan na herstarts
  • Persistente opslag: Afgestane PersistentVolumeClaims die pods volgen bij herschakelingen
  • Geordende implementatie: Pods worden opeenvolgend aangemaakt (0, 1, 2…) en worden in omgekeerde volgorde beëindigd
  • Geordende updates: Rolling updates verlopen in volgorde, wat de toepassingsstabiliteit waarborgt

StatefulSets zijn essentieel voor toepassingen zoals PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis en etcd—elke werklast waarbij de podidentiteit en datapersistente belangrijk zijn.

Begrijpen van persistente opslag in Kubernetes

Kubernetes biedt een geavanceerde opslagabstractielayer die de beheer van opslag loskoppelt van de podlevenscyclus:

Opslagcomponenten

PersistentVolume (PV): Een stuk opslag in het cluster dat door een administrator is aangemaakt of dynamisch is gegenereerd via een StorageClass. PVs bestaan onafhankelijk van pods.

PersistentVolumeClaim (PVC): Een verzoek naar opslag door een pod. PVCs binden aan beschikbare PVs die overeenkomen met hun eisen (grootte, toegangsmodus, storageclass).

StorageClass: Definieert verschillende “klassen” van opslag met verschillende provisioners (AWS EBS, GCE PD, Azure Disk, NFS, enz.) en parameters zoals replicatie, prestatieniveaus en back-upbeleid.

Toegangsmodi

  • ReadWriteOnce (RWO): Volume gemount als lees-schrijf door één knooppunt
  • ReadOnlyMany (ROX): Volume gemount als alleen-lezen door meerdere knooppunten
  • ReadWriteMany (RWX): Volume gemount als lees-schrijf door meerdere knooppunten (vereist speciale opslagback-ends)

StatefulSet-opslagarchitectuur

StatefulSets gebruiken volumeClaimTemplates om automatisch PersistentVolumeClaims aan te maken voor elke pod replica. Dit is fundamenteel anders dan Deployments:

Hoe volumeClaimTemplates werken

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

Wanneer je deze StatefulSet aanmaakt:

  1. Kubernetes maakt pod mysql-0 en PVC data-mysql-0
  2. Vervolgens maakt Kubernetes pod mysql-1 en PVC data-mysql-1
  3. Tot slot maakt Kubernetes pod mysql-2 en PVC data-mysql-2

Elke pod krijgt zijn eigen afgestane 10GB persistente volume. Als mysql-1 wordt verwijderd of herschakeld, maakt Kubernetes deze opnieuw aan en koppelt hetzelfde data-mysql-1 PVC aan, waardoor alle gegevens behouden blijven.

Het aanmaken van een headless service voor StatefulSets

StatefulSets vereisen een headless service om stabiele netwerkidentiteiten te bieden:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Dit maakt het een headless service
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Dit maakt DNS-entries voor elke 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

Toepassingen kunnen rechtstreeks verbinding maken met specifieke podinstanties via deze stabiele DNS-namen.

StatefulSet-implementatiemodellen

Voor teams die complexe Kubernetes-implementaties beheren, bieden Helm Charts een krachtige manier om StatefulSets te verpakken en te implementeren met sjablonering, versiebeheer en afhankelijkheidsbeheer. Helm vereenvoudigt het beheren van StatefulSet-configuraties over verschillende omgevingen.

Geordende schaalbaarheid

Bij het schalen van 3 naar 5 replica’s:

kubectl scale statefulset mysql --replicas=5

Kubernetes maakt pods in volgorde: mysql-3 → wacht op Ready → mysql-4

Bij het schalen van 5 naar 3 replica’s:

kubectl scale statefulset mysql --replicas=3

Kubernetes beëindigt in omgekeerde volgorde: mysql-4 → wacht op beëindiging → mysql-3

Rolling updates

StatefulSets ondersteunen twee updatestrategieën:

OnDelete: Handmatige updates—pods worden alleen bijgewerkt wanneer je ze verwijdert RollingUpdate: Automatische opeenvolgende updates in omgekeerde volgorde

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Alleen bijwerken van pods met ordinal >= 2

De partition-parameter maakt canary-implementaties mogelijk—je kunt eerst hoge-nummerde pods bijwerken en testen voordat je ze uitbreidt naar alle replica’s.

Opslagbest practices

Dynamische provisioning

Gebruik altijd StorageClasses voor dynamische volumeprovisionering:

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: Schakelt het vergroten van PVCs uit zonder ze opnieuw aan te maken reclaimPolicy: Retain houdt PV-gegevens na PVC-verwijdering vast, Delete verwijdert ze automatisch

PVC-retentiebeleid

Kubernetes 1.23+ ondersteunt persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Houd PVCs vast wanneer StatefulSet wordt verwijderd
    whenScaled: Delete     # Verwijder PVCs wanneer geschaald wordt

Opties:

  • Retain: Houd PVCs vast (standaardgedrag, veiligst)
  • Delete: Verwijder PVCs automatisch (handig voor ontwikkelomgevingen)

Back-upstrategieën

Volume snapshots: Gebruik VolumeSnapshot-resources om punt-in-tijd-back-ups aan te maken

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

Toepassingsniveau back-ups: Gebruik tools zoals mysqldump, pg_dump of Velero voor back-ups van databases

Gedistribueerde replicatie: Stel toepassingsniveau replicatie in (MySQL replicatie, PostgreSQL streaming replicatie) als eerste verdedigingslinie

Reële toepassingsgevallen

Databasecluster (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

Gedistribueerde cache (Redis)

Voor Redis clusters heb je zowel een StatefulSet als zorgvuldige configuratie nodig:

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

Berichtenwachtrij (Kafka)

Kafka vereist zowel persistente opslag voor logboeken als stabiele netwerkidentiteiten voor coördinatie van brokers:

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

Monitoring en probleemoplossing

Voor een uitgebreid overzicht van Kubernetes-commands gebruikt in deze sectie, zie de Kubernetes Cheatsheet.

Controleer StatefulSet-status

# Bekijk StatefulSet details
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Controleer de aanmaakvolgorde en status van pods
kubectl get pods -l app=mysql -w

# Bekijk PVC-status
kubectl get pvc
kubectl describe pvc data-mysql-0

Algemene problemen

Pod vastgelopen in pending: Controleer PVC-status en opslagbeschikbaarheid

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

Opslag vol: Verwijder PVC als StorageClass dat toestaat

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

Pod wil niet beëindigen: Controleer op toepassingsniveau vergrendelingen of volume-afkoppelingproblemen

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

Metrieken om te monitoren

  • Opslaggebruik: Monitor PVC-capaciteit en gebruikspersentage
  • I/O-prestaties: Volg IOPS, doorvoer en latentie
  • Podherstarts: Veel herstarts kunnen wijzen op opslagproblemen
  • PVC-bindtijd: Snel binden wijst op provisioningproblemen

Migratiestrategieën

Bij migratie naar StatefulSets, zorg er dan voor dat je Kubernetes-cluster correct is geconfigureerd. Voor homelab- of kleine clusteropstellingen, bekijk dan onze uitgebreide vergelijking van Kubernetes-distributies om het juiste platform te kiezen voor je werklastvereisten.

Van Deployment naar StatefulSet

  1. Maak een StatefulSet met volumeClaimTemplates
  2. Schaal Deployment af op een vlotte manier
  3. Herstel gegevens van back-ups naar StatefulSet-pods
  4. Bijwerken van DNS/service-verwijzingen
  5. Verwijder oude Deployment en PVCs

Back-up voor migratie

# Snapshot van bestaande PVCs
kubectl get pvc -o yaml > pvc-backup.yaml

# Maak volume snapshots
kubectl apply -f volume-snapshot.yaml

Beveiligingsoverwegingen

Opslagversleuteling

Schakel versleuteling in met StorageClass-parameters:

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

Toegangscontrole

Gebruik RBAC om wie kan maken/veranderen StatefulSets en PVCs:

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"]

Netwerkbeleid

Beperk pod-naar-pod communicatie:

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

Prestatieoptimalisatie

Opslagprestatieniveaus

Kies geschikte StorageClasses op basis van werklast:

  • Hoog IOPS: Databases met zware willekeurige lees/schrijf (gp3, io2)
  • Hoog doorvoer: Logaggregatie, analytics (st1, sc1)
  • Balans: Algemene doeleinden (gp3)

Podverdeling

Gebruik pod-anti-affinity om StatefulSet-pods over beschikbaarheidszones te verspreiden:

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

Resourceaanvragen en limieten

Stel geschikte resources in voor consistente prestaties:

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

Geavanceerde patronen

Stateful toepassing met init-containers

Gebruik init-containers voor databaseinitialisatie:

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 voor sidecars

Voeg back-upsidecars of monitoringagents toe:

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

ConfigMaps voor dynamische configuratie

Scheid configuratie van StatefulSet-definitie:

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