Aller au contenu
English
Conteneurs & Orchestration medium

Stockage Kubernetes : PV, PVC, StorageClass et CSI

70 min de lecture

logo kubernetes

Votre Pod redémarre et perd toutes ses données. Votre PVC reste bloqué en Pending. Vous ne comprenez pas pourquoi votre volume n'est pas accessible depuis un autre nœud. Ces situations sont fréquentes quand on découvre le stockage Kubernetes.

Ce guide vous donne les bases essentielles pour comprendre et manipuler le stockage dans Kubernetes : la relation entre Pod, PVC, PV et StorageClass, les access modes, les reclaim policies, et comment diagnostiquer les problèmes courants.

Ce guide couvre le domaine Storage de la certification CKA, qui pèse 10 % de l'épreuve. À la fin, vous saurez :

  • Relier Pod, PVC, PV et StorageClass, et savoir lequel crée lequel
  • Choisir un access mode en sachant ce qu'il autorise vraiment
  • Décider d'une reclaim policy, et la corriger sur un volume existant
  • Distinguer provisionnement statique et dynamique
  • Diagnostiquer un PVC qui reste en Pending

Pourquoi le stockage est particulier dans Kubernetes

Section intitulée « Pourquoi le stockage est particulier dans Kubernetes »

Dans un environnement traditionnel, vos applications stockent leurs données sur des disques locaux stables. Dans Kubernetes, les Pods sont éphémères : ils peuvent être détruits, recréés et déplacés sur d'autres nœuds à tout moment.

Se posent alors trois questions :

  1. Comment préserver les données quand un Pod disparaît ?
  2. Comment partager du stockage entre plusieurs Pods ?
  3. Comment rendre le stockage portable d'un nœud à l'autre ?

Kubernetes répond à ces questions avec un modèle en couches : volumes éphémères pour les données temporaires, et stockage persistant (PV/PVC) pour les données qui doivent survivre aux Pods.

Le modèle mental : Pod → PVC → PV → StorageClass

Section intitulée « Le modèle mental : Pod → PVC → PV → StorageClass »

Avant d'entrer dans les détails, voici le schéma mental que vous devez avoir :

Modèle de stockage Kubernetes : l'utilisateur crée un PVC qui peut être lié à un PV statique ou provisionné dynamiquement via une StorageClass, le tout connecté au backend de stockage via CSI

En résumé :

  • Le Pod monte un PVC (PersistentVolumeClaim)
  • Le PVC réclame un PV (PersistentVolume)
  • Le PV peut être créé statiquement par un admin, ou dynamiquement via une StorageClass
  • CSI (Container Storage Interface) est l'interface standard entre Kubernetes et le backend de stockage

Avant le stockage persistant, voyons les volumes éphémères, ceux dont les données ne survivent pas au Pod.

Un volume emptyDir est créé quand le Pod démarre et supprimé quand le Pod est détruit. Il permet aux conteneurs d'un même Pod de partager des fichiers.

La frontière est précise, et c'est elle qu'on se représente mal : emptyDir survit au redémarrage d'un conteneur, pas à la disparition du Pod. Vérifié sur le cluster de cette formation avec un conteneur qui sort de lui-même : au second démarrage, il relit bien le fichier écrit au premier. En revanche, supprimer le Pod ou le voir replanifié ailleurs efface tout, sans avertissement et sans trace.

Cas d'usage :

  • Cache temporaire
  • Partage de fichiers entre conteneurs d'un même Pod
  • Répertoire de travail pour des calculs intermédiaires
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
containers:
- name: app
image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0
command: ["sleep", "3600"]
volumeMounts:
- mountPath: /data
name: temp-storage
volumes:
- name: temp-storage
emptyDir: {}

Le volume hostPath monte un répertoire du nœud hôte dans le Pod. Il donne accès direct au système de fichiers du nœud.

Cas d'usage limités :

  • Accéder aux logs du nœud (/var/log)
  • Monter un socket Docker ou containerd
  • Tests locaux en développement
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: app
image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0
command: ["sleep", "3600"]
volumeMounts:
- mountPath: /host-logs
name: host-storage
volumes:
- name: host-storage
hostPath:
path: /var/log
type: Directory

Le stockage persistant permet de conserver les données même après la suppression du Pod. C'est le cœur du domaine Storage de la CKA.

Un PersistentVolume représente un volume de stockage réel dans le cluster. Il est créé soit :

  • Statiquement par un administrateur
  • Dynamiquement par une StorageClass

Le PV contient les informations techniques : capacité, access modes, reclaim policy, et les détails du backend de stockage.

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-example
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/data

