Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Faire tourner les journaux, et reprendre du disque

Par mis à jour le

Améliorer cette page

Quand un journal grossit sans fin ou qu'une écriture échoue, la tentation est de supprimer au hasard, et vous risquez d'effacer une donnée encore utile. Cette leçon vous montre comment borner les journaux d'un conteneur, vérifier que la limite s'applique vraiment, lire l'occupation du disque par catégorie et libérer un fichier jetable sans recréer le conteneur. Les mesures viennent d'une VM Debian 13 avec Docker Engine 29.9.2 et Compose 5.6.1. Les gestes qui modifient le démon système, comme changer daemon.json, ont été mesurés sur une VM jetable : ne les rejouez pas sur un hôte partagé.

Chaque geste se vérifie sur l'état réel du moteur, pas seulement dans un fichier de configuration.

  • Vous lisez le pilote actif, les options du conteneur et le chemin de son journal.
  • Vous vérifiez que les fichiers tournent après une écriture qui dépasse leur taille maximale.
  • Vous distinguez une option propre au conteneur d'une valeur par défaut du démon.
  • Vous mesurez les images, les conteneurs, les volumes et le cache, puis vous comparez ce que docker system df annonce à l'espace réellement rendu, avant de choisir ce qui peut être supprimé.
  • Vous repérez un manque d'octets ou d'inodes (les entrées qui permettent de créer des fichiers) et libérez l'espace sans remplacer le conteneur.

Vous devez savoir lancer un conteneur et lire sa configuration. La leçon Configurer le daemon Docker (daemon.json) explique le rôle de daemon.json, les options du démon et son redémarrage.

Les sorties ci-dessous ont été relevées le 4 novembre 2026 sur la VM de référence. L'image de démonstration est épinglée à son digest :

Fenêtre de terminal
IMAGE="alpine:3.23@sha256:ff7f0c727dd796aa36787bfcd9a64bf35a6ce72a73451b3359fde849b6703d63"
mkdir -p ~/journaux-disque/compose
cd ~/journaux-disque
pwd
/home/ansible/journaux-disque

Jouez les blocs de cette leçon dans l'ordre et dans un même terminal. Ils réutilisent les variables IMAGE, R et SPACE_ID, le répertoire courant et, pendant la mesure du cache, DOCKER_HOST. Dans un autre terminal, ces variables n'existent pas : redéfinissez-les avant de continuer.

Le dossier ~/journaux-disque contient uniquement les fichiers créés pour cette démonstration. Les exemples n'exposent aucun port et utilisent des noms différents de ceux des deux labs.

Le pilote de journalisation décide comment Docker conserve les sorties standard et d'erreur d'un conteneur. Sur cette VM, le pilote par défaut était json-file, qui écrit un fichier JSON par conteneur (voir la configuration et le pilote json-file). Ce pilote ne borne pas les journaux par défaut : le test suivant a produit un fichier de 172 298 octets sans aucune option de rotation.

Fenêtre de terminal
docker info --format '{{.LoggingDriver}}'
docker run -d --name c35b-log-open --network none "$IMAGE" \
sh -c 'i=0; while [ "$i" -lt 160 ]; do printf "%05d:" "$i"; printf "%01000d\n" 0; i=$((i+1)); done'
printf 'open_exit='
docker wait c35b-log-open
docker inspect c35b-log-open \
--format 'type={{.HostConfig.LogConfig.Type}} options={{json .HostConfig.LogConfig.Config}} path={{.LogPath}}'
OPEN_LOG=$(docker inspect c35b-log-open --format '{{.LogPath}}')
printf 'open_log_size='
sudo stat -c '%s' "$OPEN_LOG"
printf 'open_log_files='
sudo find "$(dirname "$OPEN_LOG")" -maxdepth 1 -type f -name "$(basename "$OPEN_LOG")*" | wc -l
json-file
open_exit=0
type=json-file options={} path=/var/lib/docker/containers/f4a0128ad251dc71241206adfe3126b3206c5ab9f7dd55ac3710a6d6d9f1716f/f4a0128ad251dc71241206adfe3126b3206c5ab9f7dd55ac3710a6d6d9f1716f-json.log
open_log_size=172298
open_log_files=1

Dans la sortie, regardez deux choses. options={} dit qu'aucune limite n'est configurée, et path= donne l'emplacement du journal, dans un dossier nommé d'après l'identifiant du conteneur. docker inspect les lit dans HostConfig.LogConfig et LogPath. Le fichier appartient à root : la mesure a lu sa taille avec sudo, sans changer ses permissions.

Le pilote local est une alternative à json-file. La documentation du pilote local décrit son stockage interne et sa rotation. Le test plus bas vérifie seulement que docker logs lit ses journaux, sans mesurer ses limites par défaut.

Trois options règlent la rotation : max-size fixe la taille maximale d'un fichier, max-file le nombre de fichiers conservés et compress compresse les fichiers déjà tournés. Vous les posez sur le conteneur, sans toucher au pilote par défaut du démon. Elles se fixent à la création : docker run ou docker create les reçoit, et un conteneur existant ne les change pas. Pour modifier une limite, vous recréez le conteneur.

Fenêtre de terminal
docker run -d --name c35b-log-capped --network none \
--log-driver=json-file --log-opt max-size=36k --log-opt max-file=4 \
--log-opt compress=true \
"$IMAGE" sh -c 'i=0; while [ "$i" -lt 160 ]; do printf "%05d:" "$i"; printf "%01000d\n" 0; i=$((i+1)); done'
printf 'capped_exit='
docker wait c35b-log-capped
docker inspect c35b-log-capped \
--format 'type={{.HostConfig.LogConfig.Type}} options={{json .HostConfig.LogConfig.Config}}'
CAPPED_LOG=$(docker inspect c35b-log-capped --format '{{.LogPath}}')
printf 'capped_log_files=%s\n' "$(sudo find "$(dirname "$CAPPED_LOG")" -maxdepth 1 -type f -name "$(basename "$CAPPED_LOG")*" | wc -l)"
capped_exit=0
type=json-file options={"compress":"true","max-file":"4","max-size":"36k"}
capped_log_files=4

Le test a compté quatre fichiers sur l'hôte : le journal courant et trois fichiers tournés compressés. Les options affichées par inspect ne prouvent pas que la rotation a eu lieu, elles disent seulement ce qui a été demandé. Il faut dépasser le plafond, puis compter les fichiers dans le dossier indiqué par LogPath.

Sans privilège, docker logs donne aussi la preuve. docker logs c35b-log-capped | wc -l a rendu 126 lignes sur les 160 écrites, et la première ligne relue commençait par 00034. Les 34 premières lignes ont disparu avec le fichier le plus ancien : la limite s'applique, et ce qui dépasse le nombre de fichiers est perdu.

Compose accepte les mêmes options sous logging.options, et leurs valeurs sont des chaînes dans le fichier YAML. Enregistrez le service suivant dans ~/journaux-disque/compose/compose.yaml. Il pose un plafond différent de celui de la commande précédente. docker compose config -q le valide, puis up -d l'exécute.

services:
journal-writer:
image: ${IMAGE}
command:
- sh
- -c
- 'i=0; while [ "$$i" -lt 96 ]; do printf "%05d:" "$$i"; printf "%01000d\n" 0; i=$$((i+1)); done'
logging:
driver: json-file
options:
max-size: "24k"
max-file: "4"
compress: "true"
Fenêtre de terminal
IMAGE="$IMAGE" docker compose -p c35b-log-compose -f compose/compose.yaml config -q
IMAGE="$IMAGE" docker compose -p c35b-log-compose -f compose/compose.yaml up -d
IMAGE="$IMAGE" docker compose -p c35b-log-compose -f compose/compose.yaml wait journal-writer
COMPOSE_CID=$(IMAGE="$IMAGE" docker compose -p c35b-log-compose -f compose/compose.yaml ps -aq journal-writer)
COMPOSE_LOG=$(docker inspect "$COMPOSE_CID" --format '{{.LogPath}}')
printf 'compose_log_files=%s\n' "$(sudo find "$(dirname "$COMPOSE_LOG")" -maxdepth 1 -type f -name "$(basename "$COMPOSE_LOG")*" | wc -l)"
compose_log_files=4

Quatre fichiers sont donc présents aussi avec Compose. Le pilote par défaut du démon est resté json-file après ces deux configurations, puisqu'elles ne touchent que leur conteneur. La documentation liste les autres options disponibles.

Avec le pilote local, le test vérifie que docker logs rend les sorties et qu'inspect renvoie un LogPath vide : il n'y a pas de fichier JSON à compter.

Fenêtre de terminal
docker run -d --name c35b-log-local --network none --log-driver=local \
"$IMAGE" sh -c 'printf "local-driver-check\n"'
printf 'local_exit='
docker wait c35b-log-local
docker inspect c35b-log-local \
--format 'type={{.HostConfig.LogConfig.Type}} options={{json .HostConfig.LogConfig.Config}} path={{.LogPath}}'
docker logs --tail 1 c35b-log-local
printf 'daemon_default_after_per_container_tests='
docker info --format '{{.LoggingDriver}}'
local_exit=0
type=local options={} path=
local-driver-check
daemon_default_after_per_container_tests=json-file

Quand les valeurs de daemon.json s'appliquent-elles ?

Section intitulée « Quand les valeurs de daemon.json s'appliquent-elles ? »

Les valeurs de log-opts dans daemon.json deviennent les valeurs par défaut des nouveaux conteneurs, après le redémarrage du démon. Docker attend des chaînes, y compris pour les tailles, les nombres de fichiers et les booléens. Voici la configuration testée :

{
"log-driver": "json-file",
"log-opts": {
"max-size": "48k",
"max-file": "3",
"compress": "true"
}
}

Dans le schéma, regardez la branche de gauche : le redémarrage du démon ne modifie pas un conteneur déjà créé. Seul un conteneur créé après lui, ou recréé, reçoit les nouvelles valeurs.

Après le redémarrage du démon, un conteneur créé avant garde des options vides et un journal sans limite, tandis qu'un conteneur créé après hérite des log-opts. Pour qu'un ancien conteneur les hérite, il faut le supprimer avec docker rm puis le recréer avec docker run.ne change riens'appliquentà recréerdaemon.jsonlog-optsRedémarrage du démonConteneur créé avantoptions = {}journal sans limiteConteneur créé aprèshérite des log-optsdocker rm,puis docker runConteneur recrééhérite des log-opts
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le test a donné trois résultats :

  • Le conteneur créé avant le changement a gardé options={} après le redémarrage. Relancé sans être recréé, il a écrit environ 344 600 octets dans un seul fichier, soit deux exécutions de 172 300 octets.
  • Un nouveau conteneur a reçu les trois options et produit trois fichiers, dont un journal courant de 26 923 octets.
  • L'ancien conteneur, supprimé puis recréé, a reçu un nouvel identifiant et les nouvelles valeurs par défaut.

Pour qu'un conteneur existant hérite des nouvelles valeurs, il faut donc le recréer : le redémarrer ne suffit pas. Le test a modifié daemon.json, redémarré le démon, puis remis la configuration d'origine. Sur un hôte réel, relisez d'abord la configuration existante et la procédure de redémarrage décrites dans Configurer le daemon Docker (daemon.json).

Que faut-il mesurer avant de reprendre de l'espace ?

Section intitulée « Que faut-il mesurer avant de reprendre de l'espace ? »

docker system df répartit l'occupation du disque par Docker entre quatre catégories : images, conteneurs, volumes et cache de construction. docker system df -v détaille ensuite les éléments de chaque catégorie. Le tableau résume ce que chaque ligne regroupe.

LigneRessource mesurée
ImagesImages locales, tailles partagées et uniques dans la vue détaillée.
ContainersEspace associé aux conteneurs, y compris ceux qui sont arrêtés.
Local VolumesVolumes locaux et espace occupé.
Build CacheCache de construction des images.

La colonne RECLAIMABLE décrit un potentiel de récupération, pas une autorisation de supprimer sans avoir examiné le périmètre. La documentation de docker system df décrit les vues résumé et détaillée.

Pour mesurer sans toucher au démon système, lancez un démon secondaire isolé. Il a son propre socket, son propre --data-root et son propre espace de noms containerd (--containerd-namespace et --containerd-plugins-namespace). Sans ces deux dernières options, il partagerait avec le démon système le magasin d'images containerd et afficherait ses images dans docker image ls. Vous construisez ensuite une image qui laisse du cache, sans réseau puisque ce démon n'a pas de pont.

Deux précautions encadrent le lancement. sudo -v demande votre mot de passe avant que dockerd parte en arrière-plan : sans lui, sudo pourrait attendre une saisie que vous ne voyez pas. Ensuite, la boucle attend le socket pendant 30 secondes au plus, puis le script affiche un message qui renvoie au journal dockerd.log si le démon n'a pas démarré.

Fenêtre de terminal
R=/tmp/c35b-sec
mkdir -p "$R/ctx"
printf 'FROM %s\nRUN dd if=/dev/urandom of=/blob1 bs=1M count=8\nRUN dd if=/dev/urandom of=/blob2 bs=1M count=8\n' "$IMAGE" > "$R/ctx/Dockerfile"
sudo -v
sudo dockerd --host "unix://$R/d.sock" --data-root "$R/data" --exec-root "$R/exec" \
--pidfile "$R/d.pid" --bridge none --iptables=false --ip-masq=false \
--containerd-namespace c35b-ns --containerd-plugins-namespace c35b-plug \
> "$R/dockerd.log" 2>&1 &
export DOCKER_HOST="unix://$R/d.sock"
for i in $(seq 1 30); do [ -S "$R/d.sock" ] && break; sleep 1; done
[ -S "$R/d.sock" ] || echo "dockerd n'a pas démarré, lisez $R/dockerd.log"
sleep 3
sudo -E docker pull -q "$IMAGE"
sudo -E docker build --network none -q -t c35b-cache-img "$R/ctx"
sleep 3
sudo -E docker system df
sudo -E docker system df -v
sudo -E docker builder prune --all --filter until=1s --force
sudo -E docker system df

Les lignes utiles de la mesure, avant puis après la suppression :

Build Cache 5 0 46.52MB 12.29kB
Total: 46.52MB
Build Cache 0 0 0B 0B

Regardez la colonne RECLAIMABLE. Avant la suppression, elle affichait 12,29 kB, alors que docker builder prune --all a affiché Total: 46.52MB. Aucun de ces deux chiffres ne mesure l'espace rendu au disque : le Total additionne les entrées de cache retirées, et RECLAIMABLE laisse de côté tout ce qui est partagé. docker system df -v montre ce partage avec sa colonne SHARED :

CACHE ID CACHE TYPE SIZE CREATED LAST USED USAGE SHARED
nyf6pjznkc80 source.local 8.19kB 4 seconds ago 3 seconds ago 1 false
w4p1wknca9ib source.local 4.1kB 4 seconds ago 3 seconds ago 1 false
k8ydgs149k1x regular 12.9MB 4 seconds ago 3 seconds ago 1 true
1ml2p8gkqeaj regular 16.8MB 3 seconds ago 3 seconds ago 1 true
ccvs2ctqyta1 regular 16.8MB 3 seconds ago 3 seconds ago 1 true

Avec le magasin containerd, les trois couches regular sont partagées avec l'image alpine et avec l'image construite. Tant que cette image existe, RECLAIMABLE ne compte pas ce qui est partagé : il vaut 12,29 kB, la somme des deux entrées source.local. Partagé ne veut pourtant pas dire conservé. Mesuré sur la VM jetable avec df -B1 --output=used /var/lib/containerd, le prune a rendu environ 26 MB : RECLAIMABLE sous-estimait l'espace rendu, et le Total le surestimait.

Un second ordre de mesure donne le même sens d'écart. Quand l'image est supprimée avant le prune, les deux couches de 16,8 MB passent à SHARED false et RECLAIMABLE affiche 33.59MB. Le prune a pourtant rendu environ 43 MB : là encore, RECLAIMABLE annonçait moins que l'espace réellement rendu.

Seul df tranche, parce qu'il mesure le disque et non une estimation du moteur. Ces mesures valent pour ce démon et cette image. Retenez la méthode plutôt que les chiffres : relevez df avant et après, et ne prenez ni RECLAIMABLE ni le Total de prune pour l'espace que vous allez récupérer.

Le sleep 3 avant la mesure compte. Lancé juste après la construction, le filtre until=1s n'a rien supprimé (Total: 0B), parce que le cache avait servi moins d'une seconde plus tôt. Cette valeur ne servait qu'à rendre le cache éligible pendant l'essai : ne la copiez pas sur un hôte partagé. Choisissez un seuil selon vos besoins, après avoir lu docker system df -v. La documentation décrit les filtres disponibles.

Un build refait sous le même tag laisse-t-il une image pendante, c'est-à-dire une image qui a perdu son nom ? Pas dans le cas mesuré. Le test ci-dessous construit deux fois c35b-cache-img avec un contenu différent dans le démon secondaire, puis liste les images, y compris les entrées cachées. La dernière commande passe par ctr, le client en ligne de commande de containerd, qui lit directement l'espace de noms c35b-ns du démon secondaire.

Fenêtre de terminal
printf 'FROM %s\nRUN echo un > /f\n' "$IMAGE" > "$R/ctx/Dockerfile"
sudo -E docker build --network none -q -t c35b-cache-img "$R/ctx"
printf 'FROM %s\nRUN echo deux > /f\n' "$IMAGE" > "$R/ctx/Dockerfile"
sudo -E docker build --network none -q -t c35b-cache-img "$R/ctx"
sudo -E docker image ls --filter dangling=true
sudo -E docker image ls -a
sudo ctr -n c35b-ns images ls -q
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
alpine@sha256:ff7f0c727dd796aa36787bfcd9a64bf35a6ce72a73451b3359fde849b6703d63 ff7f0c727dd7 13MB 3.94MB
c35b-cache-img:latest bb1e39de3365 12.9MB 3.86MB
docker.io/library/alpine@sha256:ff7f0c727dd796aa36787bfcd9a64bf35a6ce72a73451b3359fde849b6703d63
docker.io/library/c35b-cache-img:latest

Sur Docker 29.9.2 avec le magasin containerd, le second build a déplacé le tag sans laisser d'image pendante : la liste dangling=true ne contient que son en-tête, docker image ls -a ne montre qu'un seul c35b-cache-img, et ctr ne voit aucune entrée moby-dangling@. Ce constat vaut pour ce cas, un tag déplacé par un build. Sur le démon système, docker image ls --filter dangling=true reste la commande à lancer avant de décider qu'une image est un rebut, et la liste y était vide.

La mesure est finie. Retirez les images du démon secondaire, puis revenez au démon système avec unset DOCKER_HOST : sans lui, les commandes suivantes continueraient de viser le démon secondaire. Arrêtez ensuite ce démon et supprimez son dossier.

Il reste ses espaces de noms containerd, c35b-ns et c35b-ns_history. Ils vivent dans le containerd du système, pas dans le dossier supprimé, et ctr permet de les retirer. L'espace c35b-plug, réservé aux plugins, n'est jamais apparu dans ctr namespaces ls pendant la mesure : il n'y a rien à retirer. Un espace de noms ne se supprime que vide : la boucle ci-dessous retire d'abord les baux (leases) qui retiennent les couches, puis les couches (snapshots) et les blobs (content). Une fois la boucle terminée, ctr namespaces ls ne doit plus lister ni c35b-ns ni c35b-ns_history.

Fenêtre de terminal
sudo -E docker image rm c35b-cache-img "$IMAGE"
unset DOCKER_HOST
sudo kill "$(cat "${R:?}/d.pid")"
while [ -S "$R/d.sock" ]; do sleep 1; done
sudo rm -rf "${R:?}"
for ns in c35b-ns c35b-ns_history; do
sudo ctr -n "$ns" leases ls -q | xargs -r -n1 sudo ctr -n "$ns" leases delete >/dev/null
for i in $(seq 1 15); do
[ "$(sudo ctr -n "$ns" snapshots ls | wc -l)" -le 1 ] && break
sudo ctr -n "$ns" snapshots ls | awk 'NR>1{print $1}' | xargs -r -n1 sudo ctr -n "$ns" snapshots rm >/dev/null 2>&1
sleep 1
done
sudo ctr -n "$ns" content ls -q | xargs -r sudo ctr -n "$ns" content rm >/dev/null
sudo ctr namespaces rm "$ns"
done
sudo ctr namespaces ls

Comment supprimer seulement les ressources visées ?

Section intitulée « Comment supprimer seulement les ressources visées ? »

Un conteneur arrêté et un volume détaché ne sont pas forcément des rebuts. La démonstration a créé un conteneur arrêté portant un label, un autre conteneur arrêté sans ce label et un conteneur qui utilise un volume nommé. Le filtre de docker container prune n'a retiré que le conteneur portant le label demandé.

Fenêtre de terminal
docker volume create c35b-data-ref
docker run -d --name c35b-data-holder --network none \
--mount type=volume,src=c35b-data-ref,dst=/payload \
"$IMAGE" sh -c 'printf "stock=kept\n" > /payload/stock; sleep 7200'
docker run --name c35b-stop-target --label c35b.cleanup=lesson --network none "$IMAGE" true
docker run --name c35b-stop-unrelated --network none "$IMAGE" true
docker container prune --filter label=c35b.cleanup=lesson --force
docker inspect c35b-stop-unrelated \
--format 'unrelated_still_present={{.State.Status}}'
docker inspect c35b-data-holder \
--format 'data_holder_still_present={{.State.Status}} volume={{range .Mounts}}{{.Name}}{{end}}'
printf 'reference_data='
docker exec c35b-data-holder cat /payload/stock
docker volume inspect c35b-data-ref --format 'named_volume={{.Name}}'
Deleted Containers:
3343f432a41bcf8f66fe4a4b3b39c4aa816e7bf98f89ca01439bf5699642bb93
Total reclaimed space: 4.096kB
unrelated_still_present=exited
data_holder_still_present=running volume=c35b-data-ref
reference_data=stock=kept
named_volume=c35b-data-ref

La sortie ne liste qu'un seul identifiant supprimé. Le conteneur arrêté sans label et le volume nommé, avec ses données, sont restés en place. docker rm suivi du nom d'un conteneur est encore plus précis : il ne vise que celui-là. Avant d'élargir un filtre, lisez la documentation.

Un volume anonyme, créé sans nom quand un conteneur déclare un chemin sans source (ou quand l'image déclare une instruction VOLUME), peut rester après la suppression de son conteneur. La démonstration l'a retrouvé avec dangling=true (volumes qu'aucun conteneur n'utilise), puis l'a supprimé par son nom exact. Un volume nommé se supprime de la même façon, après avoir vérifié qu'aucun conteneur utile ne l'utilise.

Fenêtre de terminal
docker run --name c35b-anon-owner --network none --volume /cache "$IMAGE" \
sh -c 'printf "discard\n" > /cache/item'
ANON_VOLUME=$(docker inspect c35b-anon-owner \
--format '{{range .Mounts}}{{if eq .Destination "/cache"}}{{.Name}}{{end}}{{end}}')
docker rm c35b-anon-owner
docker volume ls --filter dangling=true
docker volume rm "$ANON_VOLUME"

docker system prune retire, à l'échelle du démon, les conteneurs arrêtés, les réseaux inutilisés, les images pendantes et le cache de construction. Avec -a, il supprime aussi les images qu'aucun conteneur n'utilise, et --volumes ajoute les volumes anonymes inutilisés. L'option --volumes met des données en danger, parce qu'un volume anonyme peut en porter : une image dont le Dockerfile déclare un VOLUME en crée un à chaque conteneur, et ce volume peut contenir une base de données que personne n'a nommée. Le guide des volumes montre comment les repérer. Cette portée large ne remplace pas un inventaire des données à conserver : n'en faites pas un geste de routine. La commande n'a pas été exécutée pendant la mesure ; la documentation liste les ressources concernées.

Où se trouve ENOSPC, et comment libérer l'espace sans recréer ?

Section intitulée « Où se trouve ENOSPC, et comment libérer l'espace sans recréer ? »

ENOSPC est l'erreur « No space left on device » : une écriture n'a plus de place. Le refus peut venir de la couche d'écriture du conteneur, d'un tmpfs (un système de fichiers en mémoire, à taille plafonnée), d'un volume ou du système de fichiers de l'hôte. Pour trouver lequel, docker inspect montre les montages et les limites tmpfs, df -h mesure les octets et df -i mesure les inodes, c'est-à-dire les entrées nécessaires pour créer des fichiers.

La démonstration a attaché un tmpfs de 2 Mio à /scratch et le volume nommé à /persistent. Elle a écrit une donnée témoin dans le volume, puis inspecté les montages et comparé les systèmes de fichiers.

Fenêtre de terminal
docker run -d --name c35b-tmpfs-space --network none \
--mount type=volume,src=c35b-data-ref,dst=/persistent \
--tmpfs /scratch:rw,size=2m,nr_inodes=64 \
"$IMAGE" sleep 7200
docker exec c35b-tmpfs-space sh -c 'printf "inventory-line\n" > /persistent/inventory'
SPACE_ID=$(docker inspect c35b-tmpfs-space --format '{{.Id}}')
docker inspect c35b-tmpfs-space \
--format 'container_id={{.Id}} tmpfs={{json .HostConfig.Tmpfs}} mounts={{json .Mounts}}'
docker exec c35b-tmpfs-space df -h / /scratch /persistent
docker exec c35b-tmpfs-space df -i /scratch
DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}')
df -h "$DOCKER_ROOT"
df -i "$DOCKER_ROOT"
Filesystem Size Used Available Use% Mounted on
overlay 19.5G 2.1G 16.5G 11% /
tmpfs 2.0M 0 2.0M 0% /scratch
/dev/vda1 19.5G 2.1G 16.5G 11% /persistent
Filesystem Inodes Used Available Use% Mounted on
tmpfs 64 1 63 2% /scratch
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 20G 2.2G 17G 12% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 1302528 51235 1251293 4% /

Lisez les montages un par un. /scratch est un tmpfs de 2,0 M, avec 64 inodes. /persistent affiche le même disque /dev/vda1 que l'hôte : un volume nommé consomme ici le disque de l'hôte, alors que le tmpfs a sa propre limite. Le disque hôte n'a pas été rempli. Identifiez d'abord le montage plein, puis retirez seulement le fichier que vous avez confirmé jetable.

Le test a écrit dans /scratch jusqu'à atteindre exactement la limite. dd a rendu le code 1, alors que df -i montrait encore 62 inodes disponibles : les octets étaient épuisés, pas les inodes.

Fenêtre de terminal
if docker exec c35b-tmpfs-space sh -c \
'dd if=/dev/zero of=/scratch/discard.bin bs=64k count=40'; then
echo 'fill_exit=0'
else
printf 'fill_exit=%s\n' "$?"
fi
docker exec c35b-tmpfs-space df -h /scratch
docker exec c35b-tmpfs-space df -i /scratch
docker exec c35b-tmpfs-space rm /scratch/discard.bin
docker exec c35b-tmpfs-space df -h /scratch
docker exec c35b-tmpfs-space sh -c \
'dd if=/dev/zero of=/scratch/new-write.bin bs=64k count=1'
docker exec c35b-tmpfs-space stat -c 'new_write_bytes=%s' /scratch/new-write.bin
printf 'useful_volume_data='
docker exec c35b-tmpfs-space cat /persistent/inventory
printf 'same_container_id='
test "$(docker inspect c35b-tmpfs-space --format '{{.Id}}')" = "$SPACE_ID" && echo yes
docker inspect c35b-tmpfs-space --format 'tmpfs_after={{json .HostConfig.Tmpfs}}'
dd: error writing '/scratch/discard.bin': No space left on device
33+0 records in
32+0 records out
2097152 bytes (2.0MB) copied, 0.000495 seconds, 3.9GB/s
fill_exit=1
Filesystem Size Used Available Use% Mounted on
tmpfs 2.0M 2.0M 0 100% /scratch
Filesystem Inodes Used Available Use% Mounted on
tmpfs 64 2 62 3% /scratch
Filesystem Size Used Available Use% Mounted on
tmpfs 2.0M 0 2.0M 0% /scratch
1+0 records in
1+0 records out
65536 bytes (64.0KB) copied, 0.000071 seconds, 880.3MB/s
new_write_bytes=65536
useful_volume_data=inventory-line
same_container_id=yes
tmpfs_after={"/scratch":"rw,size=2m,nr_inodes=64"}

Après rm, /scratch est repassé à 0 octet utilisé et une nouvelle écriture de 65 536 octets a réussi. L'identifiant du conteneur est resté inchangé, son plafond de 2 Mio n'a pas bougé et le volume nommé est resté lisible. Retirer le fichier jetable a suffi, sans recréer le conteneur.

Le cas inverse existe : un système de fichiers peut manquer d'inodes alors que des octets restent libres. Dans un second tmpfs de 8 Mio limité à huit inodes, sept fichiers ont été créés, puisque le répertoire racine du tmpfs consomme lui-même un inode ; le huitième a échoué, tandis que df -h affichait toujours 8 Mio disponibles.

Fenêtre de terminal
docker run -d --name c35b-tmpfs-inodes --network none \
--tmpfs /small:rw,size=8m,nr_inodes=8 \
"$IMAGE" sleep 7200
docker exec c35b-tmpfs-inodes sh -c \
'i=0; while [ "$i" -lt 32 ]; do if ! touch "/small/f$i"; then printf "create_failed_at=%s\n" "$i"; break; fi; i=$((i+1)); done; printf "files_created=%s\n" "$i"; df -h /small; df -i /small'
touch: /small/f7: No space left on device
create_failed_at=7
files_created=7
Filesystem Size Used Available Use% Mounted on
tmpfs 8.0M 0 8.0M 0% /small
Filesystem Inodes Used Available Use% Mounted on
tmpfs 8 8 0 100% /small

Dans la sortie, df -h annonce 8,0 M disponibles, et df -i montre 8 inodes utilisés sur 8. Quand df -h montre de l'espace libre mais que la création d'un fichier échoue, regardez donc aussi df -i. Ne remplissez jamais une partition de l'hôte pour reproduire un disque plein : utilisez un système de fichiers borné.

Quand le plein est sur l'hôte lui-même, la démarche ne change pas, mais les outils sont ceux de Linux. Le guide Contrôler l'espace disque et les inodes détaille df, du et la lecture des inodes. Il existe aussi un piège que cette leçon n'a pas mesuré : un fichier supprimé reste sur le disque tant qu'un processus le garde ouvert, si bien que du compte moins d'octets que df. Le guide Contrôler capacité et quotas disque montre comment retrouver ce processus.

Retirez d'abord les ressources Docker créées pour la leçon. Vérifiez que le dossier ~/journaux-disque ne contient que les fichiers de cette démonstration avant de supprimer son contenu. Le démon secondaire est déjà arrêté depuis la section sur la mesure de l'espace.

Fenêtre de terminal
IMAGE="alpine:3.23@sha256:ff7f0c727dd796aa36787bfcd9a64bf35a6ce72a73451b3359fde849b6703d63"
IMAGE="$IMAGE" docker compose -p c35b-log-compose \
-f "$HOME/journaux-disque/compose/compose.yaml" down
for name in c35b-log-open c35b-log-capped c35b-log-local c35b-data-holder \
c35b-stop-unrelated c35b-tmpfs-space c35b-tmpfs-inodes; do
docker rm -f "$name"
done
docker volume rm c35b-data-ref
docker image rm "$IMAGE"
cd ~
find "$HOME/journaux-disque" -depth -delete

Cette séquence a supprimé uniquement les conteneurs, le volume, l'image et le dossier créés pour la leçon. Les ressources qui existaient avant la leçon restent en place : chaque commande vise un nom, jamais une catégorie entière.

Le premier lab demande de borner le journal d'un service identifié. Ses tests contrôlent le pilote et les options déclarées, puis la rotation effective : les lignes les plus anciennes doivent avoir été jetées et le volume retenu doit tenir dans le budget que ces bornes définissent. Le service doit aussi rester actif. Il utilise des valeurs et un conteneur différents de ceux de la démonstration.

Le second lab fait diagnostiquer un espace borné et retirer un fichier jetable. Ses tests vérifient que le conteneur d'origine et les données utiles restent en place, que l'espace borné reste un tmpfs de 16 Mio sans avoir été agrandi, que le fichier jetable a disparu, puis qu'une nouvelle écriture réussit.

Une reprise fiable commence par l'identification de la ressource pleine et de son propriétaire.

  • Vous vérifiez la rotation sur les fichiers de l'hôte, pas seulement dans HostConfig.LogConfig, et vous savez que les lignes au-delà de max-file sont perdues.
  • Les options de journal se fixent à la création du conteneur, et les log-opts de daemon.json ne valent que pour les nouveaux conteneurs : vous recréez un conteneur pour qu'il les hérite.
  • Vous inspectez les catégories avec docker system df avant toute suppression. Ni RECLAIMABLE ni le Total de prune ne donnent l'espace rendu : le premier ignore les couches partagées, le second additionne des entrées. Seul df, relevé avant et après, le mesure.
  • Vous filtrez les conteneurs arrêtés et vous supprimez un volume par son nom seulement après avoir vérifié ses données. docker system prune agit sur tout le démon, et --volumes peut effacer un volume anonyme qui porte des données.
  • Vous lisez df -h et df -i ensemble avant de conclure qu'un système de fichiers est plein : les octets et les inodes s'épuisent séparément.
  • Vous libérez un fichier jetable sans recréer le conteneur quand son plafond reste inchangé.

Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent sur les choix de configuration, le diagnostic d'un disque plein et la portée des nettoyages.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn