Aller au contenu
English
Cloud medium

Volumes persistants sur Kapsule : ce qui suit le pod, et ce qui ne le suit pas

Mesuré live le ·fr-par·scw 2.62.0

70 min de lecture

logo Scaleway

Un volume Block suit votre pod d'un nœud à l'autre, mais jamais d'une zone à l'autre. Cette leçon crée un volume persistant sur Kapsule, prouve que les données survivent au pod qui les a écrites, puis provoque le blocage qui compte : forcer le pod dans une autre zone. Vous y verrez un message d'erreur qui accuse la mauvaise cause, et vous saurez pourquoi un PersistentVolumeClaim peut rester Pending pendant des heures sans qu'aucune panne ne soit en cours.

  • Choisir une classe de stockage en sachant si elle laisse survivre le volume.
  • Comprendre pourquoi un PVC reste Pending sans être en panne.
  • Prouver que les données survivent au pod, et mesurer la réattache.
  • Reconnaître la contrainte zonale derrière un message trompeur.
  • Un cluster Kapsule : voir créer un cluster Kapsule.
  • kubectl configuré, la CLI scw 2.62.0 et jq.
  • Pour la dernière partie, un second pool dans une autre zone, que la leçon vous fera créer puis supprimer.

Huit classes sont disponibles, et elles vont par paires : chacune a une jumelle qui laisse survivre le volume. C'est la distinction la plus coûteuse à ignorer.

Fenêtre de terminal
kubectl get storageclass -o 'custom-columns=NOM:.metadata.name,PROVISIONNEUR:.provisioner,DEFAUT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class,LIAISON:.volumeBindingMode'

La sortie doit afficher huit lignes, toutes servies par csi.scaleway.com. Relevé le 2026-09-12 :

ClassePar défautCe qu'elle change
sbs-defaultouile choix si vous ne précisez rien
sbs-5k, sbs-15knonle débit en entrées-sorties par seconde
scw-bssdnonl'ancienne gamme Block
toutes en -retainnonle volume survit à la suppression du PVC

La jumelle -retain est un piège budgétaire, pas une option de confort. Avec elle, supprimer le PersistentVolumeClaim ne supprime pas le volume : celui-ci passe en Released, reste provisionné chez Scaleway, et continue d'être facturé indéfiniment. C'est exactement le genre de reste discret que personne ne retrouve six mois plus tard, puisqu'il n'apparaît plus dans aucun objet Kubernetes. Employez -retain quand la perte de données serait pire que la facture, typiquement pour une base, et sachez alors que vous devrez supprimer le volume à la main. Partout ailleurs, la classe par défaut et sa politique Delete font le travail.

Parce qu'aucun pod ne le consomme, et c'est le comportement normal. Les huit classes sont en WaitForFirstConsumer : rien n'est provisionné tant que le planificateur n'a pas choisi un nœud.

Fenêtre de terminal
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: donnees
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5G
YAML

Regardez maintenant ce qui existe, et surtout ce qui n'existe pas :

Fenêtre de terminal
kubectl get pvc donnees -o 'custom-columns=ETAT:.status.phase,VOLUME:.spec.volumeName'
kubectl get events --field-selector involvedObject.name=donnees \
-o 'custom-columns=RAISON:.reason,MESSAGE:.message' --no-headers | tail -1
scw block volume list zone=fr-par-1 -o json \
| jq -r 'if length == 0 then "aucun volume provisionné" else .[].name end'

Les trois sorties doivent afficher, dans l'ordre : Pending sans volume, le message du provisionneur, et aucun volume provisionné.

donnees Pending <none>
WaitForFirstConsumer waiting for first consumer to be created before binding
aucun volume provisionné

Le pod déclenche la liaison, et le volume apparaît

Section intitulée « Le pod déclenche la liaison, et le volume apparaît »

Créer le pod suffit : la liaison et le provisionnement se font dans la foulée.

Fenêtre de terminal
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: ecrivain
spec:
containers:
- name: c
image: alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
command: ["sh","-c","echo \"écrit le $(date -Iseconds)\" >> /data/journal.txt && sleep 3600"]
volumeMounts:
- { name: v, mountPath: /data }
volumes:
- name: v
persistentVolumeClaim: { claimName: donnees }
YAML
kubectl wait --for=condition=ready pod/ecrivain --timeout=300s

La sortie doit finir par pod/ecrivain condition met. Vérifiez alors les deux côtés :

Fenêtre de terminal
kubectl get pvc donnees -o 'custom-columns=ETAT:.status.phase,VOLUME:.spec.volumeName'
scw block volume list zone=fr-par-1 -o json \
| jq -r '.[] | "\(.size / 1000000000) Go zone \(.zone) \(.status)"'

La première sortie doit afficher Bound, la seconde un volume in_use dans la zone de votre nœud.

Oui, et la réattache prend quatre secondes. C'est la raison d'être du volume persistant, et cela se prouve plutôt que se suppose.

Fenêtre de terminal
kubectl delete pod ecrivain --wait=true
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: lecteur
spec:
containers:
- name: c
image: alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
command: ["sh","-c","cat /data/journal.txt && sleep 3600"]
volumeMounts:
- { name: v, mountPath: /data }
volumes:
- name: v
persistentVolumeClaim: { claimName: donnees }
YAML
kubectl wait --for=condition=ready pod/lecteur --timeout=300s
kubectl exec lecteur -- cat /data/journal.txt

La dernière sortie doit afficher la ligne écrite par le pod précédent. Mesuré le 2026-09-12 : 4 secondes entre la création du nouveau pod et sa disponibilité.

Le planificateur replace spontanément le pod sur le nœud où le volume est attaché. Ce n'est pas un hasard : le volume est une contrainte de placement au même titre que le processeur ou la mémoire. Forcer le pod sur un autre nœud de la même zone fonctionne aussi, le volume se détachant puis se rattachant.

Il ne démarre jamais. C'est la contrainte la plus structurante du stockage Block, et celle qui décide de votre architecture bien avant le premier incident.

Créez un pool dans une seconde zone, puis forcez le pod à y aller :

Fenêtre de terminal
NOEUD_AUTRE_ZONE=$(kubectl get nodes -l topology.kubernetes.io/zone=fr-par-2 \
--no-headers | awk '$2=="Ready"{print $1; exit}')
kubectl delete pod lecteur --wait=true
kubectl apply -f - <<YAML
apiVersion: v1
kind: Pod
metadata:
name: hors-zone
spec:
nodeName: ${NOEUD_AUTRE_ZONE}
containers:
- name: c
image: alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
command: ["sh","-c","cat /data/journal.txt && sleep 3600"]
volumeMounts:
- { name: v, mountPath: /data }
volumes:
- name: v
persistentVolumeClaim: { claimName: donnees }
YAML
sleep 60
kubectl get pod hors-zone -o 'custom-columns=ETAT:.status.phase,NOEUD:.spec.nodeName'
kubectl get events --field-selector involvedObject.name=hors-zone \
-o 'custom-columns=RAISON:.reason,MESSAGE:.message' --no-headers | tail -1

La sortie doit afficher Pending et le message d'échec d'attachement. Mesuré le 2026-09-12 :

FailedAttachVolume AttachVolume.Attach failed for volume "pvc-..." :
rpc error: code = Internal desc = failed to attach volume to instance:
failed to attach volume: scaleway-sdk-go: resource instance_server
with ID 89e4504f-... is not found

Un échec seul ne prouve rien : il pourrait venir du nœud, du pilote ou du volume. Refaites donc le même pod, avec le même volume, sur un nœud de la bonne zone :

Fenêtre de terminal
NOEUD_BONNE_ZONE=$(kubectl get nodes -l topology.kubernetes.io/zone=fr-par-1 \
--no-headers | awk '$2=="Ready"{print $1; exit}')
kubectl delete pod hors-zone --wait=true

Recréez le pod en remplaçant nodeName par ${NOEUD_BONNE_ZONE}. Il doit démarrer et relire la même donnée. Mesuré : Running, avec la ligne écrite au tout début du lab.

Seule la zone a changé entre les deux essais. C'est ce qui autorise à conclure.

BesoinCe que le volume Block permetCe qu'il faut faire sinon
Un pod avec état, une zoneoui, c'est le cas nominalrien de plus
Bascule automatique vers une autre zonenon, le volume ne suit pasréplication applicative, ou base managée
Plusieurs pods sur le même volumenon, ReadWriteOnceFile Storage, qui accepte ReadWriteMany
Survie à la perte d'une zonenonsauvegardes vers Object Storage, ou réplication

Le raccourci à éviter est de croire qu'un cluster multi-zone rend une application avec état multi-zone. Les nœuds se répartissent, le volume ne bouge pas, et une base de données sur volume Block reste attachée à sa zone quoi que fasse le planificateur. La conséquence pratique se mesure au moment de l'incident, pas avant : si la zone qui héberge le volume devient indisponible, votre pod ne redémarrera nulle part. Il restera Pending jusqu'au retour de la zone, et aucune répartition de nœuds n'y changera rien, puisque le problème n'est pas de trouver une machine mais de rattacher un disque qui n'existe que là-bas. Les trois réponses possibles sont toutes applicatives : répliquer les données entre plusieurs volumes zonaux depuis l'application elle-même, confier la persistance à un service managé qui gère sa propre redondance, ou accepter l'indisponibilité en s'appuyant sur des sauvegardes vers Object Storage. Aucune ne consiste à mieux configurer Kubernetes.

La politique de récupération décide si le volume survit : lisez-la avant de supprimer quoi que ce soit.

Fenêtre de terminal
kubectl get pv -o 'custom-columns=NOM:.metadata.name,POLITIQUE:.spec.persistentVolumeReclaimPolicy,ETAT:.status.phase'

