Erreurs Docker containerd/runc : dépanner sans réinstaller

Cet article vous a aidé ?

Vous déployez un petit changement. Un conteneur qui fonctionnait hier meurt maintenant instantanément. Docker renvoie quelque chose d’inutile comme OCI runtime create failed, containerd mentionne un « shim », et runc tousse permission denied comme s’il voulait rester mystérieux.

Réinstaller Docker donne l’impression d’agir, mais c’est généralement de la panique avec des étapes supplémentaires. Ces pannes sont presque toujours diagnostiquables sur place : un montage cassé, un décalage de cgroup, un problème de pilote de stockage, un blocage seccomp/AppArmor, ou un disque qui a manqué de quelque chose que vous ne surveilliez pas.

Modèle mental : qui échoue, et où chercher

Quand un conteneur ne démarre pas, vous devez savoir quelle couche se plaint. Docker est l’accueil. containerd est le directeur de l’hôtel. runc est le serrurier. Le noyau est l’immeuble.

Ce que Docker fait (et ne fait pas)

Le démon de Docker (dockerd) gère les requêtes API, la gestion des images, les réseaux, les volumes, et orchestre les opérations runtime. Sur la plupart des installations modernes, Docker ne lance pas directement votre processus de conteneur ; il demande à containerd de le faire.

Ce que fait containerd

containerd gère le cycle de vie des conteneurs et le contenu. Il crée des « tasks » et invoque l’OCI runtime (souvent runc) pour configurer les espaces de noms, les cgroups, les montages, puis exécuter le processus du conteneur. Si vous voyez des erreurs comme failed to create shim task ou shim exited, vous êtes dans le territoire de containerd, mais la cause racine est souvent toujours le noyau/cgroup/système de fichiers.

Ce que fait runc

runc est l’implémentation de runtime OCI. Il lit un OCI config.json et demande au noyau d’exécuter les opérations difficiles : monter un rootfs, configurer les espaces de noms, appliquer les filtres seccomp, définir les capacités, configurer les cgroups, puis exécuter le binaire cible. Quand runc dit permission denied, cela peut signifier « seccomp », « LSM (AppArmor/SELinux) », « permissions du système de fichiers », ou « le noyau a refusé l’opération ». Le message est rarement précis. C’est notre travail de l’éclaircir.

Le noyau est l’endroit où « impossible » devient « évident »

La plupart des incidents « Docker est en panne » sont en fait : votre disque est plein (ou sans inodes), votre noyau a un comportement cgroup v2 auquel vous ne vous attendiez pas, votre système de fichiers ne supporte pas overlay mounts, votre politique de sécurité bloque des flags de clone(), ou votre DNS / règles iptables ont été réorganisés par quelqu’un qui « a fait du rangement ».

Idée paraphrasée (attribuée à John Allspaw) : « Le travail de fiabilité consiste à comprendre comment les systèmes échouent réellement, pas comment nous souhaiterions qu’ils échouent. »

Une opinion qui vous fera gagner des heures : ne commencez pas par réinstaller. Commencez par prouver quelle couche échoue, et si l’hôte est suffisamment sain pour exécuter quoi que ce soit.

Playbook rapide de diagnostic (vérifier d’abord/ensuite/après)

Ceci est l’ordre d’opérations « je suis en astreinte et le rayon d’impact augmente ». L’objectif n’est pas d’être exhaustif. L’objectif est de trouver rapidement le goulot d’étranglement et d’arrêter de deviner.

Premier : confirmez que l’hôte ne vous ment pas

  1. Espace disque et inodes sur le chemin des données Docker (/var/lib/docker) et sur le système de fichiers racine.
  2. Pression mémoire et activité OOM.
  3. Journaux du noyau pour les montages, seccomp, AppArmor/SELinux, et les échecs de cgroup.

Second : obtenez l’erreur de la bonne source

  1. docker events autour du moment de la panne.
  2. logs de dockerd (journal systemd) pour des traces complètes de la pile.
  3. logs de containerd pour les erreurs d’invocation shim/runc.

Troisième : isolez l’axe en échec

  1. Axe stockage : échecs de montage overlay2, métadonnées de couche corrompues, XFS d_type, bizarreries NFS/shiftfs.
  2. Axe cgroup : cgroup v2, incompatibilité du driver systemd, problèmes de permissions en mode rootless.
  3. Axe sécurité : blocages AppArmor/SELinux/seccomp, no-new-privileges, capacités manquantes.
  4. Axe runtime : binaire runc incompatible, configuration plugin containerd, shims bloqués.

Une fois que vous connaissez l’axe, arrêtez de multiplier les changements. Faites une chose délibérée, validez, puis avancez. C’est ainsi que vous raccourcissez les interruptions et rendez les postmortems ennuyants.

