Vous montez un répertoire de l’hôte dans un conteneur avec un bind mount. À l’intérieur du conteneur, il est vide. Vous revérifiez le chemin. Vous essayez un autre shell. Vous redémarrez Docker. Vous commencez à marchander avec l’univers. Le répertoire hôte est rempli de fichiers, et pourtant le conteneur insiste pour afficher un vide immaculé.
Cela n’est souvent pas un problème de permissions, pas SELinux, pas « Docker qui fait Docker ». C’est la propagation des montages : la façon dont les événements de montage (nouveaux montages) se propagent — ou non — entre l’hôte et le namespace de montage du conteneur. C’est subtil. C’est Linux. Et c’est réparable.
Le symptôme exact et pourquoi il induit en erreur des gens compétents
Définissons précisément le mode d’échec « le bind mount semble vide », car « vide » peut désigner trois choses différentes :
- Vraiment vide : à l’intérieur du conteneur vous voyez un répertoire vide, alors que l’hôte contient des fichiers.
- Contenu du sous-montage manquant : le répertoire contient des fichiers, mais un système de fichiers monté en-dessous (comme
/mnt/dataou/var/lib/kubelet/pods) apparaît vide ou comme un répertoire ordinaire. - Vue obsolète : le conteneur voit des contenus anciens, mais les montages effectués après le démarrage du conteneur n’apparaissent pas.
Le premier cas est généralement un problème de permissions, de discordance UID/GID, ou SELinux/AppArmor. Mais le deuxième et le troisième sont des bugs classiques de propagation de montage. Vous avez monté quelque chose sous le chemin bind-monté sur l’hôte, et le conteneur n’a pas reçu l’information.
Le piège mental : les bind mounts donnent l’impression d’« une fenêtre sur l’hôte ». Ce n’est pas ça. C’est une référence à un chemin, résolue dans un namespace de montage, avec une sémantique de propagation spécifique. Linux fait exactement ce que vous avez demandé, pas ce que vous aviez en tête.
Blague courte #1 : si vous voulez un jour vous sentir puissant, créez un namespace de montage ; vous pouvez faire en sorte que / signifie ce que vous voulez. C’est comme voyager dans le temps, mais avec plus de flags mount.
Faits et historique intéressants (court, concret, utile)
- La propagation des montages est arrivée dans le noyau Linux à l’époque 2.6 dans le cadre d’un effort plus large pour supporter les conteneurs : namespaces de montage séparés et partage contrôlé entre eux.
- La fonctionnalité de « shared subtree » existe à cause des montages récursifs. Un simple bind mount ne suffit pas si vous voulez que les événements de montage sous un répertoire apparaissent ailleurs.
- Docker s’est initialement appuyé sur les valeurs par défaut de Linux. Ces valeurs varient selon la distribution et la configuration de l’init system, d’où des comportements différents pour la même commande Docker sur des hôtes différents.
- systemd a changé la donne. Beaucoup de systèmes gérés par systemd définissent par défaut
/(ou des points de montage clés) commeshared, ce qui affecte la façon dont les montages se propagent vers les services et conteneurs. - Kubernetes expose explicitement la propagation de montage (
mountPropagation: HostToContainer, etc.) parce que les plugins de stockage et les drivers CSI rencontrent constamment ces problèmes. - « rshared » n’est pas une question de permissions plus permissives. Il s’agit des événements de montage, pas de l’accès aux fichiers. Vous pouvez avoir des permissions parfaites et voir quand même des sous-montages « vides ».
- OverlayFS a rendu les systèmes de fichiers racine des conteneurs bon marché mais n’a pas supprimé le besoin de comprendre les montages ; cela rend plus facile d’oublier que vous empilez des systèmes de fichiers.
- Les bind mounts peuvent masquer des montages. Si vous bind-montez un répertoire par-dessus un point de montage existant, vous cachez ce qui y était monté. Parfois « vide » signifie « vous avez monté par-dessus ».
- Les runtimes de conteneurs choisissent des valeurs par défaut sécuritaires. Beaucoup choisissent
rprivatepour les bind mounts afin d’éviter que les conteneurs n’influencent la table de montages de l’hôte.
Propagation de montage : ce qui se passe réellement
Linux a un concept appelé namespace de montage. Chaque namespace a sa propre vue de la table des montages : ce qui est monté où, et avec quels flags. Les conteneurs utilisent cela pour que un conteneur puisse monter des choses sans griffonner la table de l’hôte (et vice versa).
Mais les montages ne sont pas qu’une table statique. Ce sont des événements. Un processus peut monter un système de fichiers sur /mnt/thing après que votre conteneur a démarré. Que ce nouveau montage apparaisse dans le conteneur dépend de la propagation entre les deux namespaces de montage.
Shared, private, slave : trois personnalités
Chaque point de montage a un type de propagation. En pratique vous verrez :
- private : les événements de montage/démontage sous ce montage ne se propagent pas aux pairs.
- shared : les événements de montage/démontage se propagent à tous les montages du même groupe de pairs.
- slave : reçoit la propagation d’un montage partagé maître, mais n’envoie pas d’événements en retour.
Ensuite il y a les versions récursives :
- rshared, rprivate, rslave : appliquent la règle au montage et à tout ce qui se trouve en-dessous.
Voici le piège : les bind mounts Docker sont souvent créés avec un mode de propagation qui n’est pas rshared récursif. Donc si l’hôte monte quelque chose sous le chemin source plus tard, le conteneur ne le verra pas.
Un schéma réel fréquent qui déclenche le bug
Vous faites quelque chose comme :
- Bind-monter
/srvdans le conteneur en tant que/srv. - Sur l’hôte, plus tard, monter un partage NFS sur
/srv/data(ou systemd le monte à la demande). - À l’intérieur du conteneur,
/srv/datasemble vide ou comme un répertoire ordinaire, parce que le sous-montage n’a pas été propagé.
Ce n’est pas un problème de visibilité de fichiers. C’est un problème de visibilité de sous-montage.
Une citation, parce que les ops collectionnent ces cicatrices
Everything fails all the time.
— Werner Vogels
Mode opératoire de diagnostic rapide
Si un bind mount paraît vide, vous pouvez perdre un après-midi — ou suivre cette checklist en cinq minutes et savoir où est enterré le corps.
- Confirmez quel type de « vide » c’est. Des fichiers manquent-ils, ou seulement le contenu d’un sous-montage ?
- Vérifiez si le chemin hôte contient un point de montage en-dessous. Si oui, suspectez immédiatement la propagation.
- Inspectez les paramètres de propagation du montage dans le conteneur. Cherchez
Propagationdans la sortie de docker inspect. - Comparez les vues des namespaces de montage. Utilisez
nsenterpour regarder les montages comme le conteneur les voit. - Décidez : changer la propagation, changer l’architecture, ou cesser de monter sous ce chemin. Il existe un correctif « correct » et un correctif « colmatage ». Choisissez en connaissance de cause.
Raccourci : si vous montez des choses dynamiquement sous un répertoire et que vous bind-montez aussi ce répertoire dans des conteneurs, vous voulez presque toujours rshared ou rslave. Le défaut rprivate est sûr mais surprenant.
Tâches pratiques : commandes, sorties, décisions (12+)
Task 1: Reproduce the bug in a controlled way
cr0x@server:~$ sudo mkdir -p /srv/demo/submount
cr0x@server:~$ echo "host-file" | sudo tee /srv/demo/host.txt
host-file
cr0x@server:~$ docker run --rm -d --name mp-test -v /srv/demo:/demo alpine sleep 100000
b8a3b2c55d9f8e8a2c5a2b0f0d2f0b6d9a5f9d0c8b7a0d1c2b3a4f5e6d7c8b9
cr0x@server:~$ docker exec mp-test ls -la /demo
total 8
drwxr-xr-x 3 root root 4096 Jan 3 12:00 .
drwxr-xr-x 1 root root 4096 Jan 3 12:00 ..
-rw-r--r-- 1 root root 10 Jan 3 12:00 host.txt
drwxr-xr-x 2 root root 4096 Jan 3 12:00 submount
Ce que cela signifie : le bind mount de base fonctionne. Le conteneur voit les fichiers existants.
Décision : procédez à un montage sous /srv/demo/submount sur l’hôte et voyez si le conteneur l’hérite.
Task 2: Create a mount under the bind-mounted directory (host side)
cr0x@server:~$ sudo mount -t tmpfs tmpfs /srv/demo/submount
cr0x@server:~$ echo "mounted-file" | sudo tee /srv/demo/submount/mounted.txt
mounted-file
cr0x@server:~$ mount | grep "/srv/demo/submount"
tmpfs on /srv/demo/submount type tmpfs (rw,relatime)
Ce que cela signifie : il y a maintenant un sous-montage sous le répertoire que vous avez bind-monté dans le conteneur.
Décision : vérifiez si le conteneur voit mounted.txt. Si non, c’est la propagation.
Task 3: Check visibility inside the container
cr0x@server:~$ docker exec mp-test sh -lc "mount | grep /demo/submount || true; ls -la /demo/submount; cat /demo/submount/mounted.txt 2>/dev/null || echo 'no mounted.txt'"
total 8
drwxr-xr-x 2 root root 4096 Jan 3 12:00 .
drwxr-xr-x 3 root root 4096 Jan 3 12:00 ..
no mounted.txt
Ce que cela signifie : le conteneur ne voit pas le sous-montage tmpfs ; il voit le répertoire d’origine à la place.
Décision : confirmez le mode de propagation et corrigez avec rshared/rslave selon vos besoins de sécurité.
Task 4: Inspect the bind mount’s propagation setting
cr0x@server:~$ docker inspect mp-test --format '{{json .Mounts}}'
[{"Type":"bind","Source":"/srv/demo","Destination":"/demo","Mode":"","RW":true,"Propagation":"rprivate"}]
Ce que cela signifie : la propagation est rprivate. Les nouveaux montages sous /srv/demo n’apparaîtront pas dans le conteneur.
Décision : basculez sur rshared ou rslave pour ce montage.
Task 5: Re-run container with rshared propagation
cr0x@server:~$ docker rm -f mp-test
mp-test
cr0x@server:~$ docker run --rm -d --name mp-test -v /srv/demo:/demo:rshared alpine sleep 100000
a0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
cr0x@server:~$ docker inspect mp-test --format '{{range .Mounts}}{{.Destination}} {{.Propagation}}{{"\n"}}{{end}}'
/demo rshared
Ce que cela signifie : le bind mount est maintenant configuré pour propager les sous-montages.
Décision : validez que le sous-montage tmpfs existant devient visible.
Task 6: Validate submount visibility after changing propagation
cr0x@server:~$ docker exec mp-test sh -lc "mount | grep /demo/submount; ls -la /demo/submount; cat /demo/submount/mounted.txt"
tmpfs on /demo/submount type tmpfs (rw,relatime)
total 4
drwxr-xr-x 2 root root 60 Jan 3 12:01 .
drwxr-xr-x 3 root root 4096 Jan 3 12:01 ..
-rw-r--r-- 1 root root 13 Jan 3 12:01 mounted.txt
mounted-file
Ce que cela signifie : le conteneur voit maintenant le sous-montage de l’hôte. Correctif de propagation confirmé.
Décision : décidez si rshared est acceptable ou si vous préférez rslave pour une propagation unidirectionnelle.
Task 7: Choose rslave when you want one-way propagation
cr0x@server:~$ docker rm -f mp-test
mp-test
cr0x@server:~$ docker run --rm -d --name mp-test -v /srv/demo:/demo:rslave alpine sleep 100000
d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9
cr0x@server:~$ docker exec mp-test sh -lc "mount | grep /demo/submount; cat /demo/submount/mounted.txt"
tmpfs on /demo/submount type tmpfs (rw,relatime)
mounted-file
Ce que cela signifie : rslave laisse toujours passer les événements host → container, mais ne propage pas les montages du conteneur vers l’hôte.
Décision : en production, préférez généralement rslave sauf si vous avez explicitement besoin d’une propagation bidirectionnelle.
Task 8: Confirm host mount propagation state (shared vs private)
cr0x@server:~$ findmnt -o TARGET,PROPAGATION /srv/demo
TARGET PROPAGATION
/srv/demo shared
Ce que cela signifie : le point de montage hôte peut appartenir à un groupe de pairs partagé. S’il est private, vous devrez peut-être changer le côté hôte aussi.
Décision : si la propagation est private sur l’hôte et que vous avez besoin de partage, il peut être nécessaire d’utiliser mount --make-rshared (avec prudence).
Task 9: Safely view mount namespace differences using nsenter
cr0x@server:~$ pid=$(docker inspect -f '{{.State.Pid}}' mp-test)
cr0x@server:~$ sudo nsenter -t "$pid" -m -- findmnt -R /demo | head
TARGET SOURCE FSTYPE OPTIONS
/demo /dev/sda1 ext4 rw,relatime
/demo/submount tmpfs tmpfs rw,relatime
Ce que cela signifie : vous regardez les montages depuis le namespace de montage du conteneur sans dépendre des outils du conteneur.
Décision : si la vue du namespace n’inclut pas le sous-montage, c’est de la propagation ou du timing. Si elle l’inclut, le chemin de votre application est faux.
Task 10: Detect “mounted over it” masking mistakes
cr0x@server:~$ sudo mount -t tmpfs tmpfs /srv/demo
cr0x@server:~$ ls -la /srv/demo | head
total 8
drwxr-xr-x 2 root root 40 Jan 3 12:02 .
drwxr-xr-x 3 root root 18 Jan 3 12:00 ..
Ce que cela signifie : vous venez de monter tmpfs sur /srv/demo, masquant le contenu original. Félicitations, vous avez créé un « vide » de manière honnête.
Décision : démontez et repensez : ne montez pas par-dessus votre source de bind à moins que vous ne vouliez la remplacer.
Task 11: Inspect mounts with recursion awareness
cr0x@server:~$ sudo umount /srv/demo
cr0x@server:~$ findmnt -R /srv/demo
TARGET SOURCE FSTYPE OPTIONS
/srv/demo /dev/sda1 ext4 rw,relatime
/srv/demo/submount tmpfs tmpfs rw,relatime
Ce que cela signifie : -R montre les sous-montages. Si vous exécutez seulement mount | grep /srv/demo, vous pouvez manquer le montage imbriqué.
Décision : si les « données manquantes » résident sur un sous-montage, traitez-le comme un problème de propagation jusqu’à preuve du contraire.
Task 12: Verify SELinux label issues (to avoid false positives)
cr0x@server:~$ docker run --rm -v /srv/demo:/demo:Z alpine sh -lc "ls -la /demo | head"
total 8
drwxr-xr-x 3 root root 4096 Jan 3 12:00 .
drwxr-xr-x 1 root root 4096 Jan 3 12:03 ..
-rw-r--r-- 1 root root 10 Jan 3 12:00 host.txt
Ce que cela signifie : sur les systèmes avec SELinux en mode enforcing, l’option :Z relabelise le contenu pour l’accès du conteneur. Si cela corrige le « vide », ce n’était pas la propagation.
Décision : scindez le diagnostic : « refus de politique/permission » vs « sous-montage non propagé ». Différents correctifs, différents risques.
Task 13: Confirm AppArmor profile isn’t blocking mount operations (rare but nasty)
cr0x@server:~$ docker inspect mp-test --format '{{.AppArmorProfile}}'
docker-default
Ce que cela signifie : le conteneur s’exécute sous un profil (commun sur Ubuntu). En général cela affecte les tentatives de montage depuis le conteneur, pas la visibilité des montages hôtes.
Décision : si votre conteneur doit effectuer des montages (par ex. FUSE, comportements type CSI), vous pourriez avoir besoin de privilèges supplémentaires ou d’un design différent.
Task 14: Check whether the source path is itself a mount point with “private” propagation
cr0x@server:~$ findmnt -o TARGET,PROPAGATION /
TARGET PROPAGATION
/ shared
Ce que cela signifie : si / ou le point de montage parent pertinent est private, vous pouvez obtenir des comportements surprenants avec des montages imbriqués. Les choix par défaut de la distro comptent.
Décision : ne lancez pas aveuglément mount --make-rshared / en production. Faites-le seulement si vous comprenez l’ampleur de l’impact, et idéalement en fenêtre de maintenance.
Task 15: See exactly what Docker thinks it mounted
cr0x@server:~$ docker inspect mp-test --format '{{range .Mounts}}{{println .Source "->" .Destination "prop:" .Propagation}}{{end}}'
/srv/demo -> /demo prop: rslave
Ce que cela signifie : la vue de Docker suffit souvent à repérer le problème : rprivate alors que vous attendiez un comportement partagé.
Décision : si Docker indique rprivate, n’argumentez pas avec lui. Changez l’option de montage ou la structure.
Correctifs qui fonctionnent (et quand les utiliser)
Fix 1: Set propagation explicitly on the bind mount
Pour la CLI Docker, vous pouvez le spécifier dans la définition du volume :
- Bidirectionnel :
-v /path:/path:rshared - Hôte → conteneur seulement (généralement plus sûr) :
-v /path:/path:rslave
Quand l’utiliser : l’hôte monte ou démonte des choses sous la source du bind après le démarrage du conteneur (automount NFS, systemd mounts, staging CSI, montages loop, tmpfs temporaires).
Quand ne pas l’utiliser : si vous ne contrôlez pas ce que le conteneur peut faire et que vous craignez que des événements de montage se propagent vers l’hôte (évitez rshared sauf si nécessaire).
Fix 2: Stop mounting under a bind-mounted directory
C’est le correctif architectural ennuyeux : ne créez pas de sous-montages sous un chemin que vous bind-montez dans des conteneurs. Montez votre système de fichiers dynamique ailleurs, puis bind-montez directement le point de montage final dans le conteneur.
Exemple : au lieu de bind-monter /srv puis monter NFS sur /srv/data, montez NFS sur /mnt/nfs/data et bind-montez /mnt/nfs/data directement.
Pourquoi ça marche : vous évitez de dépendre de la propagation. Moins de magie. Moins de surprises.
Fix 3: Make the host mount shared (only if you must)
Si le point de montage côté hôte est private, même un bind mount conteneur marqué rshared peut ne pas produire l’effet désiré. Dans certaines configurations, il faut que l’arbre de montage hôte soit partagé.
cr0x@server:~$ sudo mount --make-rshared /srv
cr0x@server:~$ findmnt -o TARGET,PROPAGATION /srv
TARGET PROPAGATION
/srv shared
Ce que cela signifie : les événements de montage sous /srv peuvent se propager aux montages pairs.
Décision : faites cela seulement pour un sous-arbre que vous contrôlez. Rendre / récursivement shared peut avoir des interactions surprenantes avec d’autres services.
Fix 4: Prefer --mount syntax for clarity
La syntaxe -v est compacte mais facile à mal lire. La forme --mount est verbeuse et moins ambiguë :
cr0x@server:~$ docker run --rm -d --name mp-test \
--mount type=bind,source=/srv/demo,target=/demo,bind-propagation=rslave \
alpine sleep 100000
2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1
Pourquoi cela compte : les incidents de production aiment les configurations qui « semblaient correctes ». Une configuration verbeuse a moins de chances de sembler correcte par hasard, ce qui est un avantage.
Fix 5: If your container needs to mount things, reconsider the design
Certaines charges de travail (outils de sauvegarde, agents de stockage, FUSE, composants de type plugin) veulent monter à l’intérieur du conteneur et que cela apparaisse sur l’hôte ou dans des conteneurs pairs. Voilà où rshared et les privilèges apparaissent, et où les revues de sécurité finissent par se perdre.
Conseil d’opinion : si vous avez besoin que des montages initiés par le conteneur apparaissent sur l’hôte, vous construisez un composant de système de stockage. Traitez-le comme tel : principe du moindre privilège, contraintes strictes et propagation explicite. Si vous n’en avez pas besoin, n’activez pas « au cas où ».
Blague courte #2 : « Exécutez-le en privilégié » est l’équivalent ops de « avez-vous essayé de l’éteindre et de le rallumer », sauf que cela ouvre de nouvelles façons de casser les choses.
Trois mini-récits du monde de l’entreprise
Mini-story 1: The incident caused by a wrong assumption
Une équipe a migré un système batch legacy dans des conteneurs. Le contrat était simple : déposer des fichiers dans /incoming, les traiter, écrire le résultat dans /outgoing. Sur des VM, c’étaient juste des répertoires. À l’ère des conteneurs, ils ont bind-monté /srv/pipeline dans le conteneur comme /pipeline et ont gardé les chemins internes identiques.
Des mois plus tard, l’utilisation du stockage a grimpé. Quelqu’un a ajouté une unité automount pour que /srv/pipeline/incoming soit monté sur NFS durant les heures ouvrées. Idée propre : moins de disque local, mise à l’échelle facile. Le déploiement est parti un vendredi parce que le changement concernait « juste le stockage ».
Ce week-end-là, la flotte de workers conteneurisés a commencé à rapporter « pas de fichiers d’entrée ». Les métriques étaient étranges : le partage NFS était plein de fichiers, le contrôleur batch écrivait correctement, et les workers semblaient sains. L’astreinte a redémarré des workers. Rien n’a changé.
La cause racine : la propagation des montages. Les workers avaient démarré avant que l’automount ne se déclenche, donc dans leurs namespaces de montage, /pipeline/incoming est resté un répertoire ordinaire à jamais. Le montage NFS a eu lieu sur l’hôte, mais leur bind mount était rprivate. Ils n’ont jamais vu le sous-montage NFS.
Le correctif a été ennuyeux et immédiat : changer le bind mount en rslave et redéployer. Le correctif durable était plus important : arrêter de bind-monter un parent large comme /srv/pipeline ; monter le NFS sur un chemin stable et bind-monter ce répertoire précis. Cela a réduit le rayon d’impact quand des changements de stockage ont eu lieu.
Mini-story 2: The optimization that backfired
Une équipe plateforme voulait accélérer les bootstraps des nœuds. Ils ont déplacé plusieurs grands jeux de données du disque local vers des montages à la demande : certains étaient des images loop-mount, d’autres des partages réseau. L’idée : « activer » les montages seulement quand un nœud exécute une charge qui en a besoin.
Ils ont aussi standardisé les arguments de lancement des conteneurs : chaque service obtenait un bind mount de /opt/runtime pour que des outils communs et certificats soient disponibles. En dev ça fonctionnait. En staging aussi. En production, des services ont commencé à échouer comme si des données étaient corrompues : des applications voyaient des répertoires vides là où des jeux de données devaient être.
Il s’est avéré que le script d’« activation » montait les jeux de données sous /opt/runtime/datasets après que les conteneurs aient déjà démarré. Le bind mount était rprivate, donc les conteneurs n’ont jamais reçu les nouveaux sous-montages. Les ingénieurs ont essayé de « réparer » en redémarrant seulement les applis en échec, ce qui fonctionnait parfois — parce que le montage était actif avant le redémarrage. Ils ont appelé cela aléatoire. C’était déterministe, juste mal synchronisé.
Ils ont changé globalement le montage en rshared car cela fit disparaître rapidement le problème. Deux semaines plus tard, un autre incident : un conteneur de debug avec capacités supplémentaires a monté un tmpfs à un endroit qui s’est propagé vers l’arbre d’hôte. Cela n’a pas détruit de données, mais a embrouillé la supervision et rendu les scripts de nettoyage malades.
L’état final a été nuancé : rslave pour la plupart des services, ensembles de capacités stricts, et une règle que seul un agent dédié peut effectuer des montages. L’« optimisation » n’était pas mauvaise ; les hypothèses non examinées sur la propagation et les privilèges l’étaient.
Mini-story 3: The boring but correct practice that saved the day
Une autre organisation exploitait une flotte avec un stockage compliqué : caches SSD locaux, montages réseau, et remounts occasionnels lors d’incidents. Ils avaient une règle simple : pas de bind-mounts de répertoires parents larges. Montez seulement exactement ce dont le conteneur a besoin. Si un conteneur a besoin de /data/app, il obtient /data/app, pas /data.
Cela était impopulaire. Les développeurs aimaient la commodité. Les SRE aussi, jusqu’à ce qu’on les appelle en pleine nuit. L’équipe plateforme a fait respecter la règle en revue de code et via des vérifications CI pour les specs Compose et les manifests de déploiement.
Un jour, une maintenance de stockage a nécessité le remount d’un sous-arbre sous /data. Sur les hôtes où des services bind-montaient historiquement /data, cela aurait causé des bugs de visibilité partielle. Ici, la plupart des services avaient des montages directs de leurs chemins exacts, et seulement quelques agents spécialisés utilisaient rslave car ils s’attendaient à des sous-montages.
L’incident s’est terminé ennuyeusement. Quelques conteneurs ont été redémarrés dans le cadre de la maintenance, mais il n’y a pas eu de comportement mystère de « répertoire vide ». Le postmortem fut court et peu excitant, ce qui est le compliment le plus élevé que l’on puisse faire à une pratique opérationnelle.
Erreurs courantes : symptômes → cause racine → correctif
1) « Le répertoire est vide dans le conteneur, mais pas sur l’hôte »
- Symptôme :
lsmontre vide ; pas d’erreurs. - Cause racine : vous avez monté par-dessus le chemin source sur l’hôte (masquage), ou vous regardez un sous-montage qui n’a pas propagé.
- Correctif : exécutez
findmnt -R /host/pathpour voir les sous-montages ; évitez de monter par-dessus la source du bind ; définissezbind-propagation=rslavesi les sous-montages doivent apparaître.
2) « Les fichiers apparaissent après le redémarrage du conteneur »
- Symptôme : redémarrer le conteneur « corrige » le problème, mais seulement parfois.
- Cause racine : dépendance au timing : le montage hôte existe au démarrage du conteneur parfois, d’autres fois il est créé plus tard (automount, scripts à la demande).
- Correctif : utilisez
rslave/rsharedou assurez-vous que les montages soient actifs avant de démarrer les conteneurs (ordonnancement systemd).
3) « Seuls des sous-répertoires sont vides »
- Symptôme : fichiers de base visibles, mais un répertoire imbriqué particulier est vide.
- Cause racine : ce répertoire imbriqué est en réalité un point de montage sur l’hôte.
- Correctif : bind-montez directement le point de montage imbriqué, ou activez la propagation pour le bind mount parent.
4) « Ça marche sur Ubuntu mais pas sur une autre distro (ou inversement) »
- Symptôme : commande de conteneur identique, résultats différents selon l’hôte.
- Cause racine : valeurs par défaut de propagation différentes pour
/ou points de montage pertinents ; systemd a des defaults différents ; les runtimes de conteneurs aussi. - Correctif : ne comptez plus sur les valeurs par défaut ; déclarez explicitement la propagation ; vérifiez avec
findmnt -o TARGET,PROPAGATION.
5) « Nous avons mis rshared et maintenant des montages étranges apparaissent sur l’hôte »
- Symptôme : l’hôte voit des montages créés par des conteneurs, ou des montages persistent après la sortie du conteneur.
- Cause racine :
rsharedest bidirectionnel ; le conteneur a la capacité de monter (ou est privilégié) et les événements se propagent en retour. - Correctif : utilisez
rslavepour une propagation unidirectionnelle ; réduisez les capacités des conteneurs ; évitez les conteneurs privilégiés ; assurez-vous qu’un agent hôte gère le cleanup des montages.
6) « Le montage est là mais l’accès est refusé »
- Symptôme : les fichiers existent mais vous voyez des erreurs de permission ; parfois l’app voit « vide ».
- Cause racine : mismatch de contexte SELinux, restrictions AppArmor, discordance UID/GID, limitations Docker rootless.
- Correctif : sur systèmes SELinux utilisez
:Zou corrigez les labels ; alignez UID/GID ; ne confondez pas un refus de politique avec un problème de propagation.
7) « Nous avons bind-monté un symlink et obtenu le mauvais chemin »
- Symptôme : le conteneur voit un répertoire différent de celui attendu.
- Cause racine : le bind mount résout les symlinks au moment du montage ; les changements ultérieurs n’affectent pas cela ; ou le symlink pointe vers un montage qui n’est pas propagé.
- Correctif : bind-montez des chemins réels ; évitez l’indirection par symlink pour les points de montage de stockage ; vérifiez avec
readlink -fsur l’hôte.
Checklists / plan étape par étape
Step-by-step plan: fix a production “empty bind mount” incident without guessing
- Identifiez précisément ce qui manque. S’agit-il de fichiers, ou du contenu d’un sous-montage ? Si c’est un sous-montage, orientez-vous vite vers le diagnostic de propagation.
- Sur l’hôte, cartographiez l’arbre des montages. Utilisez
findmnt -Rsur la source du bind. Si vous voyez un montage imbriqué, vous avez un candidat coupable. - Confirmez la propagation de montage dans le conteneur.
docker inspectet regardez le champPropagation. S’il estrprivate, arrêtez d’attendre que des sous-montages apparaissent. - Décidez la direction de propagation la plus sûre.
- Besoin hôte → conteneur seulement : choisissez
rslave. - Besoin des deux directions : choisissez
rsharedet justifiez-le par écrit.
- Besoin hôte → conteneur seulement : choisissez
- Implémentez explicitement via
--mount. Déployez, redémarrez les conteneurs dépendants. - Vérifiez avec
nsenteroumountdans le conteneur. Ne vous fiez pas uniquement aux logs applicatifs comme unique signal. - Puis corrigez l’architecture. Préférez des montages directs de ce dont vous avez besoin plutôt que des bind mounts parents larges. Cela évite le prochain incident.
Checklist: when you should use rslave/rshared
- Vous utilisez des unités automount systemd sous un répertoire bind-monté dans des conteneurs.
- Vous montez du stockage réseau (NFS, CIFS) ou des périphériques bloc sous un répertoire déjà utilisé par des conteneurs.
- Vous avez un sidecar ou un agent hôte qui monte des jeux de données tardivement (après le démarrage des conteneurs).
- Vous faites quelque chose de type CSI, plugin ou intégration de stockage.
Checklist: when you should avoid rshared
- Le conteneur est privilégié ou a des capacités liées au montage et vous n’avez pas absolument besoin que des événements remontent sur l’hôte.
- Vous êtes en train de déboguer et tenté de « juste faire marcher » les choses.
- Vous ne pouvez pas garantir un comportement de nettoyage pour les montages créés par des processus conteneurisés.
FAQ
1) Why does my bind mount show files, but mounted filesystems under it are missing?
Parce que le bind mount de base est présent, mais les événements de sous-montage n’ont pas été propagés dans le namespace du conteneur. Définissez bind-propagation=rslave ou rshared, ou bind-montez le sous-montage directement.
2) What’s the difference between rshared and rslave in practice?
rshared propage les événements de montage/démontage dans les deux sens entre montages pairs. rslave permet la propagation hôte → conteneur mais pas conteneur → hôte. Pour la plupart des services en production, rslave est le choix sensé quand vous avez besoin de propagation.
3) Can mount propagation make a directory look completely empty?
Oui, si les « vraies données » sont sur un montage imbriqué (NFS, tmpfs, loop device) et que le conteneur ne voit que le répertoire sous-jacent. Cela paraîtra vide ou obsolète parce que vous regardez le mauvais niveau.
4) Is this a Docker bug?
Non. C’est le comportement des namespaces de montage Linux combiné aux valeurs par défaut raisonnables de Docker. Le bug, c’est l’hypothèse qu’un bind mount est automatiquement récursif et mis à jour dynamiquement pour les sous-montages.
5) Does this affect Docker volumes (named volumes) too?
Ça peut arriver, mais c’est le plus courant avec les bind mounts parce que vous mappez un chemin arbitraire de l’hôte où des sous-montages peuvent apparaître. Les volumes nommés sont gérés sous la zone de stockage de Docker et n’ont généralement pas de sous-montages surprises — sauf si vous en créez.
6) How does systemd automount interact with containers?
L’automount signifie que le montage a lieu au premier accès, potentiellement après le démarrage du conteneur. Si la vue du conteneur ne reçoit pas cet événement à cause de rprivate, il se peut qu’il ne voie jamais le système de fichiers monté. Utilisez rslave et considérez l’ordonnancement des montages avant le démarrage des conteneurs quand c’est possible.
7) What about rootless Docker?
Les environnements rootless ajoutent des contraintes : vous ne pourrez peut-être pas changer certaines propagations de montage ou effectuer des montages du tout. Si vous comptez sur des montages hôtes dynamiques qui doivent apparaître dans un conteneur rootless, reconsidérez l’architecture pour éviter cette dépendance.
8) Does Kubernetes solve this automatically?
Non. Kubernetes rend cela explicite. Les pods peuvent demander des modes de propagation de montage, et le cluster doit l’autoriser. Si vous exécutez des plugins de stockage ou avez besoin que des montages hôtes apparaissent dans des conteneurs, vous rencontrerez les mêmes sémantiques Linux sous-jacentes.
9) What’s the safest long-term pattern to avoid this entire class of bugs?
Ne bind-montez pas des parents larges comme /srv ou /data. Montez le stockage à des points de montage stables et dédiés et bind-montez seulement les répertoires exacts dont votre conteneur a besoin.
Conclusion: next steps you can apply today
Si vous ne retenez qu’une chose : un bind mount n’est pas un flux en direct d’événements de montage à moins que vous ne le rendiez tel. Quand votre conteneur voit un répertoire vide mais que l’hôte a des données, demandez d’abord : « ces données sont-elles réellement sur un sous-montage ? »
Actions pratiques :
- Sur un hôte affecté, exécutez
findmnt -Rsur votre source de bind et identifiez les montages imbriqués. - Exécutez
docker inspectet vérifiez le champPropagationdu montage. - Si vous avez besoin de visibilité des sous-montages hôte → conteneur, redéployez avec
bind-propagation=rslave(oursharedseulement si justifié). - Puis refactorez : bind-montez les points de montage exacts dont votre appli a besoin, pas tout le répertoire parent.
Voilà la différence entre un correctif ponctuel et ne jamais revoir cette page à 03:00.