La sortie doit afficher Delete si vous avez employé la classe par défaut. Avec Retain, le volume vous survivra et il faudra le supprimer à la main.

Fenêtre de terminal
kubectl delete pod --all --ignore-not-found=true
kubectl delete pvc donnees --ignore-not-found=true --wait=true
scw block volume list zone=fr-par-1 -o json \
| jq -r 'if length == 0 then "aucun volume restant" else .[] | "RESTE \(.id)" end'

La dernière sortie doit afficher aucun volume restant. Ne vous arrêtez pas au kubectl delete : c'est la liste côté Scaleway qui fait foi, puisque c'est elle qui est facturée.

N'oubliez pas de supprimer le pool de la seconde zone, créé pour la démonstration :

Fenêtre de terminal
CLUSTER_ID=$(scw k8s cluster list region=fr-par -o json | jq -r '.[0].id')
scw k8s pool list cluster-id="${CLUSTER_ID}" region=fr-par -o json \
| jq -r '.[] | select(.zone == "fr-par-2") | .id' \
| xargs -r -I{} scw k8s pool delete {} region=fr-par -o json
PlafondValeurSource
Mode d'accès du Block StorageReadWriteOnce uniquementpilote scaleway-csi
Portée d'un volumezonale, jamais régionalemesuré le 2026-09-12
Taille minimale d'un volume5 GoBlock Storage
Écart entre 5Gi demandé et facturé5,37 Go décimauxmesuré le 2026-09-12
Extension de volumeautorisée sur les 8 classesallowVolumeExpansion: true
Réattache sur un nouveau pod4 smesuré le 2026-09-12

Les trois premiers symptômes portent le message exact, tel que Kubernetes le renvoie.

SymptômeCauseSolution
waiting for first consumer to be created before bindingla classe est en WaitForFirstConsumer, aucun pod ne consommecréer le pod ; ce n'est pas une panne
resource instance_server with ID ... is not foundle nœud et le volume sont dans des zones différentesramener le pod dans la zone du volume
Multi-Attach error for volumeun autre pod tient déjà le volume en ReadWriteOnceattendre la fin du détachement, ou passer à File Storage
Un volume reste facturé après suppression du PVCla classe employée était en -retainsupprimer le volume avec scw block volume delete

Ces quatre erreurs supposent que le stockage suit le calcul. Il ne le suit pas : il est ancré à une zone.

AntipatternConséquenceDiscipline
Répartir une application avec état sur plusieurs zonesle pod ne redémarre jamais hors de sa zoneréplication applicative, ou base managée
Employer une classe -retain par prudencedes volumes facturés que plus aucun objet ne référence-retain seulement là où la perte serait pire, et suppression manuelle assumée
Écrire les tailles en Gi7 % de stockage facturé en plus, sur chaque volumeécrire en G, comme la facture
Conclure d'un kubectl delete pvc que le volume est partiil peut survivre selon la politiquevérifier avec scw block volume list

Que devient votre application si la zone du volume tombe ? Elle ne redémarre pas ailleurs. Le volume est zonal, et aucun réglage Kubernetes ne le déplace.

Discipline : décider explicitement, pour chaque charge avec état, si elle tolère d'être liée à une zone. Si non, la réplication se fait au niveau applicatif ou par un service managé, jamais par le volume.

Combien de volumes payez-vous qu'aucun objet Kubernetes ne référence ? La question ne se répond pas dans le cluster, puisque ces volumes n'y apparaissent plus.

Discipline : relister régulièrement scw block volume list et confronter à kubectl get pv, réserver -retain aux cas décidés, et écrire les tailles en gigaoctets décimaux.

Qui peut lire ce volume s'il est détaché ? Le pilote scaleway-csi sait chiffrer un volume au repos, par LUKS, en posant encrypted: true sur la classe de stockage et une phrase secrète dans un Secret. Ce guide ne l'a pas mesuré ; la capacité est documentée dans le dépôt du pilote.

Discipline : chiffrer les volumes portant des données sensibles plutôt que de compter sur l'isolation du cluster, et garder la phrase secrète hors du dépôt.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Huit classes de stockage, toutes en csi.scaleway.com, sbs-default étant celle par défaut.
  • Chaque classe a une jumelle -retain dont le volume survit au PVC et continue d'être facturé.
  • Un PVC sans consommateur reste Pending indéfiniment : c'est WaitForFirstConsumer, pas une panne.
  • Rien n'est facturé avant qu'un pod ne consomme le PVC.
  • 5Gi fait provisionner 5,37 Go décimaux : écrivez 5G pour payer ce que vous demandez.
  • Les données survivent au pod, avec une réattache mesurée à 4 secondes.
  • Le volume suit le pod d'un nœud à l'autre, dans la même zone.
  • Il ne change jamais de zone : le pod reste Pending pour toujours.
  • resource instance_server ... is not found signifie « mauvaise zone », pas « instance supprimée ».
  • Vérifiez la suppression avec scw block volume list, jamais avec le seul kubectl delete.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn