Exécuter des applications avec état et stockage persistant

KubernetesBeginner
Pratiquer maintenant

Introduction

Les Deployments considèrent leurs réplicas comme interchangeables. Les applications avec état nécessitent souvent des noms de Pods stables, un ordre de démarrage prévisible et un stockage qui survive au remplacement d’un Pod. Les StatefulSets associent ces identités stables à des claims propres à chaque Pod.

Vous allez examiner le stockage dynamique, déployer un StatefulSet à deux réplicas, écrire des données, remplacer un Pod, puis vérifier que son identité et ses données sont préservées.

Suivez séparément ces trois niveaux d’identité : le StatefulSet contrôle le nombre de réplicas souhaité, chaque Pod reçoit un ordinal stable tel que -0, et chaque ordinal reçoit son propre PersistentVolumeClaim. Le Pod peut être remplacé tandis que le claim et les données stockées sont conservés.

Examiner le stockage dynamique

Dans cette étape, examinez la StorageClass du cluster avant de demander du stockage.

Un PersistentVolumeClaim décrit les besoins d’une charge de travail ; une StorageClass décrit la manière dont le cluster peut fournir ce stockage. Vérifier d’abord les classes disponibles évite de créer un claim qui ne pourrait jamais être associé à un volume.

cd /home/labex/project/stateful-storage
kubectl get storageclasses
kubectl get storageclass -o custom-columns='NAME:.metadata.name,PROVISIONER:.provisioner,DEFAULT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class'

La StorageClass par défaut crée automatiquement un PersistentVolume lorsqu’un claim n’indique pas storageClassName. Cela évite de configurer un hostPath dépendant du nœud dans les manifestes de l’apprenant.

Recherchez (default) dans le tableau standard ou true dans la colonne personnalisée DEFAULT. Le nom du provisionneur est un détail d’implémentation propre à cet environnement ; l’essentiel est qu’une classe par défaut existe.

Dans cet environnement, le résultat est le suivant :

NAME                 PROVISIONER                ...
standard (default)   k8s.io/minikube-hostpath   ...

NAME       PROVISIONER                DEFAULT
standard   k8s.io/minikube-hostpath   true

standard est donc la classe par défaut. Comme le StatefulSet omettra storageClassName, Kubernetes sélectionnera automatiquement cette classe.

Créer un StatefulSet et un Service sans IP de cluster

Dans cette étape, créez un Service sans IP de cluster pour fournir un DNS stable, ainsi qu’un StatefulSet avec un claim par réplica.

Ce manifeste présente plusieurs champs liés entre eux. Lisez-les avant de l’appliquer :

  • clusterIP: None rend le Service sans IP de cluster, afin que le DNS puisse représenter les Pods individuellement.
  • serviceName: stateful-web associe le StatefulSet à ce Service.
  • volumeClaimTemplates crée un claim dont le nom est construit à partir du modèle et de l’ordinal du Pod.
  • volumeMounts monte le claim propre à chaque Pod dans le répertoire de documents de NGINX.

Les labels assurent toujours la sélection familière des Pods par le Service, comme dans le premier cours.

