StatefulSets et stockage persistant dans Kubernetes

Déployez des applications stateful avec un scaling ordonné et des données persistantes

Kubernetes StatefulSets sont la solution idéale pour gérer les applications stateful qui nécessitent des identités stables, un stockage persistant et des schémas de déploiement ordonnés — essentielles pour les bases de données, les systèmes distribués et les couches de mise en cache.

Si vous êtes nouveau dans Kubernetes ou que vous configurez un cluster, envisagez d’explorer des distributions Kubernetes comme k3s ou MicroK8s pour le développement, ou l’installation de Kubernetes avec Kubespray pour des clusters de production.

Présentation dans le café Cette belle image a été générée par le modèle AI Flux 1 dev.

Qu’est-ce qu’un StatefulSet ?

Les StatefulSets sont un objet API de charge de travail Kubernetes conçu spécifiquement pour gérer les applications stateful. Contrairement aux Deployments qui traitent tous les pods comme interchangeables, les StatefulSets maintiennent une identité unique pour chaque pod avec des garanties concernant l’ordre et l’unicité.

Fonctionnalités clés :

  • Identifiants réseau stables : Chaque pod reçoit un hôte nommé prédéfini qui persiste après les redémarrages
  • Stockage persistant : Des PersistentVolumeClaims dédiés qui suivent les pods lors des reschedules
  • Déploiement ordonné : Les pods sont créés séquentiellement (0, 1, 2…) et terminés dans l’ordre inverse
  • Mises à jour ordonnées : Les mises à jour en roulement se déroulent dans l’ordre, assurant la stabilité de l’application

Les StatefulSets sont essentiels pour les applications comme PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis et etcd — tout workload où l’identité du pod et la persistance des données sont importantes.

Comprendre le stockage persistant dans Kubernetes

Kubernetes fournit une couche d’abstraction de stockage sophistiquée qui découple la gestion du stockage du cycle de vie des pods :

Composants de stockage

PersistentVolume (PV) : Un morceau de stockage dans le cluster provisionné par un administrateur ou créé dynamiquement via un StorageClass. Les PV existent indépendamment des pods.

PersistentVolumeClaim (PVC) : Une demande de stockage par un pod. Les PVC se lient à des PV disponibles qui correspondent à leurs exigences (taille, mode d’accès, classe de stockage).

StorageClass : Définit différentes “classes” de stockage avec divers fournisseurs (AWS EBS, GCE PD, Azure Disk, NFS, etc.) et des paramètres comme la réplication, les niveaux de performance et les politiques de sauvegarde.

Modes d’accès

  • ReadWriteOnce (RWO) : Volume monté en lecture-écriture par un seul nœud
  • ReadOnlyMany (ROX) : Volume monté en lecture seule par plusieurs nœuds
  • ReadWriteMany (RWX) : Volume monté en lecture-écriture par plusieurs nœuds (nécessite des backends de stockage spécifiques)

Architecture de stockage des StatefulSets

Les StatefulSets utilisent volumeClaimTemplates pour créer automatiquement des PersistentVolumeClaims pour chaque réplica de pod. Cela diffère fondamentalement des Deployments :

Fonctionnement des volumeClaimTemplates

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

Lorsque vous créez ce StatefulSet :

  1. Kubernetes crée le pod mysql-0 et le PVC data-mysql-0
  2. Puis crée le pod mysql-1 et le PVC data-mysql-1
  3. Enfin crée le pod mysql-2 et le PVC data-mysql-2

Chaque pod reçoit son propre volume persistant de 10 Go. Si mysql-1 est supprimé ou reschedulé, Kubernetes le recrée et réattache le même PVC data-mysql-1, préservant ainsi tous les données.

Créer un Service Headless pour les StatefulSets

Les StatefulSets nécessitent un Service Headless pour fournir des identités réseau stables :

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Cela rend le service headless
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Cela crée des entrées DNS pour chaque 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

Les applications peuvent se connecter directement à des instances de pod spécifiques en utilisant ces noms DNS stables.

Schémas de déploiement des StatefulSets

Pour les équipes gérant des déploiements Kubernetes complexes, les Helm Charts offrent un moyen puissant de packager et de déployer des StatefulSets avec du templating, la gestion des versions et la gestion des dépendances. Helm simplifie la gestion des configurations des StatefulSets à travers différents environnements.

Échelle ordonnée

Lors de l’échelle de 3 à 5 réplicas :

kubectl scale statefulset mysql --replicas=5

Kubernetes crée les pods dans l’ordre : mysql-3 → attend que Ready → mysql-4

Lors de l’échelle de 5 à 3 réplicas :

kubectl scale statefulset mysql --replicas=3

Kubernetes termine dans l’ordre inverse : mysql-4 → attend la terminaison → mysql-3

Mises à jour en roulement

Les StatefulSets prennent en charge deux stratégies de mise à jour :

OnDelete : Mises à jour manuelles — les pods ne sont mis à jour que lorsqu’on les supprime RollingUpdate : Mises à jour automatiques en séquence dans l’ordre inverse des ordinaux

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Ne mettre à jour que les pods avec un ordinal >= 2

Le paramètre partition permet les déploiements canary — vous pouvez mettre à jour les pods à haut numéro d’abord et tester avant de les déployer sur tous les réplicas.

Bonnes pratiques de stockage

Provisionnement dynamique

Utilisez toujours des StorageClasses pour le provisionnement dynamique des volumes :

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 : Active la redimension des PVC sans les recréer reclaimPolicy : Retain garde les données du PV après la suppression du PVC, Delete les supprime automatiquement

Politiques de rétention des PVC

Kubernetes 1.23+ prend en charge persistentVolumeClaimRetentionPolicy :

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Garde les PVC lors de la suppression du StatefulSet
    whenScaled: Delete     # Supprime les PVC lors de l'échelle

Options :

  • Retain : Garde les PVC (comportement par défaut, le plus sûr)
  • Delete : Supprime automatiquement les PVC (utile pour les environnements de développement)

Stratégies de sauvegarde

Snapshots de volume : Utilisez des ressources VolumeSnapshot pour créer des sauvegardes ponctuelles

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

Sauvegardes au niveau de l’application : Utilisez des outils comme mysqldump, pg_dump ou Velero pour les sauvegardes spécifiques aux bases de données

Réplication distribuée : Configurez la réplication au niveau de l’application (réplication MySQL, réplication en flux continu PostgreSQL) comme première ligne de défense

Cas d’utilisation réels

Cluster de base de données (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

Cache distribué (Redis)

Pour les clusters Redis, vous avez besoin à la fois d’un StatefulSet et d’une configuration soigneuse :

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

File d’attente de messages (Kafka)

Kafka nécessite à la fois un stockage persistant pour les journaux et des identités réseau stables pour la coordination des 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

Surveillance et dépannage

Pour une référence complète des commandes Kubernetes utilisées dans cette section, consultez le Kubernetes Cheatsheet.

Vérifier l’état des StatefulSets

# Afficher les détails du StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Vérifier l'ordre de création et l'état des pods
kubectl get pods -l app=mysql -w

# Afficher l'état des PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Problèmes courants

Pod bloqué en état Pending : Vérifiez l’état du PVC et la disponibilité du stockage

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

Stockage plein : Étendez le PVC si le StorageClass le permet

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

Pod ne se termine pas : Vérifiez les verrous au niveau de l’application ou les problèmes de démontage du volume

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

Métriques à surveiller

  • Utilisation du stockage : Surveillez la capacité et le pourcentage d’utilisation du PVC
  • Performance I/O : Suivez les IOPS, le débit et la latence
  • Redémarrages de pod : Les redémarrages fréquents peuvent indiquer des problèmes de stockage
  • Temps de liaison du PVC : Un temps de liaison lent indique des problèmes de provisionnement

Stratégies de migration

Lors de la migration vers des StatefulSets, assurez-vous que votre cluster Kubernetes est correctement configuré. Pour les configurations de laboratoire ou les petits clusters, consultez notre comparaison approfondie des distributions Kubernetes pour choisir la plateforme adaptée à vos besoins de charge.

De Deployment à StatefulSet

  1. Créer un StatefulSet avec des volumeClaimTemplates
  2. Échelonner le Deployment de manière gracieuse
  3. Restaurer les données depuis les sauvegardes vers les pods du StatefulSet
  4. Mettre à jour les références DNS/Service
  5. Supprimer l’ancien Deployment et les PVCs

Sauvegarde avant la migration

# Créer des snapshots des PVC existants
kubectl get pvc -o yaml > pvc-backup.yaml

# Créer des snapshots de volume
kubectl apply -f volume-snapshot.yaml

Considérations de sécurité

Chiffrement du stockage

Activez le chiffrement au repos en utilisant les paramètres de StorageClass :

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

Contrôle d’accès

Utilisez RBAC pour limiter qui peut créer/modifier des StatefulSets et des 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"]

Politiques réseau

Restreignez la communication pod à pod :

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

Optimisation des performances

Niveaux de performance du stockage

Choisissez les bonnes StorageClasses en fonction de la charge :

  • Haute IOPS : Bases de données avec des lectures/écritures aléatoires lourdes (gp3, io2)
  • Haute bande passante : Agrégation de journaux, analyse (st1, sc1)
  • Équilibré : Applications générales (gp3)

Répartition des pods

Utilisez des anti-affinités de pod pour répartir les pods des StatefulSets entre les zones de disponibilité :

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

Demandes et limites de ressources

Définissez des ressources appropriées pour une performance cohérente :

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

Schémas avancés

Application stateful avec des conteneurs initiaux

Utilisez des conteneurs initiaux pour l’initialisation de la base de données :

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

Pods à plusieurs conteneurs pour les sidecars

Ajoutez des sidecars de sauvegarde ou des agents de surveillance :

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

ConfigMaps pour la configuration dynamique

Séparez la configuration de la définition du StatefulSet :

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

Liens utiles