Faits et contexte intéressants (pourquoi cette pile est étrange)

  • Docker a extrait containerd en 2016 pour rendre les composants runtime réutilisables au-delà de Docker ; Kubernetes s’est ensuite standardisé sur containerd pour de nombreuses distributions.
  • runc provient de libcontainer, le moteur de conteneurs initial de Docker, et est devenu l’implémentation de référence du runtime OCI.
  • OCI (Open Container Initiative) s’est formée en 2015 pour standardiser le format d’image et le comportement runtime ; c’est pourquoi « OCI runtime » apparaît dans les erreurs d’aujourd’hui.
  • Le « shim » existe pour que les conteneurs survivent aux redémarrages du démon ; containerd peut mourir et revenir sans tuer chaque conteneur, parce que le shim maintient la relation de processus enfant.
  • overlay2 est devenu le driver de stockage Linux par défaut car il est rapide et utilise OverlayFS du noyau, mais il est exigeant sur les fonctionnalités du système de fichiers et les options de montage.
  • cgroup v2 a changé les sémantiques (surtout autour de la délégation et des controllers), et beaucoup d’hypothèses « qui marchaient sur v1 » échouent silencieusement ou bruyamment sur v2.
  • Rootless Docker n’est pas juste « Docker sans sudo » ; il utilise des user namespaces et un réseau différent, et il échoue différemment (souvent plus poliment) que le mode rootful.
  • Les valeurs par défaut de seccomp sont conservatrices ; elles peuvent casser des syscalls « exotiques » utilisés par certaines charges (ou par un glibc plus récent) sur des noyaux plus anciens.
  • XFS nécessite ftype=1 (d_type) pour la correction d’overlay2 ; sans cela, vous obtenez des échecs de couche et de renommage particulièrement déroutants.

Blague 1/2 : Les conteneurs sont « légers » jusqu’à ce que vous les déboguiez à 3 h du matin, quand chaque espace de noms pèse exactement une tonne.

Tâches pratiques de débogage (commandes, sorties, décisions)

Ci‑dessous des tâches réelles et exécutables. Chacune a trois parties : la commande, ce que signifie une sortie typique, et la décision que vous prenez. Faites-les dans l’ordre quand vous êtes perdu ; faites-les sélectivement quand vous ne l’êtes pas.

Tâche 1 : Capturer l’erreur exacte depuis Docker (pas le résumé)

cr0x@server:~$ docker ps -a --no-trunc | head -n 5
CONTAINER ID                                                       IMAGE                               COMMAND                  CREATED         STATUS                     PORTS     NAMES
2b2c0f3b7a0c5f8f1c2c6d5a0a7a5f0a6e9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4   myapp:latest                       "/entrypoint.sh"         2 minutes ago   Created                              myapp_1
cr0x@server:~$ docker start myapp_1
Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown

Sens : C’est déjà plus profond que « échec de démarrage ». Cela pointe vers runc et une opération spécifique. /proc + permission denied signifie souvent LSM (AppArmor/SELinux), seccomp, ou une restriction de montage/espace de noms étrange.

Décision : Ne touchez pas aux paquets Docker. Allez directement dans journald et les logs du noyau pour voir qui a dit « non ».

Tâche 2 : Surveiller les événements Docker en direct lors de la reproduction

cr0x@server:~$ docker events --since 10m
2026-01-03T11:12:08.123456789Z container create 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.234567890Z container start 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.345678901Z container die 2b2c0f3b7a0c (exitCode=127, image=myapp:latest, name=myapp_1)

Sens : Le conteneur meurt immédiatement. Les codes de sortie sont parfois au niveau de l’application, parfois au niveau du runtime ; le timing est important. Une mort instantanée signifie souvent que init n’a jamais démarré ou que l’entrée n’existe pas.

Décision : Si l’événement die est immédiat et que le code de sortie ressemble à 127/126, vérifiez le rootfs et l’entrypoint ; si c’est une erreur runtime, consultez les logs.

Tâche 3 : Inspecter rapidement la configuration du conteneur (entrypoint, montages, options de sécurité)

cr0x@server:~$ docker inspect myapp_1 --format '{{json .HostConfig.SecurityOpt}} {{json .HostConfig.CgroupnsMode}} {{.Path}} {{json .Args}}'
null "private" /entrypoint.sh ["--serve"]

Sens : Pas d’options de sécurité spéciales, cgroup namespace privé, entrypoint /entrypoint.sh. Si ça échoue avec 127, ce chemin pourrait ne pas exister ou ne pas être exécutable dans l’image.

Décision : Si l’erreur sent le « binaire manquant », lancez l’image avec un shell connu ou ls via un entrypoint différent (si possible). Si l’erreur sent le « permission denied », pivotez vers LSM/seccomp/cgroups.

Tâche 4 : Lire les logs de dockerd pour la trace complète

cr0x@server:~$ sudo journalctl -u docker --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330123456Z" level=error msg="Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330234567Z" level=info msg="Attempting next endpoint for containerd"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330345678Z" level=error msg="Handler for POST /v1.45/containers/2b2c0f3b7a0c/start returned error: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"

