Article

Kubernetes StatefulSet : identité stable, PVC et service headless

Un StatefulSet Kubernetes gère des Pods qui doivent conserver une identité reconnaissable au fil de leurs remplacements. Avec un Deployment, les réplicas sont généralement interchangeables. Avec un StatefulSet, nginx-0, nginx-1 et nginx-2 représentent trois membres distincts, auxquels Kubernetes peut associer des volumes persistants séparés.

Cette distinction devient utile lorsque l’application doit retrouver ses données ou joindre un membre précis d’un groupe. Elle ne transforme cependant pas une application en cluster répliqué : Kubernetes gère les Pods, leurs identités et leur association au stockage ; l’application reste responsable de sa réplication et de sa cohérence.

Le chapitre 041 du dépôt Kubernetes v2 utilise Nginx pour rendre ces mécanismes visibles. On peut ainsi comprendre le StatefulSet sans introduire simultanément la configuration d’une base de données distribuée.

TL;DR

  • Un StatefulSet attribue un nom stable à chaque Pod : nginx-0, nginx-1, nginx-2 dans cette démonstration.
  • Un Service headless, avec clusterIP: None, permet la découverte DNS des Pods sans adresse IP virtuelle de Service.
  • volumeClaimTemplates crée un PVC distinct pour chaque Pod et chaque modèle de volume.
  • Un Pod remplacé conserve son nom logique et retrouve son PVC ; son UID et son adresse IP peuvent changer.
  • La politique de gestion par défaut, OrderedReady, ordonne la création et la réduction du nombre de Pods.
  • Un StatefulSet ne réplique pas les fichiers et ne garantit pas, à lui seul, la haute disponibilité du stockage.
  • Le dépôt propose une variante locale avec hostPath et nodeAffinity, puis une variante avec la StorageClass nfs-client.

La vidéo de référence : Kubernetes 041 — StatefulSets

L’épisode associé est « Kubernetes 041 – StatefulSets : c’est quoi ? comment ça marche ? démo », publié sur la chaîne Xavki.

Regarder la vidéo sur YouTube.

Il appartient à la playlist Kubernetes v2 : formation et tutoriels pour débuter. Le README du dépôt le situe après les épisodes sur les volumes et les affinités, avant les DaemonSets.

Les exemples de cet article s’appuient sur 041-statefulset/notes.txt et 041-statefulset/notes-nfs.txt. Les comportements du contrôleur sont recoupés avec la documentation officielle.

StatefulSet : quel problème Kubernetes cherche-t-il à résoudre ?

Une API sans état peut généralement perdre un Pod et le remplacer par un autre. Le client rejoint un Service, pas une instance nommée. La disparition d’un réplica ne doit pas modifier les données métier, puisqu’elles sont conservées ailleurs.

Une application avec état peut avoir d’autres besoins : retrouver le disque d’un membre, connaître ses pairs par leur nom, ou démarrer ses instances dans un ordre précis. Un StatefulSet fournit les mécanismes Kubernetes correspondant à ces besoins.

La documentation des StatefulSets distingue notamment l’identité réseau stable, le stockage persistant stable et les opérations ordonnées. Il n’est pas nécessaire d’avoir besoin de toutes ces propriétés pour choisir cet objet.

En revanche, « mon application écrit sur disque » ne signifie pas automatiquement « il me faut un StatefulSet ». Une application à instance unique peut aussi utiliser un Deployment avec un PVC. Le critère déterminant est la relation entre l’identité d’une instance, son stockage et son cycle de vie.

StatefulSet ou Deployment : les différences utiles

Besoin Deployment StatefulSet
Réplicas interchangeables Modèle habituel Membres identifiés individuellement
Nom du Pod après remplacement Nouveau nom généralement généré Même nom logique et même ordinal
PVC distinct créé pour chaque réplica Pas via volumeClaimTemplates Pris en charge nativement
Ordre de création des membres Pas de garantie par ordinal Ordonné avec OrderedReady
Adresse DNS propre à chaque membre À organiser autrement Modèle prévu avec Service headless
Réplication des données applicatives Non fournie Non fournie

Un frontal HTTP stateless convient généralement à un Deployment. Une application distribuée qui identifie durablement ses membres peut bénéficier d’un StatefulSet, souvent piloté par un opérateur spécialisé.

Le modèle mental : Pod, PVC, PV et DNS

Dans la démonstration, le StatefulSet s’appelle nginx et le modèle de PVC s’appelle nginx-html.

StatefulSet nginx
├── nginx-0 → PVC nginx-html-nginx-0 → PV disponible A
├── nginx-1 → PVC nginx-html-nginx-1 → PV disponible B
└── nginx-2 → PVC nginx-html-nginx-2 → PV disponible C

Service headless nginx
├── nginx-0.nginx.<namespace>.svc.<domaine-du-cluster>
├── nginx-1.nginx.<namespace>.svc.<domaine-du-cluster>
└── nginx-2.nginx.<namespace>.svc.<domaine-du-cluster>

Le nom du PVC suit la forme <nom-du-template>-<nom-du-statefulset>-<ordinal>. Le contrôleur peut donc retrouver le stockage du membre nginx-1 lorsqu’il recrée ce Pod.

Il faut distinguer cette identité logique de l’identité de l’objet API : un nouveau Pod possède un nouvel UID. Il peut aussi recevoir une nouvelle adresse IP. Les autres applications doivent utiliser le DNS plutôt que mémoriser son IP.

Découvrez  RabbitMQ 009 - Binaires & Répertoires

Autre subtilité : le suffixe 0 ne signifie pas que nginx-0 obtiendra nécessairement pv-affinity-k0c1. Le binding dépend des volumes compatibles et des contraintes de placement, pas d’une correspondance entre leurs noms.

Préparer le stockage local du chapitre

La première variante utilise trois PV statiques de 3Gi, associés aux nœuds k0c1, k0c2 et k0c3. Chaque PVC demandera 1Gi.

Le laboratoire suppose un cluster accessible avec kubectl, trois nœuds pouvant accueillir les Pods et un répertoire /mnt/data disponible sur chacun. Vérifiez les labels réellement présents :

kubectl get nodes -L kubernetes.io/hostname

Adaptez les valeurs du dépôt à votre cluster. Les noms k0c1, k0c2 et k0c3 sont des valeurs de laboratoire.

Créer la StorageClass manual

Ce manifeste reprend la StorageClass de notes.txt :

kubectl apply -f - <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: manual
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF

kubernetes.io/no-provisioner signifie que cette classe ne créera pas automatiquement de volume. Les PV doivent exister.

WaitForFirstConsumer reporte le binding jusqu’à ce qu’un Pod consomme le PVC. Kubernetes peut ainsi prendre en compte les contraintes de placement au moment de choisir un volume. Un PVC temporairement Pending avant la création de son consommateur n’est donc pas forcément anormal.

Créer les trois PersistentVolumes

Le dépôt écrit trois manifestes presque identiques. La boucle suivante est une adaptation éditoriale de ces manifestes : elle conserve les noms, capacités, chemins et affinités de la source.

for node in k0c1 k0c2 k0c3; do
  kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-affinity-${node}
spec:
  capacity:
    storage: 3Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ${node}
EOF
done

Les PV sont des ressources à l’échelle du cluster, tandis que les PVC appartiennent à un namespace. Utilisez ce laboratoire sur un environnement dédié, sans réutiliser des noms de ressources existantes.

Avec hostPath, /mnt/data désigne le système de fichiers du nœud. Ce chemin n’est pas un stockage partagé entre les machines. La nodeAffinity du PV associe donc le volume au nœud portant les données.

Cette variante prolonge directement le chapitre sur PersistentVolume et nodeAffinity. Elle illustre le placement du stockage ; elle ne fournit pas de réplication du disque. La documentation Kubernetes présente hostPath comme une solution de démonstration et recommande des mécanismes adaptés, tels que les volumes locaux ou un pilote CSI, pour les usages réels.

Créer le Service headless Nginx

