Vous ne découvrez pas si votre conception de stockage est bonne lors d’un déploiement calme un mardi. Vous le découvrez quand un nœud disparaît en plein écriture,
un StatefulSet se bloque, et votre canal d’incident se transforme en reconstitution en direct de « À qui appartiennent exactement les données ? ».
ZFS et Kubernetes peuvent très bien fonctionner ensemble, mais seulement si vous acceptez une vérité brutale : le domaine de panne par défaut pour ZFS local est le nœud,
et Kubernetes n’est pas sentimental vis-à-vis de votre nœud. Si vous voulez que vos PV survivent aux pannes de nœud, vous devez le concevoir dès le départ — topologie,
réplication, fencing, et contrôles opérationnels qui ne reposent pas sur l’espoir.
Ce qui échoue réellement lors d’une « panne de nœud »
« Panne de nœud » est une expression vague qui masque des résultats très différents. Votre conception de PV doit gérer les modes de panne spécifiques que vous verrez
réellement en production, pas ceux des beaux diagrammes.
Mode de panne A : le nœud est mort, le stockage est intact
Pensez : kernel panic, carte réseau morte, alimentation qui lâche, ou hyperviseur qui l’a redémarré. Les disques vont bien, le pool aussi, mais Kubernetes
ne peut pas planifier sur ce nœud — du moins pas immédiatement. Si votre PV est strictement local, votre pod est bloqué jusqu’au retour du nœud. Cela peut
être acceptable pour un cache. Ce n’est pas acceptable pour un registre de paiements.
Mode de panne B : le nœud répond, le stockage est malade
Le nœud répond, mais le pool est dégradé, un vdev manque, les erreurs NVMe augmentent, ou la latence est insupportable. Kubernetes continuera
à y placer des pods si vos règles de topologie le permettent. ZFS continuera d’essayer de livrer les données, parfois héroïquement, parfois lentement.
Vous avez besoin de signaux et d’automatisations qui traitent la « maladie » du stockage comme un problème d’ordonnancement.
Mode de panne C : le nœud est à moitié mort (le plus coûteux)
Le nœud est « Ready » juste assez pour induire en erreur la logique du control-plane mais ne peut pas effectuer d’IO fiables. C’est là que vous obtenez
des timeouts, des montages bloqués, des pods en Terminating et des tentatives en cascade.
Kubernetes est excellent pour remplacer les bêtes. Votre PV n’est pas une bête. Si vous stockez de l’état réel, vous avez besoin d’un plan pour l’endroit
où cet état vit et comment il se déplace quand l’hôte disparaît.
Faits et contexte à connaître
Ce ne sont pas des trivia. Ce sont les petites réalités qui expliquent pourquoi certaines conceptions ZFS+Kubernetes fonctionnent et d’autres finiront par vous voler le week-end.
- ZFS est né dans un monde qui détestait la corruption silencieuse. Ses sommes de contrôle de bout en bout et son auto-réparation ont été conçues pour attraper la bit rot que le RAID seul ne peut pas.
- Le copy-on-write est la raison principale pour laquelle les snapshots sont peu coûteux. ZFS ne « gèle » pas un volume ; il préserve des pointeurs de blocs pendant que les nouvelles écritures vont ailleurs.
- Les premières utilisations de ZFS étaient liées à Solaris et à une intégration étroite. Cet héritage influence encore les hypothèses sur le nommage des périphériques et les stacks de stockage stables.
- ZVOLs et datasets sont des bêtes différentes. Les ZVOLs se comportent comme des périphériques blocs ; les datasets sont des systèmes de fichiers. Ils ont des réglages et des comportements de panne différents.
- ZFS ment toujours un peu sur « l’espace libre ». Fragmentation, metaslabs et comportement des réservations signifient que 80% d’utilisation peut se ressentir comme 95% pour la latence.
- Ashifts comptent plus qu’on ne le pense. Un mauvais alignement de secteur peut pénaliser définitivement les performances ; vous ne le « réparerez » pas plus tard sans reconstruire.
- Les scrubs ne sont pas optionnels dans des flottes de longue durée. Ce sont eux qui trouvent les erreurs latentes avant que vous ayez besoin du bloc pendant une restauration.
- Les abstractions de stockage Kubernetes ont été conçues d’abord pour le réseau. Les PV locaux existent, mais l’histoire d’orchestration est délibérément limitée : elles ne téléporteront pas vos octets magiquement.
- La sémantique « single writer » est une contrainte de sécurité fondamentale. Si deux nœuds peuvent monter et écrire le même système de fichiers sans fencing, vous concevez une machine à corruption.
Trois archétypes de PV avec ZFS (et quand les utiliser)
1) PV ZFS local (lié au nœud) : rapide, simple, impitoyable
C’est le modèle « ZFS sur chaque nœud, PV se lie au nœud ». Vous créez un dataset ou un zvol sur le nœud, l’exposez via CSI ou LocalPV,
et vous planifiez le pod consommateur sur ce nœud.
À utiliser quand : la charge tolère une indisponibilité du nœud, vous pouvez reconstruire ailleurs, ou c’est un cache majoritairement en lecture avec une source de vérité en amont.
Évitez quand : vous avez besoin d’un basculement automatique sans temps de déplacement de données, ou vous ne pouvez pas tolérer « pod bloqué jusqu’au retour du nœud ».
2) Stockage réseau basé sur ZFS (iSCSI/NFS sur ZFS) : portable, centralisé, déplacement du domaine de panne
Placez ZFS sur des nœuds de stockage dédiés et exportez des volumes sur le réseau. Kubernetes le voit comme du stockage en réseau, ce qui s’aligne avec
son modèle d’ordonnancement. Votre domaine de panne devient le service de stockage, pas le nœud de calcul.
À utiliser quand : vous avez besoin que les pods se déplacent librement et êtes prêt à concevoir la HA stockage comme des adultes.
Évitez quand : votre réseau est fragile ou surchargé, ou vous n’avez pas les moyens opérationnels pour la HA du stockage.
3) Stockage local ZFS répliqué avec promotion (ZFS send/receive, style DRBD, ou un orchestrateur) : résilient, complexe
C’est le modèle « gardez la performance locale et payez pour elle » : conservez les données localement pour la performance, mais répliquez vers des pairs pour pouvoir promouvoir
une réplique quand un nœud échoue. Cela peut se faire avec les mécanismes de réplication ZFS (snapshots + send/receive) coordonnés par un contrôleur,
ou par un système de stockage superposé à ZFS.
À utiliser quand : vous voulez la performance locale avec survie aux pannes de nœud, et vous pouvez appliquer la sémantique single-writer via fencing.
Évitez quand : vous ne pouvez pas garantir le fencing ou vous avez besoin d’une réplication synchrone mais n’acceptez pas le coût en latence.
Voici l’idée tranchée : si vous exploitez des états sérieux (bases de données, files d’attente, métadonnées d’objets) et que les pannes de nœud ne doivent pas provoquer
des heures de récupération, ne prétendez pas que les PV locaux sont « hautement disponibles ». Ils sont « hautement disponibles pour ce nœud ».
Principes de conception qui évitent les mauvaises surprises
Principe 1 : Décidez explicitement de votre domaine de panne
Pour chaque StatefulSet, notez : « Si le nœud X disparaît, acceptons-nous une indisponibilité ? Pendant combien de temps ? Peut-on reconstruire ? Avons-nous besoin d’un basculement automatisé ? »
Si vous ne pouvez pas répondre à ces questions, votre StorageClass n’est qu’une prière en YAML.
Principe 2 : Faites respecter le single-writer avec du fencing, pas de l’intuition
Si vous répliquez et promouvez, vous devez empêcher deux nœuds d’écrire le même volume logique. Kubernetes ne le fera pas pour vous.
Votre conception nécessite soit :
- un fencing dur (STONITH, coupure d’alimentation, fence hyperviseur), ou
- un système de stockage garantissant une attache exclusive, ou
- une charge de travail elle-même multi-writer sûre (rare ; nécessite généralement un système de fichiers clusterisé ou une réplication applicative).
Blague #1 : Le split-brain, c’est comme donner les droits root à deux stagiaires en production — tout le monde apprend beaucoup, et l’entreprise apprend à le regretter.
Principe 3 : Choisissez dataset vs zvol selon la façon dont votre appli écrit
Beaucoup d’installations CSI ZFS vous donnent le choix : dataset (système de fichiers) ou zvol (bloc). Ne choisissez pas selon l’esthétique.
- Datasets s’intègrent bien avec POSIX, les quotas, et sont faciles à inspecter. Parfaits pour les charges fichier générales.
- ZVOLs se comportent comme des volumes bloc et sont courants pour les bases via ext4/xfs, ou bloc brut. Ils nécessitent un choix attentif de volblocksize.
Principe 4 : Ajustez les propriétés ZFS par charge, pas par cluster
« Une configuration ZFS pour tous » est la recette pour avoir soit des bases de données tristes, soit des pipelines de logs tristes. Utilisez des propriétés par dataset :
recordsize, compression, atime, xattr, logbias, primarycache/secondarycache, et des réservations là où c’est approprié.
Principe 5 : La capacité est un réglage de performance
Avec ZFS, « presque plein » n’est pas seulement un risque de capacité ; c’est un risque de latence. Planifiez des alertes autour de la fragmentation du pool et de l’allocation, pas seulement « df indique 10% de libre ».
Principe 6 : L’observabilité doit inclure ZFS, pas seulement Kubernetes
Kubernetes vous dira que le pod est Pending. Il ne vous dira pas qu’un NVMe unique réessaie des commandes et que votre pool s’étrangle.
Construisez des tableaux de bord et des alertes sur zpool status, les compteurs d’erreurs, les résultats de scrub et les métriques de latence.
Réalités CSI : ce que Kubernetes fera et ne fera pas pour vous
CSI est une interface, pas une garantie de bon comportement. Le driver que vous choisissez (ou développez) détermine si vos volumes se comportent comme
un stockage mature ou comme un projet de foire scientifique en YAML.
Kubernetes fera :
- attacher/monter les volumes selon le driver,
- respecter l’affinité de nœud pour les PV locaux,
- redémarrer les pods ailleurs si un PV est portable et que le scheduler peut le placer.
Kubernetes ne fera pas :
- répliquer vos datasets ZFS locaux,
- fencer un nœud pour empêcher les écritures doubles,
- réparer magiquement un pool dégradé,
- comprendre la notion de « santé de pool » de ZFS à moins que vous ne lui appreniez (taints, conditions de nœud, ou contrôleurs externes).
Une conception fiable accepte ces limites et construit explicitement les pièces manquantes : orchestration de réplication, règles de promotion,
et ordonnancement basé sur la santé.
Une citation opérationnelle qui a bien vieilli : « L’espoir n’est pas une stratégie. » — Gene Kranz
Tâches pratiques : commandes, sorties et décisions
Ce sont les vérifications que j’exécute réellement quand ça sent le roussi. Chaque tâche inclut : la commande, ce que signifie la sortie, et la décision
que vous prenez. Utilisez-les pendant les revues de conception et les incidents.
Tâche 1 : Confirmer sur quel nœud un PVC est réellement lié (vérification PV local)
cr0x@server:~$ kubectl get pvc -n prod app-db -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
app-db Bound pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a 200Gi RWO zfs-local 91d Filesystem
cr0x@server:~$ kubectl get pv pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}{"\n"}'
worker-07
Signification : S’il y a nodeAffinity, le PV est lié au nœud. Votre pod ne peut pas se replanifier ailleurs sans migration du volume.
Décision : Si c’est une base Tier-1, cessez de prétendre que c’est HA. Acceptez l’indisponibilité liée au nœud ou migrez vers un stockage répliqué/portable.
Tâche 2 : Voir pourquoi un pod est Pending (le scheduler vous renseigne)
cr0x@server:~$ kubectl describe pod -n prod app-db-0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m30s default-scheduler 0/12 nodes are available: 11 node(s) didn't match Pod's node affinity, 1 node(s) had taint {node.kubernetes.io/unreachable: }, that the pod didn't tolerate.
Signification : Ce n’est pas « Kubernetes qui est bizarre ». Il fait exactement ce que vous lui avez dit : placer le pod là où se trouve le PV, mais ce nœud est inaccessible.
Décision : Si vous avez besoin d’une récupération automatique, vous avez besoin d’un PV qui peut se déplacer (réseau) ou d’une réplique pouvant être promue (avec fencing).
Tâche 3 : Inspecter la santé du pool ZFS sur le nœud (commencez par les faits)
cr0x@server:~$ sudo zpool status -x
all pools are healthy
Signification : Pas d’erreurs connues et pas de vdevs dégradés. Cela ne veut pas dire « les performances sont parfaites », mais cela signifie « pas manifestement en feu ».
Décision : Si l’appli est lente mais que le pool est sain, pivotez vers la latence, le CPU, l’ARC et les propriétés du système de fichiers.
Tâche 4 : Trouver l’accumulation d’erreurs silencieuses (le genre effrayant)
cr0x@server:~$ sudo zpool status
pool: tank
state: DEGRADED
status: One or more devices has experienced an error resulting in data corruption.
action: Restore the file in question if possible. Otherwise restore the entire pool from backup.
scan: scrub repaired 0B in 05:12:44 with 2 errors on Sun Dec 22 03:10:24 2025
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
nvme-SAMSUNG_MZVLB1T0 ONLINE 0 0 2
nvme-SAMSUNG_MZVLB1T0 ONLINE 0 0 0
errors: Permanent errors have been detected in the following files:
tank/k8s/pv/pvc-...:<0x123>
Signification : Les erreurs de checksum et les erreurs permanentes ne sont pas à « surveiller plus tard ». Elles indiquent une corruption que ZFS n’a pas pu réparer.
Décision : Traitez cela comme un incident de perte de données : identifiez le PV affecté, restaurez depuis une sauvegarde ou une réplique, et planifiez le remplacement du disque + vérification du scrub.
Tâche 5 : Vérifier ashift et l’alignement des secteurs physiques (décision permanente)
cr0x@server:~$ sudo zdb -C tank | grep -E 'ashift|vdev_tree' -n | head
72: ashift: 12
Signification : ashift=12 implique des secteurs 4K. Si vos disques sont en 4K et ashift est 9, vous payez une pénalité d’écriture permanente.
Décision : Si ashift est incorrect sur des données en production, planifiez une migration vers un pool reconstruit. Ne perdez pas de temps à vouloir « régler ça » autrement.
Tâche 6 : Vérifier l’espace libre du pool et la fragmentation (capacité ≠ performance utilisable)
cr0x@server:~$ zpool list -o name,size,alloc,free,frag,health
NAME SIZE ALLOC FREE FRAG HEALTH
tank 3.62T 3.02T 614G 61% ONLINE
Signification : 61% de fragmentation avec une forte allocation est un multiplicateur de latence. Les écritures deviennent coûteuses, et les écritures sync encore plus.
Décision : Si la frag augmente et que la latence compte, augmentez le pool, réduisez le churn, ou migrez les charges chaudes. Reconsidérez aussi recordsize/volblocksize et la rétention des snapshots.
Tâche 7 : Inspecter les propriétés du dataset pour un PV (attraper le « taille unique pour tous » accidentel)
cr0x@server:~$ sudo zfs get -o name,property,value -s local,default recordsize,compression,atime,logbias,primarycache tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a recordsize 128K
tank/k8s/pv/pvc-7b3b3b9a compression lz4
tank/k8s/pv/pvc-7b3b3b9a atime on
tank/k8s/pv/pvc-7b3b3b9a logbias latency
tank/k8s/pv/pvc-7b3b3b9a primarycache all
Signification : atime=on est souvent un amplificateur d’écriture inutile pour les bases de données. recordsize=128K peut être mauvais pour des IO aléatoires petits.
Décision : Pour des datasets de base de données, envisagez atime=off et recordsize ajusté à la taille de bloc typique (souvent 16K pour Postgres). Validez avec des benchmarks, pas du folklore.
Tâche 8 : Pour les PVs basés sur ZVOL, vérifier volblocksize (levier de performance caché)
cr0x@server:~$ sudo zfs get -o name,property,value volblocksize tank/k8s/zvol/pvc-1f2e3d4c
NAME PROPERTY VALUE
tank/k8s/zvol/pvc-1f2e3d4c volblocksize 8K
Signification : volblocksize est fixé à la création. Si votre base utilise des pages 16K et que vous avez choisi 8K, vous pouvez doubler les opérations IO.
Décision : Si volblocksize est incorrect pour une charge critique, planifiez la migration du volume vers un zvol de taille correcte.
Tâche 9 : Confirmer si un dataset a une réservation (prévenir la cascade « pool plein »)
cr0x@server:~$ sudo zfs get -o name,property,value refreservation,reservation tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a reservation none
tank/k8s/pv/pvc-7b3b3b9a refreservation none
Signification : Pas d’espace réservé. Un voisin bruyant peut remplir le pool et votre PV « important » échouera lors des écritures.
Décision : Pour les PV critiques sur des pools partagés, allouez des réservations ou séparez les pools. Préférez la séparation technique plutôt que les discussions pendant les incidents.
Tâche 10 : Vérifier le calendrier de scrub et le dernier résultat (les désastres lents commencent ici)
cr0x@server:~$ sudo zpool status tank | sed -n '1,12p'
pool: tank
state: ONLINE
scan: scrub repaired 0B in 04:01:55 with 0 errors on Sun Dec 15 03:00:11 2025
config:
...
Signification : Le scrub a été exécuté et n’a trouvé aucune erreur. Parfait. Si votre scrub n’a pas tourné depuis des mois, vous différerez une mauvaise nouvelle.
Décision : Définissez un rythme de scrub adapté à la taille des disques et au churn. Puis alertez si les scrubs cessent ou commencent à trouver des erreurs.
Tâche 11 : Valider la fraîcheur de la réplication (RPO est une métrique, pas une promesse)
cr0x@server:~$ sudo zfs list -t snapshot -o name,creation -s creation | tail -5
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0010 Thu Dec 25 00:10 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0020 Thu Dec 25 00:20 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0030 Thu Dec 25 00:30 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0040 Thu Dec 25 00:40 2025
tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 Thu Dec 25 00:50 2025
Signification : Des snapshots existent sur la source. Ce n’est pas encore de la réplication. Vous devez confirmer qu’ils sont arrivés sur la cible et qu’ils sont récents.
Décision : Si les snapshots accusent un retard supérieur à votre RPO, limitez le trafic, déboguez les échecs send/recv, et cessez de vendre de la « HA » en interne.
Tâche 12 : Faire une simulation mentale de zfs send/receive (savoir ce qui se passerait)
cr0x@server:~$ sudo zfs send -nPv tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 | head
send from @ to tank/k8s/pv/pvc-7b3b3b9a@replica-20251225-0050 estimated size is 3.14G
total estimated size is 3.14G
TIME SENT SNAPSHOT
Signification : La taille estimée du send est raisonnable. Si elle est énorme pour un petit changement, vous pouvez avoir un mismatch de recordsize ou un churn excessif.
Décision : Si les envois incrémentiels sont anormalement grands, révisez la fréquence des snapshots, recordsize/volblocksize, et si l’appli réécrit constamment de gros fichiers.
Tâche 13 : Identifier les blocages de montage/IO au niveau du nœud (quand les pods « bloquent »)
cr0x@server:~$ dmesg -T | tail -20
[Thu Dec 25 01:12:11 2025] INFO: task kworker/u32:4:12345 blocked for more than 120 seconds.
[Thu Dec 25 01:12:11 2025] zio pool=tank vdev=/dev/nvme0n1 error=5 type=1 offset=123456 size=131072 flags=1809
[Thu Dec 25 01:12:12 2025] blk_update_request: I/O error, dev nvme0n1, sector 987654
Signification : Le kernel rapporte des tâches bloquées et des erreurs IO. Ce n’est pas un problème Kubernetes ; c’est un incident de stockage sur le nœud.
Décision : Cordonnez le nœud, drenez les charges non-stateful, et commencez le remplacement/réparation. Ne vous contentez pas de « redémarrer le pod » sur un chemin IO cassé.
Tâche 14 : Voir si Kubernetes est bloqué à détacher un volume (symptôme control-plane, cause stockage)
cr0x@server:~$ kubectl get volumeattachment
NAME ATTACHER PV NODE ATTACHED AGE
csi-9a2d7c5f-1d20-4c6a-a0a8-1c0f67c9a111 zfs.csi.example.com pvc-7b3b3b9a-1a2b-4f31-9bd8-3c1f9d3b2d1a worker-07 true 3d
Signification : Kubernetes croit que le volume est attaché à worker-07. Si worker-07 est mort, cette attache peut bloquer le basculement.
Décision : Suivez la procédure de détachement forcé sûre de votre driver. Si vous n’en avez pas, c’est une lacune de conception — pas une faute d’opération.
Tâche 15 : Vérifier la pression ARC (les manques de cache ressemblent à « stockage lent »)
cr0x@server:~$ sudo arcstat 1 5
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
01:20:11 812 401 49 122 15 249 31 30 3 28.1G 31.9G
01:20:12 790 388 49 110 14 256 32 22 2 28.1G 31.9G
01:20:13 840 430 51 141 17 260 31 29 3 28.1G 31.9G
01:20:14 799 420 53 150 18 244 30 26 3 28.1G 31.9G
01:20:15 820 415 51 135 16 255 31 25 3 28.1G 31.9G
Signification : Un miss% élevé signifie que les lectures tombent sur le disque. Si vos disques vont bien mais que la latence grimpe, la pression ARC peut être en cause.
Décision : Si l’ARC est trop petit ou la mémoire contrainte par des pods, pensez au dimensionnement du nœud, aux politiques mémoire cgroup, et aux paramètres primarycache des datasets.
Tâche 16 : Confirmer l’espace libre effectif du système de fichiers vs l’espace libre du pool (pièges de quota)
cr0x@server:~$ sudo zfs get -o name,property,value used,avail,quota,refquota tank/k8s/pv/pvc-7b3b3b9a
NAME PROPERTY VALUE
tank/k8s/pv/pvc-7b3b3b9a used 187G
tank/k8s/pv/pvc-7b3b3b9a avail 13.0G
tank/k8s/pv/pvc-7b3b3b9a quota 200G
tank/k8s/pv/pvc-7b3b3b9a refquota none
Signification : Le dataset a atteint son quota ; l’appli verra ENOSPC même si le pool a encore de l’espace libre.
Décision : Augmentez le quota (avec changement contrôlé), nettoyez les données, ou scalez l’application. N’ajoutez pas juste des disques si le quota limite.
Mode opératoire de diagnostic rapide
Quand la page sonne et que quelqu’un dit « le stockage est down », vous avez besoin d’un ordre d’opérations impitoyable. Le but est d’isoler rapidement le goulot :
ordonnancement, attachement/montage, santé ZFS, ou défaillance du périphérique sous-jacent.
Première étape : déterminer si c’est un problème d’ordonnancement/placement ou une panne IO
-
Le pod est-il Pending ou Running ?
- Si Pending : vérifiez les événements de
kubectl describe podpour affinité de nœud/taints. - Si Running mais bloqué : suspectez un chemin de montage/IO.
- Si Pending : vérifiez les événements de
- Le PV est-il lié au nœud ? Vérifiez PV nodeAffinity. Si oui, panne du nœud = indisponibilité du volume à moins d’avoir réplication/promotion.
Deuxième étape : vérifier l’attachement et l’état du montage (control-plane vs réalité du nœud)
- Vérifiez
kubectl get volumeattachmentpour des attachements bloqués. - Sur le nœud, vérifiez les blocages de montage et les erreurs IO via
dmesg -T. - Si votre driver CSI a des logs, cherchez timeouts, erreurs de permission, ou boucles « already mounted ».
Troisième étape : vérifier la santé du pool ZFS et du dataset (pas seulement « est-ce monté ? »)
zpool status -xetzpool statuscomplet pour les erreurs.zpool listpour la fragmentation et l’espace libre.zfs getsur le dataset/zvol affecté pour des propriétés alignées avec les attentes de la charge.
Quatrième étape : décider de récupérer en redémarrant, en basculant ou en restaurant
- Redémarrer seulement si le nœud et le pool sont sains et que le problème est logiciel (appli, kubelet, CSI transitoire).
- Basculement seulement si vous pouvez garantir single-writer et que vous avez une réplique connue bonne.
- Restaurer si vous avez des erreurs de checksum, des erreurs permanentes, ou un pool compromis.
Blague #2 : Kubernetes replanifiera votre pod en quelques secondes ; vos données se replanifieront approximativement jamais.
Trois mini-histoires d’entreprise (toutes bien réelles)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise SaaS de taille moyenne a construit une plateforme Kubernetes « haute performance » basée sur ZFS local. Chaque nœud avait des miroirs NVMe.
Leurs StatefulSets utilisaient un driver CSI qui provisionnait des datasets localement, et tout hurlait dans les benchmarks.
L’hypothèse erronée était subtile : « Si un nœud meurt, Kubernetes ramènera le pod ailleurs. » Vrai pour le stateless.
Faux pour les PV locaux. Ils avaient lu les mots « PersistentVolume » et l’ont interprété comme « persistant entre nœuds ».
Quand un switch de rack est mort, un groupe de nœuds est devenu inaccessible. Les pods de base de données sont passés en Pending car leurs PV étaient liés aux nœuds.
L’on-call a tenté de « supprimer de force » des pods, puis « recréer des PVC », puis « juste scale à zéro et revenir ». Rien de tout cela n’a déplacé les données.
L’incident n’était pas juste une indisponibilité ; c’était une paralysie décisionnelle. Personne ne pouvait répondre : est-il sûr d’attacher le même dataset sur un autre nœud ?
La réponse était « impossible », mais ils ne l’ont su qu’au moment de l’incident.
La correction était ennuyeuse : ils ont reclassifié quelles charges pouvaient utiliser des PV liés au nœud, ont déplacé l’état tier-1 vers un stockage portable,
et ont écrit un runbook qui commence par « Le PV est-il node-affined ? » Cette question seule a fait gagner des heures lors d’incidents futurs.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une grande équipe d’entreprise voulait réduire la surcharge de stockage. Les snapshots s’accumulaient, les fenêtres de réplication s’allongeaient, et quelqu’un
a proposé un « sprint d’efficacité » : augmenter la compression, réduire recordsize globalement, et augmenter la fréquence des snapshots pour réduire le RPO.
Le changement semblait excellent sur le papier. En pratique, le recordsize plus petit a augmenté la pression métadonnées et la fragmentation sur les datasets très actifs.
La fréquence accrue des snapshots a augmenté le churn et la taille des sends pour des charges qui réécrivent de gros fichiers. La réplication n’est pas devenue plus rapide ; elle est devenue plus bruyante.
Pire : ils ont appliqué les changements globalement, y compris sur des volumes qui n’en avaient pas besoin : agrégateurs de logs, caches de build, et bases de données aux IO très différentes.
Les latences tail ont augmenté d’abord. Puis les profondeurs de file. Puis les tickets support.
La leçon n’était pas « la compression est mauvaise » ni « le petit recordsize est mauvais ». La leçon est que les réglages de stockage sont spécifiques à la charge,
et les optimisations globales sont la façon de provoquer une panne inter-fonctionnelle.
Ils ont récupéré en revenant à des valeurs par défaut par StorageClass et en définissant des profils : « db-postgres », « db-mysql », « logs », « cache », chacun
mappé à un template de dataset. L’équipe plateforme a cessé d’être une secte de tuning et a commencé à être un service.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une société de services financiers exploitait ZFS sur des nœuds de stockage dédiés exportant des volumes vers Kubernetes. Rien de fancy. L’« innovation » était la discipline :
scrubs hebdomadaires, alertes sur toute erreur de checksum, et une politique visant à déclencher un incident dès qu’un pool est dégradé même si les applis semblent saines.
Un mois, les scrubs ont commencé à rapporter un petit nombre d’erreurs de checksum correctibles sur un miroir. Les applis étaient saines. La tentation
était de différer : « On remplacera le disque au prochain trimestre. » Ils ne l’ont pas fait. Ils ont remplacé le disque suspect et relancé le scrub jusqu’à nettoyé.
Deux semaines plus tard, un autre disque dans le même pool a subi une panne soudaine. Parce que le disque précédent avait été remplacé, le miroir
est resté intact et le pool est resté ONLINE. Pas de panique, pas de restauration, pas de question exécutive « pourquoi on ne l’a pas vu venir ? »
L’équipe n’a pas fêté ça en salle de crise. C’était le but. La pratique correcte était si ennuyeuse qu’elle a empêché l’histoire d’exister.
En opérations, l’ennuyeux est une fonctionnalité.
Erreurs courantes : symptôme → cause racine → correction
Cette section est volontairement spécifique. Si vous reconnaissez un symptôme, vous devez pouvoir agir sans réinventer l’ingénierie du stockage sous pression.
1) Pod bloqué en Pending après une panne de nœud
Symptôme : Le pod du StatefulSet ne se planifie pas ; les événements mentionnent l’affinité de nœud ou « volume node affinity conflict ».
Cause racine : PV local lié au nœud ; le nœud est inaccessible ou tainté.
Correction : Si l’indisponibilité est acceptable, restaurez le nœud. Sinon, redesign : stockage portable ou stockage local répliqué avec promotion et fencing.
2) Pod Running mais l’appli fait des timeouts sur le disque
Symptôme : Le conteneur est up ; les logs de l’appli montrent des timeouts IO ; kubectl exec se fige parfois.
Cause racine : Erreurs sur le périphérique sous-jacent ou IO bloqué ; ZFS peut réessayer ; nœud « à moitié mort ».
Correction : Vérifiez dmesg, zpool status. Cordonnez le nœud. Remplacez les périphériques défaillants. Ne redémarrez pas simplement les pods sur le même chemin IO cassé.
3) « No space left on device » alors que le pool a de l’espace libre
Symptôme : L’application reçoit ENOSPC ; zpool list montre de l’espace libre.
Cause racine : Quota ou refquota du dataset atteint, ou réservations de snapshots consommant l’espace.
Correction : Inspectez zfs get quota,refquota,used,avail. Augmentez le quota, nettoyez les données, ou ajustez la politique de snapshot.
4) La réplication existe mais le basculement perd les « écritures récentes »
Symptôme : Après promotion, les données sont obsolètes ; les dernières minutes manquent.
Cause racine : Réplication asynchrone et RPO non respecté (retard des snapshots, backlog send).
Correction : Mesurez le lag de réplication ; ajustez la fréquence des snapshots et la concurrence des send ; assurez la bande passante ; envisagez la réplication synchrone si le business l’exige (en acceptant la latence).
5) Envois incrémentiels anormalement grands
Symptôme : La réplication incrémentielle gonfle malgré de petits changements.
Cause racine : L’application réécrit de gros fichiers ; mauvais match de recordsize ; fragmentation ; trop de petits snapshots provoquant du churn.
Correction : Ajustez recordsize/volblocksize par charge ; réduisez le churn ; adaptez la cadence des snapshots ; envisagez la réplication au niveau applicatif pour les patterns à réécritures lourdes.
6) Erreurs de montage : « already mounted » ou pods bloqués en Terminating
Symptôme : Les logs CSI montrent des conflits de montage ; les pods restent en Terminating ; l’attachement indique true.
Cause racine : Le nœud est mort sans démontage propre ; objet d’attachement obsolète ; le driver ne gère pas proprement la récupération de crash.
Correction : Utilisez la procédure de détachement forcé supportée par le driver ; appliquez le fencing ; ajustez les timeouts kubelet ; validez le comportement de récupération de crash en staging.
7) Le pool est ONLINE mais la latence est terrible
Symptôme : Pas d’erreurs ; applis lentes ; p99 qui grimpe ; scrubs OK.
Cause racine : Pool presque plein, forte fragmentation, misses ARC, amplification des écritures sync, ou petits écritures aléatoires frappant des vdevs HDD.
Correction : Vérifiez la fragmentation et l’allocation ; ajustez les propriétés dataset ; ajoutez de la capacité ; déplacez les charges sync-intensives sur des vdevs avec SLOG approprié (uniquement si vous comprenez son usage).
Listes de contrôle / plan pas à pas
Étape par étape : choisir la bonne stratégie de PV par charge
-
Classifiez la charge.
- Tier-0 : perte de données inacceptable ; indisponibilité minutes, pas heures.
- Tier-1 : indisponibilité acceptable mais doit être prévisible ; restauration testée.
- Tier-2 : caches reconstruisables et données dérivées.
-
Choisissez le domaine de panne que vous pouvez accepter.
- Si Tier-0 : n’utilisez pas de PV local lié au nœud sans réplication + fencing.
- Si Tier-1 : le PV local peut convenir si la récupération du nœud est rapide et exercée.
- Si Tier-2 : le PV local est généralement acceptable ; optimisez pour la simplicité.
-
Choisissez dataset vs zvol.
- Dataset pour fichiers généraux, logs et applis qui bénéficient d’une inspection facile.
- Zvol pour la sémantique bloc ou quand votre stack CSI l’attend ; définissez volblocksize intentionnellement.
-
Définissez des profils de stockage en tant que code.
- Par profil : recordsize/volblocksize, compression, atime, logbias, quotas/réservations.
- Mappez les profils aux StorageClasses, pas au savoir tribal.
-
Planifiez et testez la panne d’un nœud.
- Tuez un nœud en staging pendant que la BDD écrit.
- Chronométrez combien de temps jusqu’à rétablissement du service.
- Vérifiez qu’aucun split-brain n’est possible.
Checklist opérationnelle : à quoi ressemble la production prête
- Scrubs planifiés et alertes si manqués ou erreurs trouvées.
- Surveillance de la santé des pools (dégradé, erreurs de checksum, événements de retrait de périphérique).
- Seuils de capacité basés sur l’allocation du pool et la fragmentation, pas seulement les Go libres.
- Répliques vérifiées (si réplication utilisée) : dernière réception réussie, lag, et procédure de promotion.
- Fencing documenté et testé (si promotion de répliques) : qui/quoi empêche le double-writer.
- Runbooks existants pour : perte de nœud, panne de disque, attachement bloqué, restauration depuis snapshot, et rollback.
- Sauvegardes testées en restauration, pas en bonnes intentions.
FAQ
1) Puis-je obtenir une « vraie HA » avec des PV ZFS locaux ?
Pas par défaut. Les PV locaux sont liés à un nœud. Pour la HA, vous avez besoin de réplication vers un autre nœud et d’un mécanisme de promotion sûr avec fencing,
ou de stockage réseau/portable.
2) La réplication ZFS (send/receive) suffit-elle pour le basculement ?
C’est nécessaire mais pas suffisant. Il vous faut aussi de l’orchestration (quand snapshotter, envoyer, recevoir, promouvoir) et un fencing strict en single-writer
pour éviter de corrompre les données lors de pannes partielles.
3) Dois-je utiliser des datasets ou des zvols pour les PV Kubernetes ?
Les datasets sont plus simples à inspecter et à tuner pour les charges fichier. Les zvols sont meilleurs quand vous avez besoin de sémantique bloc ou que votre driver CSI attend du bloc.
Pour les bases, l’un ou l’autre peut convenir — à condition de régler recordsize/volblocksize de manière délibérée.
4) Quel est le plus gros « piège » lors des pannes de nœud ?
L’état d’attachement/montage reste bloqué pendant que le nœud est inaccessible, et votre cluster ne peut pas rattacher en toute sécurité ailleurs sans risquer le double-writer.
Si votre conception dépend d’opérations forcées manuelles, vous pariez votre RTO sur le sang-froid humain.
5) Kubernetes gère-t-il le fencing pour moi ?
Non. Kubernetes peut supprimer des pods et les replanifier, mais il ne peut garantir qu’un nœud mort n’écrit plus sur le stockage à moins que le système de stockage
n’impose l’exclusivité ou que vous implémentiez le fencing en externe.
6) Si j’exécute ZFS sur des nœuds de stockage dédiés et que j’exporte en NFS, est-ce « mauvais » ?
Pas intrinsèquement. C’est un compromis : portabilité plus simple pour les pods, mais vous devez concevoir la HA stockage et la fiabilité réseau. Cela peut être très raisonnable,
surtout pour des charges mixtes, si vous traitez les nœuds de stockage comme des systèmes de production de première classe.
7) Quelles propriétés ZFS sont les plus importantes pour les PV ?
Pour les datasets : recordsize, compression=lz4 (généralement), atime=off pour de nombreuses charges d’écriture intensive, primarycache, logbias pour les applis sync-intensives.
Pour les zvols : volblocksize et compression. Ne négligez pas non plus quotas/réservations.
8) Comment empêcher un voisin bruyant de remplir le pool et d’affecter les PV critiques ?
Utilisez des quotas de dataset pour l’équité, et des réservations/refreservations pour les volumes critiques si vous partagez des pools. Mieux : séparez les charges critiques
sur des pools ou nœuds différents quand les enjeux sont élevés.
9) Ajouter un SLOG est-il toujours une bonne idée pour les bases de données ?
Non. Un SLOG aide seulement pour les écritures synchrones et seulement s’il est à faible latence et sûr en cas de perte d’alimentation. Un mauvais SLOG est un placebo coûteux ou un nouveau point de défaillance.
Mesurez le comportement sync de votre charge avant d’acheter du hardware.
10) Quelle est la façon la plus propre de gérer « le nœud disparaît » avec des applis stateful ?
Privilégiez une architecture où l’état autoritaire est répliqué au niveau applicatif (p.ex. réplication de base de données) ou stocké sur un stockage portable avec des
semantics de failover claires. La réplication au niveau stockage peut marcher, mais elle doit être conçue comme un système, pas un script.
Conclusion : prochaines étapes à faire cette semaine
ZFS protégera volontiers vos données contre les rayons cosmiques et les disques négligents. Kubernetes supprimera volontiers vos pods et les replanifiera ailleurs.
Le piège est de supposer que ces deux « volontés » s’alignent lors d’une panne de nœud. Elles ne le font pas à moins que vous ne conceviez pour cela.
Prochaines étapes pratiques :
- Inventoriez les StatefulSets et étiquetez-les selon la tolérance aux pannes (Tier-0/1/2). Rendez-le explicite.
- Trouvez les PV liés aux nœuds et décidez si cette indisponibilité est acceptable. Si non, redesign maintenant — pas pendant une panne.
- Standardisez les profils de stockage (propriétés dataset/zvol) par classe de charge. Arrêtez les expériences globales de tuning en production.
- Ajoutez des alertes santé ZFS et scrub en parallèle des métriques Kubernetes. Le stockage peut être cassé pendant que les pods semblent « OK ».
- Organisez une game day panne de nœud pour une charge stateful. Chronométrez-la. Documentez-la. Corrigez les parties qui demandent des exploits héroïques.
Si vous ne faites qu’une chose : faites du domaine de panne une décision de conception prioritaire. Le ZFS local est rapide. Mais un stockage rapide qui ne peut pas basculer
est juste un moyen très efficace d’être indisponible.