Sens : Docker relaie une erreur de runtime OCI. La trace n’est pas toujours affichée sauf si le logging debug est activé, mais ceci confirme que ce n’est pas « Docker ne peut pas parler à containerd ».

Décision : Prochaine étape : logs de containerd et logs kernel/audit. Les refus de permission sont de la politique, pas de la plomberie.

Tâche 5 : Lire les logs de containerd (contexte shim et runc)

cr0x@server:~$ sudo journalctl -u containerd --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.320111222Z" level=info msg="starting containerd" revision= version=1.7.12
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.328222333Z" level=error msg="RunPodSandbox for "2b2c0f3b7a0c" failed" error="failed to create task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"

Sens : containerd confirme l’échec de runc. Si vous voyez « shim exited » sans détails runc, vous pouvez avoir un binaire runtime planté ou un répertoire d’état corrompu.

Décision : Si l’erreur de containerd est mince, activez temporairement le logging debug (tâche plus tard). Sinon, pivotez vers les logs du noyau pour trouver qui a refusé l’accès.

Tâche 6 : Vérifier les logs du noyau pour les refus AppArmor/SELinux/seccomp

cr0x@server:~$ sudo dmesg -T | tail -n 25
[Fri Jan  3 11:12:08 2026] audit: type=1400 audit(1735902728.332:312): apparmor="DENIED" operation="open" class="file" profile="docker-default" name="/proc/self/fd/" pid=22451 comm="runc:[2:INIT]" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

Sens : Voilà la preuve irréfutable : AppArmor a bloqué un open sur /proc/self/fd/ pour le profil docker-default.

Décision : Corrigez la politique AppArmor ou exécutez le conteneur avec un ajustement de profil approprié (prudemment). Ne désactivez pas AppArmor globalement, sauf si vous aimez devoir vous justifier ensuite.

Tâche 7 : Confirmer l’état d’AppArmor et les profils chargés

cr0x@server:~$ sudo aa-status | head -n 20
apparmor module is loaded.
73 profiles are loaded.
67 profiles are in enforce mode.
   docker-default
   /usr/sbin/cupsd
   /usr/sbin/sshd
6 profiles are in complain mode.
0 processes are unconfined but have a profile defined.

Sens : AppArmor est actif et en mode enforcement. Si docker-default est en enforcement, votre conteneur est probablement sous ce profil sauf override.

Décision : Si ce comportement est nouveau, trouvez ce qui a changé : mise à jour du noyau, mise à jour d’AppArmor, ou quelqu’un a modifié les profils. Pour une mitigation rapide, utilisez --security-opt apparmor=unconfined pour la charge affectée (puis écrivez une vraie politique).

Tâche 8 : Vérifier l’espace disque et les inodes (oui, à chaque fois)

cr0x@server:~$ df -hT / /var/lib/docker
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4    96G   95G  120M 100% /
/dev/nvme0n1p2 ext4    96G   95G  120M 100% /
cr0x@server:~$ df -i /var/lib/docker
Filesystem     Inodes  IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2 6553600 6551000   2600  100% /

Sens : Vous êtes à court d’espace et d’inodes. L’un ou l’autre peut casser la création de conteneurs, les pulls d’images et les montages overlay. Quand Docker échoue avec des « no space left on device », cela signifie parfois « plus d’inodes », ce qui est particulièrement cruel.

Décision : Libérez de l’espace/inodes d’abord. Prunez en sécurité, supprimez de vieux logs, faites pivoter les journaux, ou déplacez DockerRootDir. Ne perdez pas de temps sur des flags runc pendant que le disque est saturé.

Tâche 9 : Identifier le driver de stockage et le répertoire racine Docker

cr0x@server:~$ docker info --format 'Driver={{.Driver}} DockerRootDir={{.DockerRootDir}} CgroupDriver={{.CgroupDriver}} CgroupVersion={{.CgroupVersion}}'
Driver=overlay2 DockerRootDir=/var/lib/docker CgroupDriver=systemd CgroupVersion=2

Sens : overlay2 sur cgroup v2 avec driver systemd. Cette combinaison est correcte, mais elle restreint vos modes d’échec : problèmes de montage overlay, délégation/permissions cgroup, limites unit systemd.

Décision : Si l’erreur implique des montages/couches, concentrez-vous sur overlay2 et le FS sous-jacent. Si elle implique « cgroup » ou « systemd », concentrez-vous là-dessus.

Tâche 10 : Vérifier les fonctionnalités du backing filesystem pour overlay2 (XFS ftype, options de montage)

cr0x@server:~$ findmnt -no FSTYPE,OPTIONS /var/lib/docker
ext4 rw,relatime,errors=remount-ro
cr0x@server:~$ sudo docker info | sed -n '/Backing Filesystem/,+5p'
 Backing Filesystem: extfs
 Supports d_type: true
 Using metacopy: false
 Native Overlay Diff: true