Un PersistentVolumeClaim est une demande de stockage faite par un utilisateur. Le PVC spécifie :

  • La capacité souhaitée
  • L'access mode requis
  • Optionnellement, une StorageClass

Kubernetes lie automatiquement le PVC à un PV compatible.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-example
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: manual

Une StorageClass définit comment provisionner dynamiquement des volumes. Elle spécifie :

  • Le provisioner (driver CSI ou in-tree)
  • Les paramètres du backend (type de disque, IOPS, etc.)
  • La reclaim policy par défaut
  • Le volumeBindingMode
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

Un cluster porte en général une StorageClass par défaut, annotée storageclass.kubernetes.io/is-default-class: "true". Un PVC qui ne déclare aucun storageClassName lui est attribué automatiquement. Prenez l'habitude de regarder laquelle, et surtout avec quels réglages, car ils gouvernent tout le reste :

Fenêtre de terminal
kubectl get storageclass
Sortie sur le cluster kind de cette formation
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false

Trois informations décisives dans cette seule ligne. La reclaim policy est Delete, donc supprimer un PVC détruit les données. Le mode de liaison est WaitForFirstConsumer, donc un PVC seul restera Pending et ce sera normal. Et allowVolumeExpansion vaut false, donc aucun volume ne pourra être agrandi sur ce cluster.

Les access modes définissent comment un volume peut être monté par les nœuds et les Pods. C'est un point central pour la CKA.

Access modeAbréviationSignificationCas d'usage typique
ReadWriteOnceRWOLecture/écriture depuis un seul nœudDB mono-instance, app stateful
ReadOnlyManyROXLecture seule depuis plusieurs nœudsAssets statiques partagés
ReadWriteManyRWXLecture/écriture depuis plusieurs nœudsStockage partagé (NFS, CephFS)
ReadWriteOncePodRWOPLecture/écriture depuis un seul PodApps nécessitant un writer unique strict

ReadWriteOnce veut dire un seul nœud, pas un seul Pod

Section intitulée « ReadWriteOnce veut dire un seul nœud, pas un seul Pod »

C'est la confusion la plus coûteuse du domaine, et elle se démontre en deux manipulations. Sur le cluster de cette formation, un PVC en ReadWriteOnce a été monté par un premier Pod qui y a écrit un fichier, puis par un second Pod placé sur le même nœud :

Fenêtre de terminal
kubectl exec p2 -- cat /data/temoin.txt
Sortie
bonjour-du-lab

Le second Pod lit ce que le premier a écrit. ReadWriteOnce ne l'a pas empêché, parce qu'il ne parle pas des Pods mais des nœuds.

Un troisième Pod contraint sur l'autre nœud échoue, lui, et son message mérite d'être connu car il ne ressemble pas à une erreur de stockage :

Warning FailedScheduling default-scheduler
0/2 nodes are available: 1 node(s) didn't match PersistentVolume's node affinity

Le Pod ne reste pas bloqué au montage, il n'est jamais planifié : le volume porte une affinité de nœud, et le scheduler refuse de placer le Pod ailleurs. Un Pending dont la cause est un volume ressemble donc à un problème de scheduling, et c'est bien dans les événements du scheduler qu'il faut le chercher.

Pour garantir un accès depuis un seul Pod, il faut ReadWriteOncePod, stable depuis Kubernetes 1.29 et réservé aux volumes CSI.

Déclarer un access mode dans un PVC ne le rend pas disponible : c'est le backend de stockage qui décide de ce qu'il sait faire. Le tableau ci-dessous donne l'ordre de grandeur à retenir. La ligne à observer est celle du RWX, réservé aux systèmes de fichiers réseau comme NFS ou CephFS : un disque bloc attaché à une machine virtuelle ne peut par construction pas être monté en écriture par plusieurs nœuds à la fois. Vérifiez toujours la documentation de votre driver CSI, les capacités évoluent d'une version à l'autre.

BackendRWOROXRWXRWOP
AWS EBS
GCE PD
Azure Disk
NFS
CephFS

Reclaim policies : que faire quand le PVC est supprimé ?

Section intitulée « Reclaim policies : que faire quand le PVC est supprimé ? »

La reclaim policy définit ce qui arrive au PV (et au stockage sous-jacent) quand le PVC qui l'utilise est supprimé.

Reclaim PolicyEffetUsage
DeleteLe PV et le stockage backend sont supprimésEnvironnements cloud, données non critiques
RetainLe PV est conservé (statut Released), données préservéesDonnées critiques, reprise manuelle
RecycleDéprécié, Effacement basique (rm -rf /volume/*)Ne plus utiliser

La reclaim policy d'un PV est modifiable à chaud, sans interrompre le Pod qui l'utilise. C'est le geste de sauvetage à connaître : quand vous découvrez qu'un volume de production a été provisionné en Delete, passez-le en Retain avant toute suppression de PVC. La modification est prise en compte immédiatement, kubectl get pv affiche la nouvelle valeur dans la colonne RECLAIM POLICY.

Fenêtre de terminal
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

Le volumeBindingMode d'une StorageClass contrôle quand le binding PVC → PV se produit.

ModeComportementQuand l'utiliser
ImmediateLe PV est créé/lié dès la création du PVCStockage sans contrainte de topologie
WaitForFirstConsumerLe binding attend qu'un Pod consomme le PVCStockage zonal (EBS, GCE PD), scheduling-aware

L'intérêt de WaitForFirstConsumer tient en une phrase : Kubernetes connaît le nœud cible avant de provisionner le volume, et ne risque donc pas de créer un disque dans une zone où aucun Pod ne pourra tourner. La contrepartie est visible immédiatement, un PVC seul reste Pending avec cet événement :

Normal WaitForFirstConsumer persistentvolume-controller
waiting for first consumer to be created before binding

Ce n'est pas une panne, c'est le mode qui fonctionne comme prévu. Dès qu'un Pod monte le PVC, la liaison se fait en quelques secondes.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: zonal-storage
provisioner: pd.csi.storage.gke.io
volumeBindingMode: WaitForFirstConsumer

Les deux modes répondent à la même question, qui crée le PV, mais pas au même contexte. En statique, un administrateur prépare les volumes à l'avance et les utilisateurs se servent dans ce stock. En dynamique, personne ne prépare rien : le PV naît au moment où le PVC le réclame. Le second est devenu la norme, mais le premier reste indispensable dès qu'un volume existe déjà hors de Kubernetes, un partage NFS d'entreprise par exemple, et qu'il s'agit seulement de le déclarer.

L'administrateur crée manuellement les PV avant que les utilisateurs créent leurs PVC.

  1. Admin crée le PV

    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: static-pv
    spec:
    capacity:
    storage: 50Gi
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain
    storageClassName: manual
    nfs:
    server: nfs.example.com
    path: /exports/data
  2. Utilisateur crée le PVC

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: my-pvc
    spec:
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 50Gi
    storageClassName: manual
  3. Kubernetes lie PVC → PV

    Le PVC passe en status Bound.

Avantages : Contrôle total, pas de dépendance au provisioner.

Inconvénients : Manuel, ne scale pas, risque de sur/sous-provisionnement.

Kubernetes crée automatiquement les PV à la demande, via une StorageClass et un driver CSI.

  1. StorageClass existe

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
    name: fast
    provisioner: csi.example.com
    parameters:
    type: ssd
  2. Utilisateur crée le PVC

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: dynamic-pvc
    spec:
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 20Gi
    storageClassName: fast
  3. CSI provisionne automatiquement le PV

    Le driver CSI crée le volume sur le backend et le PV dans Kubernetes.

Avantages : Automatique, scalable, moins d'erreurs humaines.

Inconvénients : Dépendance au driver CSI, debugging plus complexe.

CSI (Container Storage Interface) est l'interface standard pour intégrer des solutions de stockage à Kubernetes. C'est le mécanisme derrière les StorageClasses modernes.

Un driver CSI se scinde en deux moitiés, et cette séparation explique la plupart des pannes de stockage. Les opérations qui concernent le cluster entier, créer ou détruire un volume, l'attacher à un nœud, sont regroupées d'un côté ; celles qui se passent sur une machine précise, monter le volume dans le Pod, de l'autre. Quand un volume est créé mais jamais monté, c'est donc la seconde moitié qu'il faut aller regarder, sur le nœud concerné.

Un driver CSI se compose de :

  • Controller Plugin : Gère les opérations au niveau du cluster (création/suppression de volumes, attachement aux nœuds)
  • Node Plugin : Gère les opérations sur chaque nœud (montage/démontage du volume dans le Pod)

Ces plugins communiquent avec Kubernetes via gRPC.

La spécification CSI décrit un catalogue de capacités, mais chaque driver choisit celles qu'il implémente. Un cluster qui refuse un snapshot ou une expansion de volume n'a donc pas forcément un problème de configuration : le driver ne sait peut-être simplement pas le faire. Deux vérifications utiles avant de conclure : allowVolumeExpansion: true doit être présent dans la StorageClass pour agrandir un volume, et un VolumeSnapshotClass doit exister pour les snapshots.

Selon le driver, CSI permet :

  • Provisionnement dynamique
  • Snapshots de volumes
  • Clonage de volumes
  • Expansion de volumes (allowVolumeExpansion: true)
  • Topologie (placement zone-aware)

La liste officielle des drivers, avec les capacités de chacun, est publiée sur kubernetes-csi.github.io. C'est la page à consulter avant de promettre un snapshot ou une expansion à une équipe applicative.

Un PVC en Pending est un problème classique. Voici les causes les plus fréquentes :

Parcourez ce tableau de haut en bas, les causes sont classées de la plus fréquente à la plus rare. La dernière ligne mérite une mention particulière : avec volumeBindingMode: WaitForFirstConsumer, un PVC en Pending est le comportement normal tant qu'aucun Pod ne le monte. Avant de chercher une panne, vérifiez donc le mode de la StorageClass, sinon vous passerez du temps à diagnostiquer un fonctionnement attendu.

CauseVérificationSolution
Pas de StorageClass par défautkubectl get scCréer une SC par défaut ou spécifier storageClassName
StorageClass inexistantekubectl get sc <nom>Créer la StorageClass ou corriger le nom
Aucun PV statique compatiblekubectl get pvCréer un PV avec capacité/access mode compatibles
Capacité demandée trop grandekubectl describe pvcRéduire la demande ou créer un PV plus grand
Access mode non supportékubectl describe pvcVérifier le support du backend
Driver CSI non fonctionnelkubectl get pods -n kube-systemVérifier les pods du driver CSI
WaitForFirstConsumer actifkubectl describe pvcCréer un Pod qui consomme le PVC

L'information utile ne se trouve pas dans kubectl get, qui affiche seulement le statut, mais dans la section Events de kubectl describe pvc. C'est là que le contrôleur explique pourquoi il n'a pas pu lier le volume, avec un message souvent explicite du type storageclass.storage.k8s.io "..." not found. Si describe ne montre rien, remontez d'un cran vers les événements du namespace, puis vers les pods du driver CSI.

Fenêtre de terminal
# Lister les ressources de stockage
kubectl get pv
kubectl get pvc -A
kubectl get storageclass
# Inspecter un PVC
kubectl describe pvc <nom>
# Inspecter un PV
kubectl describe pv <nom>
# Voir les événements récents
kubectl get events -A --sort-by=.lastTimestamp | grep -i pvc
# Vérifier les pods CSI
kubectl get pods -n kube-system | grep csi
Fenêtre de terminal
$ kubectl describe pvc my-pvc
Name: my-pvc
Namespace: default
Status: Pending
...
Events:
Type Reason Message
---- ------ -------
Warning ProvisioningFailed storageclass.storage.k8s.io "nonexistent" not found

Diagnostic : La StorageClass nonexistent n'existe pas.

Solution : Corriger le storageClassName dans le PVC ou créer la StorageClass.

Ce manifeste réunit les trois objets dans l'ordre où Kubernetes les traite, et il se lit de bas en haut : le Pod réclame un PVC, le PVC déclenche la StorageClass, la StorageClass fait provisionner le volume. Le provisioner rancher.io/local-path est celui qu'installent kind et k3s, ce qui rend cet exemple applicable tel quel sur un cluster de test. Sur un cluster cloud, remplacez-le par le driver CSI de votre fournisseur et adaptez parameters.

Voici un exemple fonctionnel avec provisionnement dynamique :

# 1. StorageClass (à adapter selon votre environnement)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: rancher.io/local-path # Exemple avec local-path-provisioner
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
# 2. PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
---
# 3. Pod qui utilise le PVC
apiVersion: v1
kind: Pod
metadata:
name: app-with-storage
spec:
containers:
- name: app
image: nginx:1.29-alpine@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
volumeMounts:
- mountPath: /usr/share/nginx/html
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data

Appliquez et vérifiez :

Fenêtre de terminal
kubectl apply -f storage-example.yaml
kubectl get pvc app-data
kubectl get pv
kubectl describe pod app-with-storage

Les 5 points essentiels de ce guide :

  • Pod → PVC → PV → StorageClass : c'est le modèle mental à avoir pour comprendre le stockage Kubernetes
  • Access modes : RWO = un seul nœud, RWX = plusieurs nœuds, RWOP = un seul Pod
  • Reclaim policies : Delete supprime les données, Retain les conserve
  • Provisionnement dynamique via StorageClass + CSI est le mode moderne
  • Un PVC en Pending = vérifier StorageClass, capacité, access mode, et driver CSI

Les questions ci-dessous portent uniquement sur ce guide et reprennent les confusions les plus coûteuses en examen CKA : la portée réelle de ReadWriteOnce, l'effet d'une reclaim policy au moment de la suppression du PVC, et les causes d'un PVC bloqué en Pending.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% 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

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