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-2dans cette démonstration. - Un Service headless, avec
clusterIP: None, permet la découverte DNS des Pods sans adresse IP virtuelle de Service. volumeClaimTemplatescré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
hostPathetnodeAffinity, puis une variante avec la StorageClassnfs-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.
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 :
serviceName: nginxrattache l’identité réseau au Service headless créé précédemment.replicas: 3demande les membresnginx-0,nginx-1etnginx-2.volumeClaimTemplatesproduit 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.
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.yamlet./deploy/deployment.yamlajoutent un niveau de dossier superflu. Depuis ce répertoire, les fichiers sontrbac.yamletdeployment.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
- Vidéo de référence : Kubernetes 041 — StatefulSets
- Playlist Kubernetes v2 de Xavki
- Dépôt tutorials-kubernetes-v2
- Chapitre 041 : variante locale
- Chapitre 041 : variante NFS
- Documentation officielle des StatefulSets
- Tutoriel officiel StatefulSet Basics
- PersistentVolumes et PVC
- DNS pour les Services et les Pods
- NFS Subdir External Provisioner
- Parcours Kubernetes sur Xavki
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.