Déployer un StatefulSet sur Kubernetes — Guide pas-à-pas

Manifestes, déploiement, vérifications et tests (persistance, identité réseau stable, redémarrage, scale, update, debug).

0) Prérequis

  • kubectl configuré et pointant sur ton cluster : kubectl version --client && kubectl get nodes.
  • Optionnel : créer un namespace de test pour éviter de polluer default :
    kubectl create namespace demo-ss || true
    kubectl config set-context --current --namespace=demo-ss
  • Connaître ton StorageClass (si tu veux PV dynamiques) : kubectl get storageclass.

1) Manifests (choisis l’option adaptée)

Deux options : A pour clusters avec provisioner dynamique (ex. GKE, EKS, Minikube avec standard), B pour cluster local sans provisioner (hostPath / PV statiques).

Option A — StatefulSet + PVC dynamique (StorageClass standard)

Enregistrer dans statefulset-nginx.yaml

# statefulset-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-headless
  labels:
    app: nginx
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
    - port: 80
      name: http

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
spec:
  serviceName: "nginx-headless"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
          name: http
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 1Gi

Option B — Variante locale / Minikube / hostPath (si pas de provisioner)

Enregistrer dans statefulset-nginx-hostpath.yaml. Note : pour hostPath pur, un administrateur doit créer des PV statiques pointant vers des chemins sur chaque nœud.

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
  - port: 80
    name: http

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
spec:
  serviceName: "nginx-headless"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
          name: http
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ""
      resources:
        requests:
          storage: 1Gi

Remarque : si PVC reste en Pending, vérifie le StorageClass ou crée des PV statiques (hostPath) correspondant aux réclamations.

2) Déployer

Appliquer le manifest choisi :

kubectl apply -f statefulset-nginx.yaml
# ou
kubectl apply -f statefulset-nginx-hostpath.yaml

3) Vérifications initiales

Surveiller la création et vérifier que les PVC sont Bound :

kubectl get statefulset
kubectl get sts -o wide
kubectl get pods -l app=nginx -o wide
kubectl get pvc
kubectl get pv
kubectl describe sts nginx
kubectl describe pod nginx-0
kubectl describe pvc www-nginx-0

Résultat attendu : pods nginx-0, nginx-1, nginx-2 en Running; 3 PVCs www-nginx-0/1/2 en Bound.

4) Tests pratiques — identité réseau & persistance

Test A — identité réseau stable (hostname + FQDN)

Écrire un fichier depuis nginx-0 et lire depuis nginx-1 via le FQDN :

kubectl exec -it nginx-0 -- /bin/sh -c "hostname; echo 'hello-from-nginx-0' > /usr/share/nginx/html/index.html; cat /usr/share/nginx/html/index.html"
kubectl exec -it nginx-1 -- /bin/sh -c "wget -qO- http://nginx-0.nginx-headless:80 || curl -s http://nginx-0.nginx-headless:80"

Test B — persistance après suppression d’un Pod

Écrire un fichier unique sur nginx-1, supprimer le pod, attendre la recréation puis vérifier :

kubectl exec -it nginx-1 -- /bin/sh -c "echo 'persist-$(date +%s)' > /usr/share/nginx/html/unique.txt; cat /usr/share/nginx/html/unique.txt"
kubectl delete pod nginx-1
# attendre que nginx-1 soit recréé (kubectl get pods -w)
kubectl exec -it nginx-1 -- cat /usr/share/nginx/html/unique.txt

Si le contenu est toujours présent → la PVC/PV a correctement persisté les données.

Test C — vérifier que chaque réplique a son PVC/PV

kubectl get pvc -o wide
kubectl describe pvc www-nginx-0
kubectl describe pvc www-nginx-1
kubectl describe pvc www-nginx-2

5) Exposer un pod/service pour tests externes

Option rapide — port-forward

kubectl port-forward pod/nginx-0 8080:80
# puis depuis ta machine
curl http://127.0.0.1:8080

Option NodePort (exposer au niveau nœud)

# nginx-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

# appliquer
kubectl apply -f nginx-nodeport.yaml
# accéder via NodeIP:30080

6) Scale & Update

Scale

kubectl scale sts nginx --replicas=5
kubectl get pods -l app=nginx

Mise à jour d’image (rolling update ordonné)

# patcher l'image (exemple)
kubectl patch sts nginx -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.26-alpine"}]}}}}'
# ou modifier YAML et appliquer : kubectl apply -f statefulset-nginx.yaml

Par défaut, StatefulSet met à jour pod par pod en respectant l’ordre (ordinal) ; pour contrôler la partition, utilisez spec.updateStrategy.rollingUpdate.partition.

7) Nettoyage

kubectl delete -f statefulset-nginx.yaml    # supprime StatefulSet et Service
kubectl delete pvc --all                    # supprime PVCs (optionnel, attention aux PV statiques)
kubectl delete pv --all                     # si PV statiques, supprimer manuellement si nécessaire
kubectl delete service nginx-nodeport       # si créé

8) Résolution des problèmes courants & debugging

PVC en Pending

kubectl get storageclass
kubectl describe pvc 
# si pas de StorageClass compatible -> créer StorageClass ou PV statiques

Pod stuck in ContainerCreating

kubectl describe pod 
kubectl get events --sort-by='.lastTimestamp'
# vérifier logs du provisioner/csi-driver sur le namespace kube-system si volume attach échoue
kubectl -n kube-system get pods | grep csi
kubectl -n kube-system logs     # si présent

Reverse Path Filter (rp_filter)

Sur l’hôte, si paquets asymétriques sont rejetés :

sysctl net.ipv4.conf.all.rp_filter
# pour tester (désactiver temporairement)
sudo sysctl -w net.ipv4.conf.all.rp_filter=0
sudo sysctl -w net.ipv4.conf.default.rp_filter=0
# persister via /etc/sysctl.d/99-rpfilter.conf si nécessaire

PV hostPath monté sur un autre nœud

Les PV de type hostPath existent physiquement sur un nœud. Si le scheduler place un pod sur un autre nœud, le volume ne pourra pas être monté. Solution : utiliser nodeAffinity sur le pod ou provisionner PV dynamiques.

9) Session exemple complète (copier-coller)

# 1. namespace (optionnel)
kubectl create ns demo-ss
kubectl config set-context --current --namespace=demo-ss

# 2. apply manifest
kubectl apply -f statefulset-nginx.yaml

# 3. watch status
kubectl get sts -w
kubectl get pods -l app=nginx -o wide

# 4. check PVCs
kubectl get pvc
kubectl describe pvc www-nginx-0

# 5. test persistence
kubectl exec -it nginx-1 -- /bin/sh -c "echo 'hello-$(date +%s)' > /usr/share/nginx/html/test.txt; cat /usr/share/nginx/html/test.txt"
kubectl delete pod nginx-1
# attendre que nginx-1 se recrée :
kubectl get pods -w
# vérifier le fichier apres recréation :
kubectl exec -it nginx-1 -- cat /usr/share/nginx/html/test.txt

# 6. port-forward for quick access
kubectl port-forward pod/nginx-0 8080:80
curl http://127.0.0.1:8080

Tu veux que je génère un script bash automatique qui applique le manifest, exécute les tests et affiche un rapport ? Ou veux-tu que j’adapte le manifest pour Minikube, EKS ou GKE (je peux ajouter StorageClass spécifique) ?