Docker « No Space Left on Device » : les endroits cachés où Docker vide votre disque

Cet article vous a aidé ?

Vous êtes d’astreinte. Un déploiement échoue. Le CI est rouge. Des conteneurs qui fonctionnaient hier refusent soudainement de démarrer avec no space left on device. Vous faites un SSH, lancez df -h, et votre disque a l’air… plutôt correct. Ou pire : il est plein, et vous ne savez pas ce qui l’a rempli parce que « on ne lance que quelques conteneurs ».

Docker est un grand magicien. Il fait apparaître des applis. Il fait aussi disparaître de l’espace disque—silencieusement, à travers plusieurs couches de stockage, journaux, caches et métadonnées. L’astuce est de savoir où regarder, et quels nettoyages sont sûrs en production.

Méthode de diagnostic rapide

Voici le flux « débloquez-vous en 10 minutes ». Il priorise les vérifications qui vous indiquent si vous avez un problème d’espace disque, un problème d’inodes, ou une contrainte spécifique au système de fichiers (particularités d’overlay, métadonnées thinp, quotas de projet).

Première étape : confirmer ce qui est plein (octets vs inodes vs un montage)

  • Vérifier les octets libres : df -h sur le montage concerné (/, /var, /var/lib/docker et tout disque de données Docker dédié).
  • Vérifier les inodes : df -i. Si les inodes sont à 100 %, vous pouvez avoir « no space » avec des gigaoctets libres.
  • Confirmer le root Docker : docker infoDocker Root Dir. Les gens vérifient / et oublient que Docker est sur /var (ou l’inverse).

Deuxième étape : identifier quelle catégorie croît

  • Comptabilité Docker : docker system df -v pour voir images, conteneurs, volumes et cache de build.
  • Réalité du système de fichiers : du -xhd1 /var/lib/docker (ou votre répertoire racine) pour voir où résident réellement les octets. Les chiffres de Docker peuvent être en retard, surtout pour les journaux.
  • Journaux : vérifiez les journaux JSON des conteneurs ou l’utilisation de journald. Les journaux sont le premier mangeur d’espace auquel on ne pense pas.

Troisième étape : remédier dans l’ordre le moins destructeur

  1. Arrêter l’hémorragie : faire tourner les journaux, limiter les drivers de logs, ou brider les applis bavardes.
  2. Libérer de l’espace sûr : purger le cache de build et les images pendantes. Évitez de tout supprimer dans les volumes à moins d’en être certain.
  3. Traiter les problèmes structurels : déplacer le Docker root vers un disque plus grand, ajouter de la surveillance, mettre des quotas, définir la rétention des logs et corriger la prolifération des builders CI.

Blague #1 : Le disque, c’est comme le minibar d’un hôtel—personne ne se rappelle l’avoir utilisé jusqu’au checkout.

Ce que « no space left on device » signifie réellement

Le message est mensonger par omission. Il peut signifier :

  • Pas de blocs libres sur le système de fichiers qui alimente la couche inscriptible de Docker, un volume ou un répertoire temporaire.
  • Pas d’inodes libres (vous ne pouvez pas créer de nouveaux fichiers même si vous avez de l’espace).
  • Quota atteint (quotas de projet, quotas XFS ou limites de métadonnées du driver de stockage).
  • Méta-données du thin pool pleines (fréquent avec d’anciens setups devicemapper).
  • Un autre montage est plein que celui que vous avez vérifié (par ex. /var est plein alors que / ne l’est pas).
  • Contraintes du système de fichiers overlay qui se manifestent par des erreurs d’espace (par ex. trop de couches, ou le comportement copy-up qui fait exploser l’utilisation).

Opérationnellement : traitez cela comme « le kernel a refusé une allocation ». Votre travail est de découvrir quelle allocation et .

Une citation à garder sur un post-it dans la salle serveur :

« L’espoir n’est pas une stratégie. » — idée paraphrasée souvent attribuée au leadership des opérations dans les cercles de fiabilité

Si votre stratégie de gestion des disques est « on prune quand ça fait mal », vous fonctionnez déjà à l’espoir.

Les endroits cachés où Docker dévore votre disque

1) Couches inscriptibles des conteneurs (overlay2) : mort par petits écritures

Chaque conteneur en cours d’exécution a une couche inscriptible. Sous overlay2, c’est une arborescence sous quelque chose comme :

  • /var/lib/docker/overlay2 (commun)
  • /var/lib/docker/containers (journaux et config)

Les couches inscriptibles gonflent quand les applications écrivent dans des chemins que vous pensiez éphémères ou externalisés. Les classiques :

  • Des applis écrivant dans /tmp à l’intérieur du conteneur, et vous supposiez que c’était en mémoire. Ce n’est pas le cas, sauf si vous montez tmpfs.
  • Des bases de données écrivant dans /var/lib/postgresql/data à l’intérieur du conteneur sans volume nommé.
  • Des gestionnaires de paquets, environnements de langage, ou vérifications « utiles » qui écrivent des caches dans /root, /var/cache, /home.

Le copy-up d’overlay est une trahison spéciale : lire un fichier d’une couche inférieure est bon marché ; le modifier force une copie dans la couche inscriptible. Toucher un gros fichier peut le dupliquer. C’est ainsi que « on n’a changé qu’une petite config » devient des gigaoctets.

2) Fichiers journaux JSON : le plus bruyant gagne l’espace disque

Le logging Docker par défaut (json-file) écrit les journaux par conteneur dans :

  • /var/lib/docker/containers/<container-id>/<container-id>-json.log

Si vous ne définissez pas de rotation des journaux, ces fichiers grossissent indéfiniment. Et « indéfiniment » en production se mesure en incidents.

Blague #2 : La seule chose qui s’auto-scale sans approbation budgétaire, c’est le volume de vos logs.

3) Volumes nommés : durables par conception, oubliés par habitude

Les volumes sont l’endroit où l’état vient vivre. Ils survivent aussi à la suppression des conteneurs. C’est le but, et aussi le piège.

La prolifération de volumes arrive quand vous :

  • Utilisez des noms de volumes générés automatiquement dans Compose ou CI et ne les nettoyez jamais.
  • Lancez des stacks de test éphémères qui créent des volumes à chaque exécution.
  • Faites des bind-mounts incorrects et vous retrouvez à écrire dans un volume nommé non voulu.

Nuance importante : supprimer des images libère rarement l’espace des volumes. Les gens « docker rmi tout » et ont encore le disque plein parce que ce sont les volumes qui occupent l’espace.

4) Cache de build : BuildKit est rapide parce qu’il se souvient de tout

Les builds Docker modernes (surtout avec BuildKit) cachent les couches agressivement. C’est fantastique pour la vitesse du CI. C’est aussi la façon dont vos nœuds builders deviennent des décharges disque.

Le cache de build grossit à cause de :

  • Des builds multi-étapes avec de nombreuses étapes invalidées fréquemment.
  • Beaucoup de branches et tags produisant des couches uniques.
  • Des téléchargements de gestionnaires de paquets mis en cache dans des couches, multipliés par les variations.

5) Images inutilisées : le musée des tags « on pourrait en avoir besoin »

Les images s’accumulent quand les nœuds jouent à la fois runtime et build host, ou quand votre stratégie de déploiement télécharge de nombreuses versions et ne les supprime jamais. Dans les clusters, chaque nœud devient son propre musée privé de couches « potentiellement utiles un jour ».

6) Conteneurs orphelins et couches mortes : restes après crashes et mises à jour

En fonctionnement normal Docker nettoie. En fonctionnement anormal—plantages du daemon, redémarrages forcés, drivers de stockage cassés—des déchets peuvent persister. Aussi : certains patterns d’orchestration créent et abandonnent des conteneurs à un rythme choquant.

7) Répertoires temporaires hors du root Docker : embuscades /tmp et /var/tmp

Docker utilise des fichiers temporaires lors des pulls, builds et décompressions. Selon la configuration et les variables d’environnement, l’usage temporaire peut atterrir dans :

  • /tmp ou /var/tmp sur l’hôte
  • un tmp privé systemd pour le service docker

Vous pouvez donc remplir /tmp et faire planter des services non liés, même si /var/lib/docker est sur un disque séparé.

8) Journald : « mais on n’utilise pas json-file » (si, vous avez quand même des logs)

Si Docker log vers journald, les logs peuvent ne pas être sous le répertoire Docker du tout. Ils s’accumuleront dans le stockage de journald (souvent sous /var/log/journal), régi par les paramètres de rétention de journald. Super, jusqu’à ce que vos defaults de logging soient « tout garder » et vos disques « petits ».

9) Cas limites du driver de stockage : métadonnées devicemapper, quotas de projet XFS, et compagnie

La plupart des installations modernes utilisent overlay2 sur ext4 ou XFS. Mais des environnements plus anciens (ou « soigneusement préservés legacy systems ») peuvent encore utiliser devicemapper. En devicemapper, il est courant d’atteindre les limites de métadonnées avant les limites réelles du disque—donnant un « no space » alors que le disque n’est pas plein.

Les quotas de projet XFS peuvent aussi vous surprendre : Docker peut être configuré pour appliquer des limites par conteneur. Utile si c’est intentionnel, déroutant si hérité d’une AMI non auditée.

Tâches pratiques : commandes, sorties et décisions

Ce sont des commandes réelles et exécutables. Chacune inclut ce que la sortie signifie et la décision que vous en tirez. Utilisez-les dans l’ordre, pas au hasard comme un raton laveur dans une salle serveur.

Task 1: Identify the full filesystem

cr0x@server:~$ df -h
Filesystem                         Size  Used Avail Use% Mounted on
/dev/nvme0n1p2                      80G   62G   14G  82% /
/dev/nvme1n1p1                     200G  196G  4.0G  99% /var/lib/docker
tmpfs                               16G  1.2G   15G   8% /run

Signification : Votre disque de données Docker est plein (/var/lib/docker à 99 %). Le système racine n’est pas le problème principal.

Décision : Concentrez-vous sur l’utilisation du répertoire root de Docker ; ne perdez pas de temps à nettoyer /.

Task 2: Check inode exhaustion (the sneaky “space” error)

cr0x@server:~$ df -i
Filesystem                        Inodes   IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2                   5242880  841120 4401760   17% /
/dev/nvme1n1p1                  13107200 13107200       0  100% /var/lib/docker

Signification : Le système de fichiers Docker est à court d’inodes, pas de blocs. Cela arrive souvent avec des millions de petits fichiers (node_modules, décompression de couches d’images, caches de build).

Décision : Le pruning peut aider, mais à long terme vous devrez probablement recréer le système de fichiers avec une densité d’inodes adaptée (ext4) ou passer à XFS (inodes dynamiques) et réduire le churn de petits fichiers.

Task 3: Confirm Docker’s actual root directory

cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker

Signification : Docker confirme qu’il utilise /var/lib/docker.

Décision : Toutes les analyses disque suivantes doivent cibler ce chemin (sauf si vous utilisez un runtime alternatif ou Docker rootless).

Task 4: Get Docker’s high-level space accounting

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          48        12        72.4GB    41.8GB (57%)
Containers      18        7         3.1GB     2.2GB (71%)
Local Volumes   64        9         88.6GB    55.0GB (62%)
Build Cache     214       0         61.3GB    61.3GB

Signification : Les volumes et le cache de build dominent. Ce n’est pas principalement « trop de conteneurs ».

Décision : Commencez par purger le cache de build (généralement sûr), puis auditez les volumes avant toute suppression.

Task 5: Drill down with verbose Docker accounting

cr0x@server:~$ docker system df -v
Images space usage:
REPOSITORY   TAG      IMAGE ID       CREATED        SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
app/api      prod     2a1b3c4d5e6f   2 days ago     1.21GB    820MB         390MB         4
app/api      old      7f6e5d4c3b2a   3 weeks ago    1.18GB    820MB         360MB         0

Build cache usage:
CACHE ID    CACHE TYPE    SIZE     CREATED         LAST USED      USAGE     SHARED
k9x...      regular       2.3GB    2 weeks ago     2 weeks ago    1
...

Signification : Vous pouvez repérer des tags d’images inutilisés (0 conteneur) et des caches anciens.

Décision : Supprimez d’abord les images et caches inutilisés ; envisagez des politiques pour ne garder que N versions par nœud.

Task 6: Identify the biggest directories under Docker root (reality check)

cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
1.1G    /var/lib/docker/network
3.6G    /var/lib/docker/containers
62G     /var/lib/docker/buildkit
112G    /var/lib/docker/overlay2
181G    /var/lib/docker

Signification : Overlay2 et buildkit sont les gros contributeurs. Le répertoire containers est non négligeable (souvent des journaux).

Décision : Si containers est gros, inspectez les journaux. Si buildkit est gros, purgez le cache de build. Overlay2 requiert un nettoyage soigné via Docker, pas une suppression manuelle.

Task 7: Find top container log files (json-file driver)

cr0x@server:~$ sudo find /var/lib/docker/containers -name "*-json.log" -printf "%s %p\n" | sort -nr | head
21474836480 /var/lib/docker/containers/4c2.../4c2...-json.log
 9876543210 /var/lib/docker/containers/91a.../91a...-json.log
 1234567890 /var/lib/docker/containers/ab7.../ab7...-json.log

Signification : Un conteneur a écrit ~20 Go de logs. Ce n’est pas « un peu bavard ». C’est un avis d’expulsion disque.

Décision : Troncature immédiate de ce log (solution sûre à court terme), puis mise en place de rotation et correction de l’appli bavarde.

Task 8: Safely truncate an oversized container log without restarting Docker

cr0x@server:~$ sudo truncate -s 0 /var/lib/docker/containers/4c2.../4c2...-json.log
cr0x@server:~$ sudo ls -lh /var/lib/docker/containers/4c2.../4c2...-json.log
-rw-r----- 1 root root 0 Jan  2 11:06 /var/lib/docker/containers/4c2.../4c2...-json.log

Signification : Vous avez récupéré de l’espace immédiatement ; le fichier est maintenant vide. Le conteneur continue à journaliser.

Décision : Considérez cela comme un pansement d’urgence. Planifiez la correction : options de logging, choix du driver, ou réduction des logs côté application.

Task 9: Confirm which container maps to the noisy log directory

cr0x@server:~$ docker ps --no-trunc --format 'table {{.ID}}\t{{.Names}}'
CONTAINER ID                                                       NAMES
4c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b   api-prod-1

Signification : Le pire pollueur de logs est api-prod-1.

Décision : Examinez le niveau de logs de l’application, les tempêtes de requêtes, les retries ou les boucles d’erreur. Les problèmes disque sont souvent le symptôme d’une défaillance en amont.

Task 10: Check journald disk usage (if using journald log driver)

cr0x@server:~$ journalctl --disk-usage
Archived and active journals take up 18.7G in the file system.

Signification : Journald consomme un espace significatif. Cela peut être des logs Docker, des logs système, ou les deux.

Décision : Définissez des limites de rétention dans la configuration de journald et vacuumez les anciens journaux. Ne supprimez pas simplement les fichiers sous /var/log/journal pendant que journald tourne.

Task 11: Vacuum journald logs to reclaim space

cr0x@server:~$ sudo journalctl --vacuum-size=2G
Deleted archived journal /var/log/journal/7a1.../system@000...-000...journal
Vacuuming done, freed 16.7G of archived journals on disk.

Signification : De l’espace a été récupéré en toute sécurité via les outils journald.

Décision : Mettez en place une politique journald persistante (caps taille/temps) pour éviter un retour du problème la semaine suivante.

Task 12: Prune build cache (usually low-risk, high reward)

cr0x@server:~$ docker builder prune --all --force
Deleted build cache objects:
k9x...
m2p...
Total reclaimed space: 59.8GB

Signification : Vous avez récupéré près de 60 Go en supprimant le cache de build. Les builds peuvent être plus lents tant que le cache se reconstruit.

Décision : Si c’est un builder CI, planifiez des purges périodiques ou imposez un plafonnement du cache plutôt que de « tout pruner » en panique.

Task 13: Prune unused images (safe-ish, but understand your deploy strategy)

cr0x@server:~$ docker image prune -a --force
Deleted Images:
deleted: sha256:7f6e5d4c3b2a...
deleted: sha256:1a2b3c4d5e6f...
Total reclaimed space: 28.4GB

Signification : Docker a supprimé des images non référencées par aucun conteneur. Si vous comptez sur des rollbacks rapides en gardant des images locales, vous venez de supprimer votre filet de sécurité.

Décision : Sur des nœuds de production, envisagez de conserver les N dernières versions ou de compter sur un registry fiable et un bon cache.

Task 14: Find large volumes and who uses them

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     api_db_data
local     prometheus_data
local     tmp_ci_run_1738

cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/*/_data | sort -h | tail
6.2G  /var/lib/docker/volumes/prometheus_data/_data
48G   /var/lib/docker/volumes/api_db_data/_data
71G   /var/lib/docker/volumes/tmp_ci_run_1738/_data

Signification : Un volume CI « tmp » occupe 71 Go. C’est probablement du garbage. Le volume DB est volumineux mais vraisemblablement légitime.

Décision : Auditez l’attachement avant suppression : identifiez quels conteneurs utilisent le volume tmp. Ne supprimez pas les volumes de base de données à la légère.

Task 15: Map volumes to containers (avoid deleting live state)

cr0x@server:~$ docker ps -a --format '{{.ID}} {{.Names}}' | head
a1b2c3d4e5f6 api-prod-1
d4e5f6a1b2c3 ci-runner-1738
...

cr0x@server:~$ docker inspect -f '{{.Name}} -> {{range .Mounts}}{{.Name}} {{end}}' d4e5f6a1b2c3
/ci-runner-1738 -> tmp_ci_run_1738

Signification : Le grand volume tmp appartient à un conteneur CI runner spécifique (peut-être déjà arrêté, peut-être encore utilisé).

Décision : Si le conteneur est arrêté et le volume vraiment éphémère, supprimez conteneur et volume. S’il est en cours d’exécution, corrigez le job qui écrit autant.

Task 16: Remove a confirmed-orphan volume

cr0x@server:~$ docker rm -f ci-runner-1738
ci-runner-1738

cr0x@server:~$ docker volume rm tmp_ci_run_1738
tmp_ci_run_1738

Signification : Le conteneur et le volume sont supprimés ; l’espace disque devrait diminuer.

Décision : Ajoutez de l’automatisation du cycle de vie pour les artefacts CI afin que les « tmp volumes » ne deviennent pas des résidents permanents.

Task 17: One command to clear the obvious junk (use with judgment)

cr0x@server:~$ docker system prune --all --volumes --force
Deleted Containers:
...
Deleted Images:
...
Deleted Volumes:
...
Total reclaimed space: 132.6GB

Signification : Vous venez de supprimer essentiellement tout ce qui était inutilisé, y compris des volumes. Cela peut être catastrophique si vous avez mal classifié « inutilisé ».

Décision : Utilisez ceci uniquement sur des hôtes jetables (CI, builders dev) ou quand vous avez vérifié la sécurité des volumes. En production, préférez des nettoyages ciblés.

Task 18: Move Docker’s root dir to a bigger disk (the grown-up fix)

Quand vous faites du pruning en permanence, vous traitez des symptômes. Parfois il faut déplacer les données.

cr0x@server:~$ sudo systemctl stop docker
cr0x@server:~$ sudo rsync -aHAX --numeric-ids /var/lib/docker/ /mnt/docker-data/
cr0x@server:~$ sudo mkdir -p /etc/docker
cr0x@server:~$ sudo tee /etc/docker/daemon.json > /dev/null
{
  "data-root": "/mnt/docker-data"
}
cr0x@server:~$ sudo systemctl start docker
cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/mnt/docker-data

Signification : Docker utilise désormais le nouveau data root. Si des conteneurs échouent au démarrage, vous avez probablement manqué des permissions, des contexts SELinux, ou des flags rsync.

Décision : C’est une opération de changement contrôlé. Faites-la en fenêtre de maintenance, et validez d’abord avec un conteneur canari.

Trois mini-récits d’entreprise depuis le terrain

Mini-récit #1 : L’incident causé par une mauvaise supposition (les journaux « ne peuvent pas être si gros »)

L’entreprise était en migration des VMs vers des conteneurs. Le service central avait été stable pendant des années, et l’effort de containerisation était volontairement minimal : « lift and shift, ne pas refactorer ». Cette décision n’était pas mauvaise. L’hypothèse attachée l’était.

Ils supposaient que les journaux étaient « pris en charge par la plateforme » parce que l’ancienne image VM avait logrotate. Dans Docker, l’appli écrivait toujours sur stdout/stderr. La plateforme s’en est occupée—en écrivant des journaux JSON sur disque, pour toujours, sans rotation. Le premier jour tout allait bien. Le vingtième jour, un nœud a commencé à renvoyer des 500. L’orchestrateur a continué de rescheduler, parce que « les conteneurs sont du bétail ». Le nœud est resté plein parce que le rescheduling ne supprime pas assez vite les fichiers de logs, et les nouveaux conteneurs continuaient à écrire dans le même abîme.

L’ingénieur d’astreinte a vérifié df -h sur /, a vu 40 % libres, et a déclaré « pas disque ». Il a manqué que Docker vivait sur /var, et que /var était un montage différent. Un deuxième ingénieur a lancé docker system df et n’a rien vu d’extraordinaire—parce que l’outil de Docker ne criait pas « un fichier de log fait 20 Go ».

La correction a été brutalement simple : tronquer le fichier de log, limiter la taille des logs, et baisser le niveau de log pour une boucle chaude qui était inoffensive sur les VMs parce que les logs tournaient. L’action post-incident était aussi simple et plus importante : documenter où vivent les logs pour chaque driver de logs, et alerter sur la croissance. Voilà ce que signifie réellement « travail plateforme ».

Mini-récit #2 : L’optimisation qui s’est retournée contre eux (cache BuildKit partout)

Une autre équipe était fière de la rapidité de son CI. Les builds duraient quelques minutes, en grande partie parce que le cache BuildKit fonctionnait parfaitement. Trop parfaitement. Leurs builders faisaient aussi tourner quelques services longue durée (parce qu’« on avait de la capacité libre »), et les builders avaient des SSD locaux volumineux. Ça paraissait efficace : une classe de machines, une image dorée, tout schedulé n’importe où.

Le cache a grossi silencieusement. Builds multi-arch, mises à jour fréquentes de dépendances, et l’habitude d’étiqueter chaque commit ont créé un cache à fort churn. Ça a tenu une semaine. Puis une grosse branche de release a déclenché une tempête de builds et de variantes de couches. Le cache a gonflé et a poussé le disque au-delà du bord en heures ouvrables.

La partie douloureuse n’était pas le disque plein. La partie douloureuse était l’effet de second ordre : à mesure que le disque se remplissait, les builders ralentissaient, les jobs timeout, les retries augmentaient la charge, et le cache grossissait encore plus vite. Le système est devenu une boucle auto-alimentée : l’« optimisation » rendait la défaillance plus explosive.

La correction finale n’a pas été « pruner plus ». Ils ont séparé les rôles : builders dédiés avec capping de cache programmé, runtimes dédiés avec rétention d’images stricte, et limites explicites pour les logs. Ils ont aussi arrêté de confondre « build rapide » et « build stable » comme KPI identiques.

Mini-récit #3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (quotas et alertes)

Une équipe plateforme interne orientée finance avait l’habitude impopulaire : mettre des quotas et des seuils d’alerte sur tout. Les développeurs se plaignaient, parce que les quotas semblent bureaucratiques jusqu’à ce qu’on comprenne le rayon d’impact.

Ils ont configuré la rotation des logs pour le driver json-file de Docker et ont aussi mis des caps journald sur les hôtes qui utilisent journald. Ils ont placé des alertes sur l’utilisation de /var/lib/docker, l’utilisation des inodes, et sur la taille des N plus gros fichiers de log par conteneur. Le bruit d’alerte était faible parce que les seuils étaient ajustés, et les alertes avaient des runbooks attachés.

Un vendredi soir, un service a commencé à spammer un message d’erreur dû à une rotation de credentials en aval. Sur les plateformes d’autres équipes, ce genre d’incident devient « disque plein » plus « appli down ». Sur la plateforme de cette équipe, les fichiers de logs ont atteint leur cap, les logs ont tourné, le disque est resté sain, et l’astreinte a reçu une seule alerte : « taux d’erreur du service + augmentation du volume de logs ». Ils ont corrigé le problème de credential. Pas de panique de nettoyage. Pas de triage système de fichiers. La fiabilité ennuyeuse a encore gagné.

Erreurs courantes : symptôme → cause racine → correctif

1) « df montre de l’espace libre, mais Docker dit no space »

Symptôme : Pull/build/démarrage échoue avec no space left on device ; df -h sur / montre beaucoup de libre.

Cause racine : Le root Docker est sur un montage différent (/var ou un disque dédié), ou vous remplissez /tmp pendant les builds.

Fix : docker info pour Docker Root Dir ; lancez df -h sur ce montage et sur /tmp. Déplacez data-root ou étendez le système de fichiers correct.

2) « No space » mais vous avez des gigaoctets libres

Symptôme : Les écritures échouent ; df -h montre des Go libres ; les erreurs persistent.

Cause racine : Épuisement des inodes (df -i à 100 %) ou métadonnées thin pool pleines (devicemapper).

Fix : Si inodes : prune des caches générant de très petits fichiers et recréer le système de fichiers avec une densité d’inodes adaptée (ou utiliser XFS). Si devicemapper : migrer vers overlay2 ou étendre les métadonnées du thin pool.

3) « docker system prune n’a rien libéré »

Symptôme : Vous avez pruné, mais l’utilisation disque a peu changé.

Cause racine : Le coupable est les journaux ou journald, ou de gros volumes nommés attachés à des conteneurs en cours d’exécution.

Fix : Inspectez /var/lib/docker/containers et l’utilisation journald ; vérifiez la taille des volumes sous /var/lib/docker/volumes et mappez les volumes aux conteneurs.

4) « On a supprimé des conteneurs, mais le disque n’a pas baissé »

Symptôme : La suppression de conteneurs ne libère pas l’espace attendu.

Cause racine : Les volumes persistent ; les images persistent ; le cache de build persiste ; aussi, des fichiers supprimés mais encore ouverts peuvent garder l’espace alloué jusqu’à ce que le process se termine.

Fix : Vérifiez les volumes et le cache de build ; si vous suspectez des fichiers supprimés mais ouverts, redémarrez le processus en cause (parfois le daemon Docker ou le runtime de conteneur) après un nettoyage sûr.

5) « Le répertoire overlay2 est énorme ; peut-on le supprimer ? »

Symptôme : /var/lib/docker/overlay2 domine l’utilisation disque.

Cause racine : C’est là où vivent les couches d’images et les couches inscriptibles. Une suppression manuelle casse l’état Docker.

Fix : Utilisez les commandes Docker pour purger images/conteneurs inutilisés ; si l’état est corrompu, planifiez une suppression contrôlée et une recréation pour hôtes jetables, pas pour des nœuds d’état en production.

6) « Après passage à journald, le disque se remplit toujours »

Symptôme : Vous avez changé le driver de log ; l’utilisation disque continue de croître.

Cause racine : Les paramètres de rétention journald sont trop permissifs, ou le stockage journal persistant est activé sans limites.

Fix : Configurez des limites de taille/temps pour journald et validez avec journalctl --disk-usage.

7) « Les builders CI saturent le disque chaque semaine »

Symptôme : Les nœuds builders se remplissent de façon prévisible.

Cause racine : Rétention du cache BuildKit non bornée ; plusieurs toolchains génèrent beaucoup de couches uniques ; trop de tags/branches construits sur le même nœud.

Fix : docker builder prune programmé ; séparer builder et runtime ; imposer une rétention et/ou reconstruire périodiquement les builders (immutable infrastructure aide ici).

8) « L’espace est libéré, mais le service reste cassé »

Symptôme : Vous avez récupéré de l’espace, mais les conteneurs ne démarrent toujours pas ou se comportent mal.

Cause racine : Métadonnées Docker corrompues, pulls partiels, ou défaillance applicative qui a initialement causé l’explosion des logs.

Fix : Validez avec un conteneur connu bon, vérifiez les logs du daemon, et corrigez le problème applicatif en amont (limitation de débit, tempêtes de retries, erreur d’auth). Le disque n’était que dommage collatéral.

Listes de contrôle / plan étape par étape

Checklist d’urgence (le nœud de production est plein maintenant)

  1. Confirmer le montage : lancez df -h et df -i sur le root Docker et /tmp.
  2. Arrêter d’abord les journaux incontrôlés : trouvez les plus gros fichiers de logs de conteneurs ; tronquez les pires ; réduisez le niveau de logs si c’est sûr.
  3. Récupérer les caches sûrs : lancez docker builder prune --all sur les builders ; lancez docker image prune -a si vous comprenez l’impact sur les rollback.
  4. Auditer les volumes avant d’y toucher : identifiez les plus gros volumes et mappez-les aux conteneurs. Supprimez seulement les volumes orphelins confirmés.
  5. Vérifier l’espace libre : relancez df -h. Gardez au moins quelques Go libres ; certains systèmes de fichiers et daemons se comportent mal près de 100 %.
  6. Stabiliser : redémarrez les composants défaillants seulement après avoir soulagé la pression disque ; évitez le flapping.
  7. Rédiger la note d’incident : ce qui a rempli le disque, à quelle vitesse, et quelle modification de politique l’empêche.

Checklist de durcissement (faire en sorte que ça cesse)

  1. Définir la rotation des logs Docker : limiter taille et nombre pour json-file.
  2. Définir la rétention journald : plafonner le stockage et/ou le temps si vous utilisez journald.
  3. Séparer les responsabilités : builders et runtimes ne devraient pas être la même flotte sauf si vous aimez la croissance mystère.
  4. Définir une politique de pruning : purge programmée du cache de build, et règles de rétention d’images selon le rôle de l’hôte.
  5. Déplacer Docker root vers un stockage dédié : surtout sur des systèmes racine petits.
  6. Alerter sur inodes et octets : et inclure des runbooks pointant vers ces commandes exactes.
  7. Mesurer les pires pollueurs : plus gros volumes, plus gros fichiers de logs par conteneur, plus grosses images par hôte.
  8. Concevoir pour la défaillance : si un service en aval casse et déclenche des tempêtes de retries, votre plateforme doit se dégrader sans s’autodétruire.

Paramètres recommandés de base pour le daemon Docker (valeurs pratiques)

Si vous utilisez le driver de logs json-file, définissez la rotation. C’est le contrôle d’espace le plus rentable que vous puissiez faire.

cr0x@server:~$ sudo tee /etc/docker/daemon.json > /dev/null
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}
cr0x@server:~$ sudo systemctl restart docker

Signification : Le fichier de log de chaque conteneur tourne vers ~50 Mo, en gardant 5 fichiers (~250 Mo par conteneur dans le pire des cas).

Décision : Ajustez les tailles selon l’environnement. La production nécessite souvent une centralisation des logs ; les logs locaux doivent être un buffer, pas une archive.

Faits intéressants et contexte historique

  • Fait 1 : Les premières déploiements Docker utilisaient souvent le mode devicemapper loopback par défaut, qui était lent et sujet à des échecs d’espace/métadonnées sous charge.
  • Fait 2 : Le passage de Docker à overlay2 comme défaut a rendu le stockage plus rapide et plus simple, mais a aussi fait du comportement copy-up une surprise fréquente pour les équipes écrivant dans les systèmes de fichiers des conteneurs.
  • Fait 3 : Le driver de logs par défaut de Docker étant historiquement json-file était optimisé pour la simplicité, pas pour l’hygiène disque à long terme.
  • Fait 4 : La popularité de BuildKit a augmenté parce qu’il rend les builds plus rapides et parallèles, mais la taxe opérationnelle est la gestion du cache—surtout sur des builders partagés.
  • Fait 5 : L’expression « no space left on device » est un errno générique (ENOSPC) retourné par le kernel, et il est utilisé pour plus que juste « disque plein ».
  • Fait 6 : L’épuisement d’inodes est un vieux problème Unix qui n’est jamais mort ; les conteneurs l’ont ramené parce que l’extraction d’images et les écosystèmes de langages génèrent beaucoup de petits fichiers.
  • Fait 7 : Beaucoup d’opérateurs ont appris à la dure que « les conteneurs sont éphémères » n’est pas une affirmation sur les données. Les volumes sont l’état, et l’état est éternel à moins que vous ne le supprimiez.
  • Fait 8 : La comptabilité d’espace de Docker (docker system df) est utile mais pas autoritative ; le système de fichiers est la vérité, surtout pour les journaux et l’usage temporaire hors Docker.

FAQ

1) Pourquoi Docker dit « no space left on device » alors que df -h montre de l’espace ?

Parce que vous avez vérifié le mauvais montage, ou vous êtes à court d’inodes, ou vous avez atteint un quota/limite de métadonnées. Vérifiez toujours le répertoire root Docker et lancez df -i.

2) Est-il sûr d’exécuter docker system prune -a en production ?

Parfois. Cela supprime les images, conteneurs et réseaux inutilisés. Cela peut casser des stratégies de rollback rapide et ralentir les redeploys à cause des pulls d’images. Préférez d’abord un pruning ciblé.

3) Est-il sûr d’exécuter docker system prune --volumes ?

Uniquement si vous avez vérifié que les volumes « inutilisés » sont réellement jetables. « Unused » signifie « pas actuellement référencé », pas « sans importance ». C’est comme ça qu’on supprime des données.

4) Pourquoi mes journaux de conteneur sont-ils énormes ?

Parce que le logging json-file par défaut n’est pas limité sauf si vous définissez max-size et max-file. De plus, une appli bruyante peut générer des gigaoctets par heure lors de boucles d’erreur.

5) Si je tronque les logs de conteneur, Docker ou l’appli vont-ils casser ?

Tronquer le fichier de logs JSON est généralement sûr comme mesure d’urgence. Vous perdez l’historique des logs, et l’appli continue à journaliser. Ensuite, corrigez la rotation correctement.

6) Pourquoi la suppression d’un conteneur ne libère pas d’espace ?

Parce que l’espace est probablement dans des volumes, des images, ou le cache de build. Aussi, l’espace de fichiers supprimés peut rester alloué si un processus a encore le fichier ouvert.

7) Pourquoi /var/lib/docker/overlay2 est-il si gros alors que je n’ai pas beaucoup d’images ?

Overlay2 inclut les couches inscriptibles et le contenu extrait des couches. Quelques images « grosses » plus des conteneurs qui écrivent beaucoup peuvent facilement dominer le disque.

8) Quelle est la meilleure façon de prévenir les incidents disque Docker sur des builders CI ?

Builders dédiés, docker builder prune programmé, caches bornés, et reconstruction périodique des builders. Traitez les caches comme des consommables, pas comme des trésors.

9) Puis-je simplement supprimer des fichiers sous /var/lib/docker manuellement ?

Ne le faites pas. La suppression manuelle corrompt souvent la vue qu’a Docker du monde. Utilisez les commandes Docker, ou faites un wipe contrôlé seulement sur des hôtes vraiment jetables.

10) Combien d’espace libre dois-je garder sur un hôte Docker ?

Assez pour que les pulls/décompressions et les rafales de logs ne vous poussent pas à 100 %. Pratiquement : gardez une marge de plusieurs gigaoctets et alertez bien avant la falaise.

Conclusion : prochaines étapes pour éviter les récidives

Quand Docker manque d’espace, ce n’est rarement « Docker est gros » et presque toujours « on n’a pas géré les parties ennuyeuses ». Les journaux, caches et volumes sont ennuyeux. Ils sont aussi la source des incidents.

Vos prochaines étapes pratiques :

  1. Imposez un plafond sur les logs dès aujourd’hui (rotation json-file et/ou rétention journald). Cela élimine déjà une grande classe d’incidents.
  2. Définissez des rôles d’hôtes : les nœuds runtime ne doivent pas accumuler les caches de build ; les builders doivent avoir un pruning programmé et des rebuilds prévisibles.
  3. Alertez sur octets et inodes pour le système de fichiers racine Docker, plus les plus gros fichiers de logs de conteneurs et les plus gros volumes.
  4. Arrêtez d’écrire l’état dans les couches inscriptibles : utilisez les volumes intentionnellement, montez tmpfs pour les données vraiment temporaires, et auditez les chemins d’écriture de vos applis.
  5. Quand vous nettoyez, soyez chirurgicaux : caches et images inutilisées d’abord, volumes seulement avec preuves.

Le disque n’est pas glamour. C’est pourquoi il gagne tant de batailles. Faites-en la responsabilité de quelqu’un—de préférence la vôtre—avant que ça ne devienne votre week-end.

← Précédent
Proxmox « dpkg was interrupted » : réparer les paquets cassés sans réinstaller
Suivant →
Planification des scrubs ZFS : éviter les problèmes aux heures de pointe

Laisser un commentaire