cd /home/labex/project/stateful-storage
cat <<'EOF' > stateful-web.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-stateful}
---
apiVersion: v1
kind: Service
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
  clusterIP: None
  selector: {app: stateful-web}
  ports: [{name: http, port: 80, targetPort: http}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
  serviceName: stateful-web
  replicas: 2
  selector: {matchLabels: {app: stateful-web}}
  template:
    metadata: {labels: {app: stateful-web}}
    spec:
      containers:
        - name: web
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports: [{name: http, containerPort: 80}]
          volumeMounts: [{name: web-data, mountPath: /usr/share/nginx/html}]
  volumeClaimTemplates:
    - metadata: {name: web-data}
      spec:
        accessModes: [ReadWriteOnce]
        resources: {requests: {storage: 100Mi}}
EOF

Appliquez le manifeste et attendez le déploiement dans l’ordre. Les StatefulSets créent généralement l’ordinal 0 avant l’ordinal 1 ; le second Pod peut donc apparaître un peu plus tard :

kubectl apply -f stateful-web.yaml
kubectl rollout status statefulset/stateful-web -n practice-stateful --timeout=90s
kubectl get pods,pvc -n practice-stateful

Les noms des Pods utilisent des ordinals stables : stateful-web-0 et stateful-web-1. Chaque claim contient le nom du modèle, le nom du Pod et son ordinal.

Vous devriez voir les claims web-data-stateful-web-0 et web-data-stateful-web-1, tous deux Bound. Cet état signifie que Kubernetes a trouvé ou créé dynamiquement un stockage répondant à chaque demande.

Les messages du déploiement rendent visible le fonctionnement séquentiel, puis les deux claims apparaissent :

Waiting for 2 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...

pod/stateful-web-0   1/1   Running   ...
pod/stateful-web-1   1/1   Running   ...

persistentvolumeclaim/web-data-stateful-web-0   Bound   ...   100Mi   RWO   standard
persistentvolumeclaim/web-data-stateful-web-1   Bound   ...   100Mi   RWO   standard

Les identifiants de volumes générés changent à chaque exécution. Concentrez-vous sur les noms stables des claims, leur état Bound, la capacité demandée et la classe de stockage.

Écrire des données persistantes propres à chaque Pod

Dans cette étape, écrivez un contenu différent dans le claim monté sur chaque Pod.

kubectl exec ... -- sh -c est utilisé parce que la redirection de sortie (>) doit être exécutée à l’intérieur du conteneur. Sans sh -c, votre shell local tenterait de rediriger la sortie sur la machine virtuelle plutôt qu’à l’intérieur du Pod.

cd /home/labex/project/stateful-storage
kubectl exec stateful-web-0 -n practice-stateful -- sh -c 'echo persistent-zero > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-1 -n practice-stateful -- sh -c 'echo persistent-one > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl exec stateful-web-1 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pods -n practice-stateful -o custom-columns='POD:.metadata.name,HOSTNAME:.spec.hostname,CLAIM:.spec.volumes[0].persistentVolumeClaim.claimName'

Les Pods utilisent la même image, mais montent des claims différents ; leurs données ne sont donc pas interchangeables.

Les deux commandes cat doivent renvoyer des chaînes différentes. Le tableau personnalisé associe ensuite chaque nom de Pod stable à son claim, ce qui rend concrètement visible la relation entre le Pod et son stockage.

persistent-zero
persistent-one
POD              HOSTNAME         CLAIM
stateful-web-0   stateful-web-0   web-data-stateful-web-0
stateful-web-1   stateful-web-1   web-data-stateful-web-1

L’ordinal apparaît dans les trois colonnes. La répétition de 0 ou 1 constitue l’identité qui relie un Pod à son propre stockage.

Remplacer un Pod et récupérer ses données

Dans cette étape, supprimez l’ordinal 0 et laissez le StatefulSet recréer la même identité.

Supprimer un Pod géré ne revient pas à réduire le nombre de réplicas souhaité. Le StatefulSet exige toujours deux Pods ; il crée donc un remplacement nommé stateful-web-0 et rattache le claim réservé à cet ordinal.

cd /home/labex/project/stateful-storage
kubectl delete pod stateful-web-0 -n practice-stateful --wait=true
kubectl wait --for=condition=Ready pod/stateful-web-0 -n practice-stateful --timeout=90s
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pod stateful-web-0 -n practice-stateful -o jsonpath='pod={.metadata.name} claim={.spec.volumes[0].persistentVolumeClaim.claimName}{"\n"}'

L’objet Pod est nouveau, mais son nom et son association avec le claim restent stables. La récupération du fichier prouve que les données appartenaient au PersistentVolume, et non au système de fichiers du conteneur supprimé.

pod "stateful-web-0" deleted from practice-stateful namespace
pod/stateful-web-0 condition met
persistent-zero
pod=stateful-web-0 claim=web-data-stateful-web-0

La ligne essentielle est persistent-zero : cette valeur a été écrite avant la suppression et lue depuis le Pod de remplacement.

Si le fichier avait été écrit en dehors du répertoire monté, il aurait disparu avec le système de fichiers de l’ancien conteneur. La persistance dépend du chemin de montage, et non du seul nom du StatefulSet.

Résumé

Vous avez relié la StorageClass par défaut, le provisionnement dynamique des PVC, un Service sans IP de cluster, les ordinals stables d’un StatefulSet et le stockage propre à chaque Pod. Le remplacement d’un Pod a préservé à la fois son identité et ses données.