Le Service du dépôt utilise clusterIP: None :

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  clusterIP: None
  ports:
    - port: 80
  selector:
    app: nginx
EOF

Un Service headless ne fournit pas l’adresse IP virtuelle d’un Service ClusterIP classique. Avec ce sélecteur, Kubernetes publie les informations permettant de joindre directement les Pods correspondants via le DNS.

Dans le namespace default, avec le domaine de cluster habituel cluster.local, un membre pourra être joint par :

nginx-0.nginx.default.svc.cluster.local

La publication DNS dépend notamment de la disponibilité des endpoints. Par défaut, il ne faut pas supposer qu’un Pod non prêt possède déjà une entrée utilisable. La documentation DNS Kubernetes détaille ce comportement et l’option publishNotReadyAddresses.

Le Service headless ne constitue pas non plus une exposition publique : ces noms sont destinés à la découverte interne au cluster.

Déployer le StatefulSet avec un PVC par Pod

Voici le manifeste du chapitre, remis en forme sans modifier son fonctionnement :

kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
spec:
  serviceName: nginx
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: init-hostname
          image: busybox
          command: ['sh', '-c']
          args:
            - 'printenv POD_NAME > /usr/share/nginx/html/index.html;
               sleep infinity'
          volumeMounts:
            - name: nginx-html
              mountPath: /usr/share/nginx/html
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-html
              mountPath: /usr/share/nginx/html
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
  volumeClaimTemplates:
    - metadata:
        name: nginx-html
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: manual
        resources:
          requests:
            storage: 1Gi
EOF

Trois champs structurent la démonstration :

  1. serviceName: nginx rattache l’identité réseau au Service headless créé précédemment.
  2. replicas: 3 demande les membres nginx-0, nginx-1 et nginx-2.
  3. volumeClaimTemplates produit les trois PVC qui seront montés dans les Pods.

Les deux conteneurs d’un même Pod montent son volume nginx-html. BusyBox écrit le nom du Pod dans index.html, puis Nginx sert ce fichier. POD_NAME provient de la Downward API, via metadata.name.

Attention au nom init-hostname

Dans la source, init-hostname figure sous containers, et non sous initContainers. C’est donc un conteneur ordinaire qui reste actif, pas un init container exécuté avant Nginx.

Nginx peut démarrer avant l’écriture du fichier. Pour garantir une initialisation préalable, une adaptation consiste à déplacer ce conteneur sous initContainers et à supprimer sleep infinity : un init container classique doit terminer pour laisser démarrer les conteneurs applicatifs.

Les images busybox et nginx:latest sont celles du support original. Pour figer la reproductibilité d’un laboratoire, choisissez des versions ou des digests précis. Aucun déploiement de ces manifestes n’a été exécuté pour la rédaction de cet article.

Vérifier les Pods, les PVC et la persistance

Les commandes suivantes adaptent les opérations du tutoriel officiel StatefulSet Basics aux noms utilisés dans le dépôt.

kubectl get statefulset nginx
kubectl get pods -l app=nginx -o wide
kubectl get pvc
kubectl get pv
kubectl get service nginx

Les noms attendus des PVC sont :

nginx-html-nginx-0
nginx-html-nginx-1
nginx-html-nginx-2

Avec la politique par défaut OrderedReady, le contrôleur attend que nginx-0 soit prêt avant de créer nginx-1, puis procède de même pour nginx-2. Un premier Pod bloqué peut donc expliquer l’absence des suivants.

Pour lire les noms écrits sur les volumes :

for i in 0 1 2; do
  kubectl exec "nginx-$i" -c nginx -- cat /usr/share/nginx/html/index.html
done

La sortie attendue est respectivement nginx-0, nginx-1 et nginx-2. Pour vérifier aussi le service HTTP, le conteneur BusyBox du chapitre fournit un client léger :

kubectl exec nginx-0 -c init-hostname -- wget -qO- http://127.0.0.1/

Tester une donnée que le démarrage ne réécrit pas

Le script BusyBox réécrit index.html à chaque démarrage. Retrouver uniquement le nom du Pod dans ce fichier ne suffit donc pas à prouver que son contenu a survécu.

Découvrez  RabbitMQ 023 - Kubernetes : manager un cluster externe (topology)

Pour une démonstration plus explicite, créez un autre fichier, puis remplacez le Pod dans le laboratoire :

kubectl exec nginx-0 -c nginx -- sh -c \
  'echo "donnee-conservee" > /usr/share/nginx/html/persistence.txt'

kubectl delete pod nginx-0
kubectl get pods -l app=nginx --watch

Arrêtez l’observation avec Ctrl+C lorsque le nouveau nginx-0 est prêt, puis vérifiez :

kubectl exec nginx-0 -c nginx -- cat /usr/share/nginx/html/persistence.txt

Le résultat attendu est donnee-conservee, tant que le même PVC et son stockage ont été conservés. Cette commande est une adaptation pédagogique du test de persistance officiel, pas une sortie observée pendant la rédaction.

Que se passe-t-il lors d’un changement du nombre de réplicas ?

Avec OrderedReady, une réduction de trois à deux réplicas supprime d’abord nginx-2 :

kubectl scale statefulset nginx --replicas=2
kubectl get pods -l app=nginx
kubectl get pvc

Dans le manifeste présenté, aucune politique de suppression automatique des PVC n’est définie : leur conservation est le comportement par défaut. Revenir à trois réplicas permet donc à nginx-2 de retrouver son PVC :

kubectl scale statefulset nginx --replicas=3

Les versions récentes de Kubernetes permettent de configurer persistentVolumeClaimRetentionPolicy. Il faut donc vérifier le manifeste effectif avant de généraliser le comportement de conservation à un autre StatefulSet.

Cette politique est distincte de persistentVolumeReclaimPolicy: Retain, portée ici par les PV, qui intervient lorsque la réclamation est libérée. Conserver un PVC et conserver un volume après suppression de son PVC sont deux questions différentes.

Enfin, demander quatre réplicas dans ce laboratoire local nécessiterait un quatrième PV compatible. Un PVC réserve un PV entier : le surplus entre les 3Gi annoncés par le PV et les 1Gi demandés ne permet pas de l’attribuer à plusieurs PVC.

Variante NFS : même StatefulSet, provisionnement différent

Le fichier notes-nfs.txt reprend le Service et le StatefulSet, mais remplace storageClassName: manual par storageClassName: nfs-client.

Il s’appuie sur le projet NFS Subdir External Provisioner. Le provisionneur crée des sous-répertoires dédiés sur un export NFS existant et les associe aux PV demandés par les PVC.

Les prérequis changent : un serveur NFS accessible depuis les nœuds, les outils clients NFS sur les nœuds Linux concernés, les droits RBAC et un provisionneur correctement configuré doivent être en place.

Deux points des notes demandent une correction avant exécution :

  • Après cd nfs-provisionner/deploy/, les chemins ./deploy/rbac.yaml et ./deploy/deployment.yaml ajoutent un niveau de dossier superflu. Depuis ce répertoire, les fichiers sont rbac.yaml et deployment.yaml.
  • Après édition de deployment.yaml, il faut appliquer ce manifeste pour lancer le provisionneur. Cette étape n’apparaît pas dans la séquence des notes.

La procédure du projet upstream décrit le déploiement complet. Les réglages NFS no_root_squash et chmod 777 du support sont des simplifications de laboratoire ; ils ne constituent pas une configuration de permissions à reprendre telle quelle pour des données réelles.

Aspect Variante locale du chapitre Variante NFS
Création des PV Manuelle Par le provisionneur
Classe utilisée manual nfs-client
Données Répertoire d’un nœud Sous-répertoire d’un export NFS
Contrainte de placement Affinité vers le nœud du PV Accès réseau au serveur NFS
Dépendance principale Disponibilité du nœud et de son disque Disponibilité du service NFS

Les trois Pods restent associés à trois PVC distincts. Un serveur NFS commun ne signifie pas que les trois réplicas écrivent automatiquement dans le même répertoire.

Diagnostic : StatefulSet bloqué, PVC Pending ou DNS absent

Seul nginx-0 apparaît

Commencez par son état et ses événements. Le contrôleur peut simplement attendre qu’il soit prêt :

kubectl describe pod nginx-0
kubectl logs nginx-0 -c init-hostname
kubectl logs nginx-0 -c nginx

Vérifiez notamment le téléchargement des images, le montage du volume et les permissions du répertoire.

Le PVC reste Pending

kubectl describe pvc nginx-html-nginx-0
kubectl get pv
kubectl get storageclass

Pour la variante manual, vérifiez la capacité, le mode d’accès, le nom de la classe, la disponibilité des PV et la compatibilité entre leur nodeAffinity et les nœuds pouvant accueillir le Pod.

Pour la variante NFS, vérifiez aussi le provisionneur :

kubectl get all -n nfs-system

Le nom DNS du Pod ne répond pas

Contrôlez le nom du Service, le namespace, spec.serviceName, les labels du Pod et le sélecteur du Service. Vérifiez aussi que le Pod est prêt. Un cache DNS négatif peut temporairement conserver une réponse obtenue avant la création du Pod.

Le nœud du volume local tombe

Le StatefulSet ne déplace pas le contenu de /mnt/data. Si la nodeAffinity du PV impose un nœud indisponible, l’identité du Pod ne suffit pas à rendre ses données accessibles ailleurs. Il faut traiter séparément la disponibilité et la restauration du stockage.

Liens utiles

FAQ : StatefulSet, PVC et stockage persistant

Un StatefulSet conserve-t-il l’adresse IP de chaque Pod ?

Non. Il conserve une identité logique stable, notamment le nom du Pod. Le Pod de remplacement peut avoir une nouvelle IP. Utilisez son nom DNS pour le joindre.

Un StatefulSet crée-t-il automatiquement des volumes ?

Il crée les PVC définis par volumeClaimTemplates. La création effective des PV dépend du stockage : provisionnement dynamique avec une classe adaptée, ou PV déjà créés dans la variante statique.

Supprimer un Pod supprime-t-il ses données ?

Dans ce laboratoire, supprimer uniquement le Pod conserve son PVC et les données du volume. La suppression des PVC, les politiques de rétention et les opérations sur le stockage constituent des actions distinctes.

ReadWriteOnce signifie-t-il qu’un seul Pod peut utiliser le volume ?

Non. ReadWriteOnce décrit un montage en lecture-écriture depuis un seul nœud, pas nécessairement un seul Pod. ReadWriteOncePod, disponible avec les volumes CSI compatibles, exprime une exclusivité au niveau du Pod. Le modèle présenté fournit néanmoins un PVC distinct à chaque membre.

Pourquoi utiliser Nginx pour expliquer une application avec état ?

Nginx permet de visualiser simplement le contenu d’un volume et l’identité d’un Pod. Le but du laboratoire est d’étudier le comportement du contrôleur, sans ajouter la configuration d’un cluster de bases de données.

Un StatefulSet rend-il une base de données hautement disponible ?

Non, pas à lui seul. La réplication, l’élection d’un leader, le quorum, les sauvegardes et la restauration dépendent de l’application et de son architecture de stockage.

Conclusion

Le StatefulSet organise une relation durable entre un membre nommé, son identité réseau et son stockage. Le chapitre 041 permet de la suivre concrètement : nginx-0 possède son PVC, monte son volume et peut être retrouvé par son DNS après un remplacement.

Pour choisir cet objet, posez trois questions : mes réplicas sont-ils interchangeables ? Chaque membre doit-il retrouver ses propres données ? Le cycle de vie nécessite-t-il un ordre précis ? Ces réponses permettent de distinguer le besoin de stockage persistant du besoin, plus spécifique, d’un StatefulSet.

Transparence IA

Ce contenu a été partiellement rédigé ou structuré avec l aide d outils d intelligence artificielle, puis relu, corrigé et complété par l auteur avant publication.

Explorer les formations Xavki

Pour apprendre dans l ordre, repartez depuis la roadmap ou une playlist thematique.