Sens : Le FS supporte d_type ; overlay2 devrait être structurellement OK. Sur XFS, vous voudrez ftype=1 ; sur ext4, on se soucie surtout d’être sur un FS local avec des options de montage adéquates.

Décision : Si le backing FS est NFS, CIFS, ou quelque chose de « créatif », attendez-vous à des problèmes avec overlay2. Déplacez DockerRootDir sur un stockage local ou utilisez un driver compatible.

Tâche 11 : Chercher des échecs de montage overlay et des « invalid argument » dans les logs du noyau

cr0x@server:~$ sudo dmesg -T | grep -E 'overlay|OverlayFS' | tail -n 10
[Fri Jan  3 10:58:41 2026] overlayfs: upper fs does not support RENAME_WHITEOUT
[Fri Jan  3 10:58:41 2026] overlayfs: failed to set xattr on upper

Sens : OverlayFS n’est pas content des capacités du filesystem upper. Cela peut arriver avec certaines options de montage, des noyaux anciens, ou des backing filesystems qui ne supportent pas les xattrs/fonctionnalités requises.

Décision : Corrigez le filesystem (options de montage, support noyau) ou déplacez DockerRootDir. Essayer de « pruner les images » ne réparera pas un FS incapable d’exécuter les opérations requises par overlay.

Tâche 12 : Valider le montage cgroup v2 et la disponibilité des controllers

cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cr0x@server:~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma

Sens : cgroup v2 est monté et les controllers existent. Les problèmes apparaissent lorsque des controllers ne sont pas délégués ou que des restrictions systemd les bloquent pour Docker.

Décision : Si des erreurs de démarrage mentionnent cgroups, confirmez que le driver cgroup de Docker correspond à systemd et que la version de systemd supporte les fonctionnalités nécessaires.

Tâche 13 : Trouver les kills OOM qui arrêtent « mystérieusement » des conteneurs

cr0x@server:~$ sudo journalctl -k --since "2 hours ago" --no-pager | grep -i -E 'oom|killed process' | tail -n 10
Jan 03 10:44:19 server kernel: Out of memory: Killed process 21902 (myapp) total-vm:812340kB, anon-rss:512000kB, file-rss:1200kB, shmem-rss:0kB, UID:0 pgtables:1400kB oom_score_adj:0

Sens : Le noyau a tué votre processus. Docker peut signaler que le conteneur « s’est arrêté » sans erreur runtime utile. Ce n’est pas un problème containerd. C’est l’hôte qui fait le tri.

Décision : Corrigez la pression mémoire : augmentez les limites, ajustez requests/limits, ajoutez du swap (prudemment), ou déplacez la charge. Déboguer runc ne ressuscitera pas la mémoire.

Tâche 14 : Vérifier les shims bloqués et les processus runtime zombies

cr0x@server:~$ ps -eo pid,ppid,comm,args | grep -E 'containerd-shim|runc' | grep -v grep | head
22451  1044 containerd-shim-runc-v2 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 2b2c0f3b7a0c -address /run/containerd/containerd.sock
22460 22451 runc:[2:INIT] runc:[2:INIT]

Sens : Des shims existent par conteneur. Si des shims restent après la disparition des conteneurs, ou si des processus INIT runc se bloquent, vous pouvez avoir un montage coincé ou un bug noyau. Souvent c’est un blocage de stockage ou un sommeil non interrompable.

Décision : Ne tuez pas des PID au hasard en premier. Identifiez s’ils sont en état D (tâche suivante). S’ils le sont, vous traitez un problème d’E/S ou de filesystem.

Tâche 15 : Vérifier l’état des processus pour D-state (blocage I/O) et tâches bloquées

cr0x@server:~$ ps -o pid,state,wchan,comm -p 22460
  PID S WCHAN  COMMAND
22460 D ovl_wa runc:[2:INIT]
cr0x@server:~$ sudo dmesg -T | tail -n 5
[Fri Jan  3 11:05:12 2026] INFO: task runc:[2:INIT]:22460 blocked for more than 120 seconds.

Sens : L’état D signifie sommeil non interrompable, généralement en attente d’E/S. Le tuer ne fonctionnera pas. C’est là que « Docker est mort » signifie en réalité « votre stockage est en feu ».

Décision : Arrêtez d’essayer de redémarrer Docker. Enquêtez sur le stockage : disque sous-jacent, RAID, stockage réseau, erreurs de filesystem. Vous pourriez devoir rebooter l’hôte si le noyau est bloqué, mais comprenez pourquoi avant d’appuyer sur ce levier.

Tâche 16 : Valider la santé du socket containerd et la réactivité de l’API

cr0x@server:~$ sudo ss -lxnp | grep containerd
u_str LISTEN 0      4096   /run/containerd/containerd.sock 12345              * 0    users:(("containerd",pid=1044,fd=9))
cr0x@server:~$ sudo ctr --address /run/containerd/containerd.sock version
Client:
  Version:  1.7.12
  Revision: 9a8b7c6d5e4f
Server:
  Version:  1.7.12
  Revision: 9a8b7c6d5e4f

Sens : containerd écoute et répond. Si Docker prétend qu’il ne peut pas se connecter à containerd, ceci aide à séparer « configuration du démon » de « runtime mort ».

Décision : Si ctr fonctionne mais docker non, concentrez-vous sur la configuration de dockerd, les sockets, ou les permissions. Si ctr plante, containerd est malade ou bloqué (souvent à cause du stockage).

Tâche 17 : Utiliser ctr pour lister les tasks et repérer les conteneurs à moitié créés

cr0x@server:~$ sudo ctr -n moby containers list | head
CONTAINER    IMAGE          RUNTIME
2b2c0f3b7a0c  docker.io/library/myapp:latest  io.containerd.runc.v2
cr0x@server:~$ sudo ctr -n moby tasks list
TASK        PID     STATUS
2b2c0f3b7a0c 0       STOPPED

Sens : L’objet container existe, mais la task n’est pas en cours (PID 0). Cela correspond à « create failed » ou « init failed ».

Décision : Si beaucoup de tasks STOPPED s’accumulent, vous avez probablement un problème systémique de runtime/stockage/sécurité. Corrigez l’axe, puis nettoyez l’état en attente.

Tâche 18 : Vérifier l’utilisation du stockage de Docker et faire un prune réfléchi

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        6         38.2GB    31.7GB (83%)
Containers      19        2         1.1GB     900MB (81%)
Local Volumes   15        8         120GB     0B (0%)
Build Cache     10        0         8.4GB     8.4GB
cr0x@server:~$ docker image prune -a --filter "until=168h"
Deleted Images:
deleted: sha256:...
Total reclaimed space: 28.4GB

Sens : Images et cache de build sont récupérables ; les volumes ne le sont pas. La sortie vous indique où vous pouvez récupérer de l’espace en toute sécurité sans supprimer les données clients.

Décision : Prunez images/cache dans un cadre contrôlé. Évitez de pruner les volumes sauf si vous êtes absolument certain. Si vous êtes à court d’inodes, le pruning peut aussi aider, mais vérifiez avec df -i.

Tâche 19 : Vérifier le mode iptables/nftables quand des erreurs réseau se déguisent en pannes runtime

cr0x@server:~$ sudo docker run --rm busybox:latest nslookup example.com
Server:    127.0.0.11
Address 1: 127.0.0.11

Name:      example.com
Address 1: 93.184.216.34
cr0x@server:~$ sudo iptables -S | head -n 5
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-USER

Sens : La résolution DNS dans le conteneur fonctionne ; les chaînes iptables existent. Si les conteneurs ne peuvent rien atteindre et que vous voyez une politique FORWARD DROP sans règles Docker appropriées, vous pouvez obtenir des symptômes ressemblant à « démarrage du conteneur échoué » parce que les apps échouent immédiatement aux checks de santé.

Décision : Si le runtime réussit mais que les applications meurent immédiatement, validez le réseau et la résolution DNS. Toutes les « morts de conteneur » ne sont pas la faute de runc.

Tâche 20 : Augmenter temporairement la verbosité de dockerd (en sécurité) pour obtenir plus de contexte

cr0x@server:~$ sudo mkdir -p /etc/docker
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
  "log-level": "info"
}
cr0x@server:~$ sudo sh -c 'cat > /etc/docker/daemon.json <

Sens : Les logs debug montrent où dockerd passe du temps et ce qu’il a demandé à containerd. C’est particulièrement utile pour les blocages (vous verrez la dernière étape réussie).

Décision : Utilisez le mode debug brièvement pendant la réponse à incident, puis repassez en info. Les logs debug sont verbeux et peuvent consommer du disque—le même disque que vous venez probablement de découvrir saturé.

Blague 2/2 : « Redémarrez simplement Docker » est l’équivalent opérationnel de frapper un distributeur automatique — parfois efficace, toujours suspect.

Trois mini-récits d’entreprise issus du terrain

1) Incident causé par une mauvaise hypothèse : « permission denied signifie permissions de fichier »

Ils avaient une flotte stable. De petites mises à jour OS étaient déployées chaque semaine. Un mardi, un déploiement sur un sous-ensemble d’hôtes a commencé à échouer avec OCI runtime create failed: permission denied. L’hypothèse immédiate : un fichier dans l’image avait perdu son bit exécutable. L’équipe a reconstruit l’image, l’a déployée, et… rien n’a changé. Même erreur, mêmes hôtes, différents digests d’image.

Quelqu’un a alors essayé la solution classique : réinstaller Docker. Ça a « fonctionné » sur un hôte, ce qui a suffi à transformer le correctif en superstition. Sur l’hôte suivant, ça n’a pas fonctionné. Ils se sont retrouvés avec de l’incohérence et du downtime, le pire des deux mondes.

La percée est venue d’un SRE fatigué qui a cessé de regarder Docker et a commencé à consulter les logs d’audit du noyau. Des refus AppArmor. La mise à jour OS avait introduit un comportement plus strict du profil par défaut pour docker-default sur cette build de distro, et une charge ouvrait un chemin /proc pendant l’init qui déclenchait le deny.

La correction fut ennuyeuse et spécifique : définir un override de profil AppArmor par conteneur pour la charge affectée, puis travailler avec la sécurité pour élaborer un changement de profil minimal. Pas de désactivation globale. Pas de réinstallation. L’hôte où la « réinstallation avait marché une fois » ? Il avait un cache de politique obsolète qui ne s’était pas rechargé immédiatement, c’est ainsi que naissent les mythes en production.

Conclusion : « permission denied » est une catégorie, pas un diagnostic. Si vous ne vérifiez pas les logs audit/LSM, vous déboguez les yeux bandés avec une lampe dont les piles sont mortes.

2) Optimisation qui s’est retournée contre eux : déplacer DockerRootDir sur du « stockage partagé rapide »

Une équipe plateforme voulait accélérer le remplacement de nœuds. Ils ont décidé de placer /var/lib/docker sur un montage de stockage partagé pour que les hôtes puissent être reprovisionnés sans repuller les images. Sur le papier : moins de pulls, montée en charge plus rapide, moins de bande passante. En réalité : ils venaient d’attacher un filesystem sensible à la latence pour un overlay filesystem réseau avec des pauses occasionnelles.

Au début, tout semblait bien. Les conteneurs démarraient. Les pulls étaient rapides. Puis un événement réseau de stockage a provoqué des pauses de 2–5 secondes. OverlayFS n’a pas pris ça gentiment. Les init runc ont commencé à se bloquer en état D. Les shims restaient comme des fantômes. Les redémarrages de Docker n’ont pas aidé parce que les threads noyau étaient bloqués sur l’E/S.

L’incident a escaladé parce que les symptômes étaient trompeurs. Des ingénieurs ont vu des logs containerd parlant de shims quittant et ont supposé des bugs runtime. D’autres ont vu des « context deadline exceeded » et ont supposé un problème réseau. La vérité : le backend stockage faisait parfois des pauses, et les opérations upperdir d’overlay (rename/whiteout/xattr) amplifiaient les dégâts.

Ils sont revenus à un SSD local pour DockerRootDir. Ils ont conservé le stockage partagé, mais seulement pour des volumes explicites où le pattern d’E/S et le domaine de défaillance étaient compris. Le remplacement de nœud était un peu plus lent. Les incidents ont raréfié. Tout le monde a mieux dormi.

Conclusion : mettre le store de couches Docker sur du stockage partagé est une optimisation qui devient souvent une taxe de fiabilité. Si vous devez le faire, testez les sémantiques d’OverlayFS et le comportement en cas de pauses, pas seulement le débit.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : garder un runbook « santé de l’hôte »

Une fintech exécutait des jobs batch critiques dans des conteneurs sur un petit cluster. Un vendredi, des conteneurs ont commencé à échouer avec failed to mount overlay: no space left on device. Les gens ont supposé que le disque était plein. Il ne l’était pas—du moins pas selon df -h. Encore des gigaoctets libres.

Mais l’astreint avait un runbook qui commençait par deux commandes : df -h et df -i. Les inodes étaient à 100 %. Le coupable était des millions de petits fichiers générés par un sidecar de logs qui écrivait sur un chemin hôte (oui, vraiment). Docker ne pouvait pas créer de nouvelle métadonnée de couche car l’allocation d’inodes échouait.

Ils ont arrêté le processus fautif, nettoyé le répertoire, et les conteneurs ont redémarré immédiatement. Pas de réinstallations. Pas de déplacement de data root. Pas de tuning noyau désespéré. Ils ont ensuite ajouté la surveillance des inodes et l’obligation de rotation des logs, et le problème n’est jamais revenu sous cette forme.

Conclusion : les vérifications basiques de l’hôte ne sont pas en dessous de vous. Elles font la différence entre « réparation en cinq minutes » et « chasse aux fantômes de deux heures ».

Erreurs courantes : symptôme → cause racine → correction

1) Symptôme : OCI runtime create failed: permission denied

Cause racine : Souvent un refus AppArmor/SELinux, ou un syscall bloqué par seccomp, pas les permissions du fichier.

Correction : Vérifiez dmesg -T et les logs d’audit. Ajustez les options de sécurité par conteneur ou la politique. Évitez de désactiver l’LSM globalement.

2) Symptôme : no space left on device mais df -h affiche des Go libres

Cause racine : Inodes épuisés, ou le chemin des données Docker est sur un autre filesystem que celui que vous avez vérifié.

Correction : df -i /var/lib/docker. Prunez images/cache, nettoyez les répertoires de logs, et ajoutez la surveillance des inodes.

3) Symptôme : failed to mount overlay: invalid argument

Cause racine : Filesystem backing non supporté (ex. XFS sans ftype=1, NFS/CIFS), incompatibilité kernel/OverlayFS.

Correction : Déplacez DockerRootDir sur un filesystem local supporté. Sur XFS, assurez-vous ftype=1. Évitez les montages « créatifs » pour le store de couches.

4) Symptôme : démarrage du conteneur bloqué ; commandes docker expirent

Cause racine : I/O bloqué (état D), pauses de stockage, ou erreurs de filesystem provoquant le blocage d’opérations overlay.

Correction : Vérifiez l’état des processus (ps ... state), les tâches bloquées dans dmesg, la santé du stockage. Redémarrer Docker ne décoincera pas le noyau.

5) Symptôme : containerd: failed to create task et shim exited unexpectedly

Cause racine : plantage de runc, binaire runtime incompatible, répertoires d’état corrompus, ou refus sous-jacent (LSM/cgroup).

Correction : Lisez les logs containerd et les logs noyau. Confirmez les versions du runtime ; ne supprimez pas d’état au hasard à moins de savoir ce que vous effacez.

6) Symptôme : les conteneurs quittent instantanément après le démarrage, pas d’erreur runtime

Cause racine : L’application échoue vite (config manquante, échec DNS, incapacité à se connecter), ou OOM kill.

Correction : Vérifiez les logs du conteneur, le comportement des healthchecks, et les logs OOM du noyau. Ne poursuivez pas runc quand le noyau a simplement tué votre appli.

7) Symptôme : le démon Docker ne démarre plus après une mise à jour

Cause racine : daemon.json corrompu, configuration de driver de stockage incompatible, flags résiduels d’anciennes versions.

Correction : Validez le JSON, consultez journalctl -u docker, revenez temporairement à une config minimale, puis réintroduisez les paramètres un par un.

8) Symptôme : les conteneurs rootless échouent avec des erreurs cgroup

Cause racine : délégation cgroup non configurée, contraintes de session user systemd, controllers manquants pour le slice utilisateur.

Correction : Vérifiez la délégation cgroup v2 et les prérequis rootless. Rootless n’est pas un remplacement transparent ; traitez-le comme un runtime différent.

Listes de contrôle / plan étape par étape (ne pas agiter)

Plan pas-à-pas de triage lorsqu’un conteneur ne démarre pas

  1. Capturez l’erreur exacte avec docker start et docker ps -a --no-trunc. Collez-la dans un endroit durable.
  2. Vérifiez la santé de l’hôte :
    • df -hT / /var/lib/docker et df -i /var/lib/docker
    • free -m et logs OOM du noyau
    • dmesg -T | tail pour des refus évidents ou des erreurs de filesystem
  3. Récupérez les logs depuis la source :
    • journalctl -u docker --since ...
    • journalctl -u containerd --since ...
    • journalctl -k --since ...
  4. Choisissez l’axe selon les preuves :
    • Permission denied + logs d’audit → axe sécurité
    • Erreurs de montage overlay + dmesg overlayfs → axe stockage
    • Erreurs cgroup + indices cgroup v2 → axe cgroup
    • Bouclages + processus en état D → axe stockage/E/S
  5. Faites un seul changement, puis retestez immédiatement. Pas de modifications « au cas où » groupées.
  6. Rétablissez la verbosité si vous l’avez activée. Le debug est une lampe-torche temporaire, pas votre nouveau design d’éclairage.

Checklist pour « faut-il redémarrer docker/containerd ? »

  • Oui si : les démons sont bloqués mais l’hôte est sain, pas d’état D, pas d’erreurs filesystem, et vous pouvez tolérer une courte interruption.
  • Non si : le noyau montre des tâches bloquées, des opérations overlay pendent, des erreurs de stockage sont présentes, ou le système de fichiers racine est plein. Réparez l’hôte d’abord.
  • Parfois si : des shims bloqués existent pour des conteneurs morts. Un arrêt propre et un redémarrage peut nettoyer l’état, mais seulement après avoir confirmé que l’E/S est saine.

Checklist d’hygiène du stockage (préviens la moitié des incidents)

  • Surveillez les inodes, pas seulement les octets.
  • Gardez DockerRootDir sur un stockage local avec des fonctionnalités de filesystem connues et fiables.
  • Pivotez les journaux et logs applicatifs ; ne laissez pas /var/log devenir une décharge.
  • Prunez les images/cache de build selon un calendrier adapté à votre cadence de déploiement (avec garde-fous).
  • Surveillez les messages noyau pour les avertissements overlayfs ; ils apparaissent souvent avant la panne totale.

FAQ

1) Dois‑je réinstaller Docker pour corriger des erreurs containerd/runc ?

Rarement. La réinstallation peut masquer le symptôme en réinitialisant config/état, mais elle ne corrige pas une exhaustion disque, des refus LSM, la délégation cgroup, ou un filesystem incapable d’effectuer des opérations overlay. Prouvez l’axe d’abord.

2) Comment savoir si c’est Docker, containerd, ou runc ?

Utilisez les logs. Docker dira souvent « OCI runtime create failed » (c’est runc). Les logs de containerd mentionneront la création de shim/task. Les logs kernel/audit vous diront si le noyau a refusé une opération (mount, open, syscall, cgroup).

3) Quelle est la méthode la plus rapide pour déboguer un « permission denied » ?

Cherchez des refus LSM : dmesg -T et les logs d’audit. Si vous voyez des messages AppArmor/SELinux, vous avez votre réponse. Sinon, considérez les permissions du filesystem et seccomp.

4) Que signifie réellement « shim exited unexpectedly » ?

Cela signifie que containerd a démarré un processus shim pour gérer la task du conteneur, et qu’il est mort. Causes possibles : erreurs runc, répertoires d’état corrompus, ou défaillances noyau sous-jacentes. C’est un symptôme, pas une cause racine.

5) Pourquoi les problèmes overlay2 ressemblent à des pannes runtime aléatoires ?

Parce que overlay2 est en dessous de tout. Si les montages overlay échouent, runc ne peut pas créer un rootfs ; si les opérations overlay bloquent sur l’E/S, les processus se figent ; si les xattrs/semantiques de renommage ne sont pas supportées, vous obtenez des « invalid argument » à des endroits qui ne mentionnent pas le stockage.

6) Comment déboguer sans casser les conteneurs en cours d’exécution ?

Privilégiez d’abord les actions en lecture seule : logs, docker info, ctr version, df, dmesg. Évitez les redémarrages de démon tant que vous n’avez pas évalué si le problème est localisé ou systémique. Si le stockage est bloqué, les redémarrages peuvent empirer les choses.

7) Si j’active le logging debug, quels sont les risques ?

Usage disque et bruit. Les logs debug peuvent croître rapidement durant une boucle d’échec. Si vous êtes déjà proche des limites disque/inodes, le debug peut être l’insulte finale. Activez-le brièvement, capturez ce dont vous avez besoin, puis désactivez-le.

8) Pourquoi cgroup v2 provoque des échecs de démarrage de conteneur ?

Parce que la délégation et l’activation des controllers sont plus strictes. Certaines configurations supposent la disposition cgroup v1 ou dépendent de controllers non disponibles pour Docker/systemd slices. Des drivers cgroup mismatches (systemd vs cgroupfs) peuvent aussi poser problème.

9) Le Docker rootless est‑il plus difficile à déboguer ?

Différent, pas forcément plus difficile. Les erreurs pointent souvent clairement vers user namespace, délégation cgroup, ou contraintes réseau. Mais vous devez le déboguer avec les hypothèses rootless : chemins différents, permissions différentes, limites différentes.

10) Quel indicateur unique ajouteriez‑vous si vous voyez souvent des erreurs runtime ?

L’utilisation des inodes sur le filesystem contenant DockerRootDir, plus les compteurs « tâches bloquées » du noyau ou des alertes sur des avertissements overlayfs répétés. Surveiller uniquement les octets, c’est la meilleure façon d’être surpris.

Conclusion : prochaines étapes vraiment utiles

Quand Docker/containerd/runc commence à émettre des erreurs, votre travail n’est pas de trouver une commande magique. Votre travail est d’identifier l’axe en échec — stockage, cgroups, politique de sécurité, ou état du runtime — puis d’appliquer une correction ciblée sans faire sauter le reste du nœud.

Prochaines étapes pratiques :

  1. Adoptez le playbook de diagnostic rapide et conservez‑le dans vos notes d’incident.
  2. Ajoutez la surveillance de l’utilisation des inodes, pas seulement des octets, sur DockerRootDir.
  3. Intégrez les logs kernel/audit dans votre flux standard de débogage de démarrage de conteneur.
  4. Gardez DockerRootDir sur un filesystem local supporté ; traitez le « stockage partagé rapide » pour les stores de couches comme suspect jusqu’à preuve du contraire.
  5. Quand vous changez une politique de sécurité (AppArmor/SELinux/seccomp), faites‑le par charge d’abord. Les désactivations globales transforment des mitigations temporaires en regrets permanents.

Si vous faites tout cela, vous aurez toujours des incidents—la production trouve toujours un moyen. Mais vous cesserez de réinstaller votre runtime comme un rituel, et commencerez à corriger le système réel.

← Précédent
SPF : trop de requêtes DNS — réduire en toute sécurité et réussir les contrôles
Suivant →
ZFS ARC vs cache de pages Linux : qui gagne et pourquoi cela vous concerne

Laisser un commentaire