Les pires incidents Docker ne semblent pas dramatiques au départ. Un conteneur « fonctionne » jusqu’à ce qu’il ne fonctionne plus, et quand il échoue il échoue souvent en mode latéral :
la latence augmente, les tentatives se multiplient, les disques se remplissent, les nœuds commencent à swaper, puis tout devient « sans rapport » jusqu’à ce que tout soit lié.
La solution n’est pas « ajouter plus de tableaux de bord ». La solution est un minimum impitoyable : un petit ensemble de métriques et de logs qui s’allument de manière fiable avant que les utilisateurs ne se plaignent,
et un moyen d’interroger un hôte en quelques minutes quand les tableaux de bord mentent ou manquent.
Le signal minimum : quoi observer et pourquoi
« Observabilité » dans l’univers des conteneurs est souvent vendue comme un buffet à volonté : traces, profiles, eBPF, cartes de services, jolis flame graphs,
détection d’anomalies par IA et un tableau de bord avec douze nuances de rouge. En production, l’observabilité viable minimale est plus simple :
détecter les modes de défaillance imminents tôt, avec des signaux difficiles à falsifier et peu coûteux à collecter.
Pour Docker sur un hôte unique ou une flotte, vous jonglez avec trois couches qui échouent chacune différemment :
- Couche application : erreurs de requête, latence, tentatives, timeouts, profondeur de file d’attente. C’est là que les utilisateurs vocifèrent.
- Couche conteneur : redémarrages, OOM kills, throttling CPU, épuisement des descripteurs de fichiers, volume de logs.
- Couche hôte : remplissage disque/inodes, saturation IO, pression mémoire/swap, épuisement de conntrack, problèmes noyau.
L’ensemble minimum doit vous donner un avertissement précoce pour les cas prévisibles : disque qui se remplit, fuite mémoire, throttling CPU, boucle de crash,
et les tueurs silencieux comme l’épuisement d’inodes et le conntrack. Si vous maîtrisez ces éléments, vous empêchez la plupart des pannes « c’était bien hier ».
Règles du minimum
- Privilégiez les indicateurs avancés. « Disque 98% plein » est un indicateur retardé. « Disque qui croît de 2% par heure » est un indicateur avancé.
- Choisissez une source de vérité par chose. Pour le CPU, utilisez l’usage CPU des cgroups et le throttling. Ne mélangez pas cinq définitions de « CPU% ».
- Gardez la cardinalité sous contrôle. Étiquetez chaque métrique par ID de conteneur et vous allez DoS votre monitoring avec succès.
- Alarmez sur des symptômes sur lesquels vous pouvez agir. « Load average haut » est triviaux sauf s’il est lié au steal CPU, à l’IO wait ou au throttling.
Il existe deux types d’équipes : celles qui alertent sur « conteneur redémarré » et celles qui aiment être surprises à 2h du matin.
Aussi, une vérité sèche : si votre pile d’« observabilité » tombe chaque fois que le cluster est malheureux, ce n’est pas de l’observabilité ; c’est de la décoration.
Votre minimum doit être accessible depuis l’hôte avec un shell.
Faits intéressants et contexte historique (les parties qui font encore mal)
- Les cgroups ont été introduits dans Linux en 2007. Docker n’a pas inventé l’isolation des ressources ; il l’a empaquetée et l’a rendue facile à mal utiliser.
- Le driver de logs Docker par défaut d’origine était json-file. C’est pratique, mais sur des services chargés ça peut devenir votre mangeur de disque furtif.
- OverlayFS (overlay2) est devenu le driver de stockage par défaut pour de nombreuses distributions vers 2016–2017. C’est assez rapide, mais « où est passé mon disque ? » est devenu une question hebdomadaire.
- Le comportement de l’OOM killer précède les conteneurs de plusieurs décennies. Les conteneurs ont juste rendu plus facile d’atteindre les limites mémoire et plus difficile de voir pourquoi sans les bons signaux.
- Conntrack est une fonctionnalité netfilter de Linux depuis les noyaux 2.4. Quand le NAT et beaucoup de connexions de courte durée rencontrent des conteneurs, conntrack devient un élément de planification de capacité, pas une note de bas de page.
- Les HEALTHCHECK dans Dockerfile ont été ajoutés après des douleurs en production. Avant cela, « le conteneur tourne » était traité comme « le service est sain », ce qui est adorable.
- systemd-journald impose des limites de débit pour une raison. Le logging est de l’IO ; l’IO est de la latence ; la latence devient des retries ; les retries deviennent une tempête. Les logs peuvent vous faire tomber.
- Le throttling CPU n’est pas « le CPU est élevé ». C’est « vous avez demandé du CPU et le noyau a dit non ». Cette distinction est devenue plus visible avec la conteneurisation généralisée.
Ensemble minimal de métriques (par mode de panne)
Ne commencez pas par « collecter tout ». Commencez par la manière dont les systèmes Docker meurent réellement. Ci‑dessous un minimum qui détecte les pannes tôt
et vous donne une direction de débogage. Cela suppose que vous pouvez collecter des métriques hôte (node exporter ou équivalent), des métriques conteneur
(cAdvisor ou API Docker), et des métriques applicatives de base (statistiques HTTP/gRPC). Même si vous ne lancez pas Prometheus, les concepts restent valables.
1) Boucles de crash et mauvais déploiements
- Taux de redémarrage des services (par service, pas par ID de conteneur). Alerter sur des redémarrages soutenus pendant 5–10 minutes.
- Distribution des codes de sortie. Le code 137 est généralement un OOM kill ; le code 1 est souvent un crash applicatif ; le 143 est SIGTERM (souvent normal lors d’un déploiement).
- Échecs de healthcheck (si vous utilisez HEALTHCHECK). Alarmer avant les redémarrages, car l’échec du healthcheck est votre indicateur avancé.
Décision que vous voulez permettre : rollback ou arrêter l’hémorragie. Si le taux de redémarrage augmente après un déploiement, n’« attendez pas que ça se calme ».
Ça ne se calmera pas. Les conteneurs ne sont pas des plantes d’intérieur.
2) Fuites mémoire, pression mémoire et OOM kills
- Working set mémoire du conteneur (pas seulement RSS, et pas le cache à moins que vous sachiez ce que vous faites).
- Événements OOM kill au niveau de l’hôte et du conteneur.
- Mémoire disponible de l’hôte et swap in/out. L’activité de swap est le film catastrophe au ralenti.
Seuils d’alerte : working set approchant la limite (par ex., > 85 % pendant 10 minutes), OOM kills > 0 (alerte immédiate), taux de swap-in > 0 soutenu (avertissement).
3) Disque : le générateur d’incidents n°1 ennuyeux
- % libre du système de fichiers hôte pour la racine Docker (souvent
/var/lib/docker) et pour les systèmes de fichiers de logs. - Octets écrits/sec et utilisation IO (await, signaux svctm selon l’outil).
- % d’inodes libres. Vous pouvez être « 50 % libre » et pourtant mort parce que vous avez manqué d’inodes.
- Croissance des images Docker + couche écritable des conteneurs (si vous pouvez la suivre). Sinon, suivez le taux de croissance de
/var/lib/docker.
Le modèle d’alerte qui fonctionne vraiment : alerter sur le temps-avant-saturation (basé sur le taux de croissance) plutôt que sur le pourcentage brut.
« Le disque sera plein dans 6 heures » déclenche une action. « Disque à 82 % » est ignoré jusqu’à 99 %.
4) CPU : usage élevé vs throttling
- Usage CPU du conteneur (cœurs ou secondes/sec).
- Temps CPU throttlé et périodes throttlées du conteneur. C’est le métrique « on est en pénurie ».
- iowait CPU de l’hôte. Si l’iowait augmente avec la latence, votre goulot d’étranglement est le disque, pas le CPU.
Si l’usage CPU est modéré mais que le throttling est élevé, vous avez fixé des limites trop basses ou trop de conteneurs sur le nœud. Les utilisateurs le perçoivent comme
des pics de latence aléatoires. Les ingénieurs le diagnostiquent mal comme « instabilité réseau » parce que les graphiques ont l’air corrects.
5) Réseau : quand « c’est le DNS » est en fait conntrack
- Retransmissions TCP et erreurs de socket sur l’hôte.
- Utilisation de la table conntrack (% utilisée).
- Taux d’erreurs DNS de votre résolveur (SERVFAIL, timeout). Le DNS est souvent une victime, pas la cause.
6) « Signaux d’or » applicatifs (ceux qui valent la peine d’être câblés)
- Taux de requêtes, taux d’erreur, latence (p50/p95/p99), saturation (profondeur de file, utilisation des workers).
- Taux d’erreurs des dépendances (BD, cache, API en amont). Les incidents Docker se manifestent souvent comme des tempêtes de dépendances.
Alertes minimales qui ne vous feront pas haïr votre pager
- Temps-avant-plein disque < 12h (avertissement), < 2h (page)
- Temps-avant-zero inodes < 12h (avertissement), < 2h (page)
- Événement OOM kill (page)
- Boucle de redémarrages : redémarrages/minute > baseline pendant 10m (page)
- Ratio temps CPU throttlé > 10% pendant 10m (avertissement), > 25% pendant 5m (page) pour services sensibles à la latence
- Swap-in hôte soutenu (avertissement), pic du taux de fautes de page majeur (avertissement/page selon le cas)
- Utilisation conntrack > 80% pendant 10m (avertissement), > 90% (page)
- Latence app p99 + taux d’erreur se dégradent tous les deux (page). L’un sans l’autre est généralement du bruit.
Ensemble minimal de logs (et comment ne pas se noyer)
Les métriques vous disent qu’il y a un problème. Les logs vous disent quel type de problème. La stratégie minimale de logging n’est pas « tout logger ».
C’est « logger les événements qui expliquent les transitions d’état et les échecs, et les garder assez longtemps pour déboguer ».
stdout/stderr du conteneur : traitez-le comme une interface produit
Dans Docker, stdout/stderr est le chemin de logs le plus pratique et le plus facile à abuser. Si votre appli logge du JSON, bien. Si elle logge des traces de pile
et des corps de requête complets, aussi bien—jusqu’à ce que ça ne le soit plus. Votre minimum :
- Logs structurés (JSON de préférence) avec timestamp, niveau, request ID et champs d’erreur.
- Bannière de démarrage incluant la version, le checksum de config et les ports à l’écoute.
- Résumés en une ligne pour les échecs de requête et les échecs de dépendance.
- Logs bruyants limités en débit (timeouts, retries). Les logs répétés doivent être agrégés, pas spammés.
Logs du daemon Docker et de l’hôte : le côté ennuyeux qui explique tout
- Logs dockerd : pulls d’images, erreurs de graphdriver, échecs d’exec, problèmes containerd.
- Logs noyau : messages de l’OOM killer, erreurs de système de fichiers, pertes réseau.
- journald/syslog : redémarrages de services, échecs d’unités, avertissements de limitation de débit.
Rétention et rotation des logs : choisissez une politique, pas un espoir
Si vous gardez des logs json-file sans rotation, vous finirez par remplir le disque. Ce n’est pas un « peut‑être ». C’est de la physique.
Blague n°1 : Les logs Docker non tournés sont comme un tiroir à bazar—ça va jusqu’au moment où vous essayez de le fermer et la cuisine cesse de fonctionner.
Politique minimale pour json-file :
- Définir max-size et max-file globalement.
- Privilégier l’envoi externe des logs (journald, fluentd, ou un sidecar/agent) si vous avez besoin d’une rétention plus longue.
- Alerter sur la croissance des logs de la même manière que sur la croissance du disque. Les logs sont juste des écritures disque avec des opinions.
Tâches pratiques : commandes, sorties, décisions (12+)
Les tableaux de bord sont super… jusqu’à ce qu’ils ne le soient plus. Quand un hôte est en feu, vous avez besoin d’un petit ensemble de commandes qui vous disent ce qui échoue :
CPU, mémoire, disque, réseau, Docker lui‑même, ou votre appli. Ci‑dessous des tâches que vous pouvez exécuter sur n’importe quel hôte Docker.
Chacune inclut : commande, sortie représentative, ce que ça signifie, et la décision à prendre.
Tâche 1 : Confirmer le périmètre affecté (ce qui tourne, ce qui redémarre)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES IMAGE STATUS PORTS
api-7c9d registry/app:1.8.2 Up 3 minutes (healthy) 0.0.0.0:8080->8080/tcp
worker-2a11 registry/worker:1.8.2 Restarting (1) 12 seconds ago
redis-01 redis:7 Up 14 days 6379/tcp
Ce que ça signifie : Un conteneur est en boucle de redémarrage. « Restarting (1) » n’est pas une sensation ; c’est un symptôme.
Décision : Trier immédiatement la cause du redémarrage (code de sortie, logs, OOM). Ne poursuivez pas le diagnostic de la latence ailleurs tant que la boucle de redémarrage n’est pas comprise.
Tâche 2 : Voir pourquoi un conteneur est mort (code de sortie + indice OOM)
cr0x@server:~$ docker inspect -f 'Name={{.Name}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} FinishedAt={{.State.FinishedAt}}' worker-2a11
Name=/worker-2a11 ExitCode=137 OOMKilled=true Error= FinishedAt=2026-01-03T00:12:41.981234567Z
Ce que ça signifie : Exit 137 avec OOMKilled=true indique que le conteneur a été tué par le noyau/cgroup pour violation de la limite mémoire.
Décision : Cessez de deviner des « crashes aléatoires ». Augmentez la limite mémoire (avec prudence), réduisez la concurrences, ou corrigez la fuite. Vérifiez aussi la pression mémoire de l’hôte.
Tâche 3 : Récupérer les derniers logs sans se noyer
cr0x@server:~$ docker logs --tail=80 --timestamps worker-2a11
2026-01-03T00:12:35.112Z level=info msg="starting worker" version="1.8.2" concurrency=64
2026-01-03T00:12:39.003Z level=error msg="failed processing job" err="context deadline exceeded" job_id=91311
2026-01-03T00:12:41.978Z level=info msg="shutdown requested"
Ce que ça signifie : Les logs montrent un démarrage et quelques timeouts, mais rien sur l’OOM (parce que l’OOM est hors du processus).
Décision : Corréler avec les logs noyau pour l’OOM. N’attendez pas que l’application avoue.
Tâche 4 : Confirmer les OOM kills dans le journal du noyau
cr0x@server:~$ sudo dmesg -T | tail -n 30
[Sat Jan 3 00:12:41 2026] Memory cgroup out of memory: Killed process 28144 (worker) total-vm:2145932kB, anon-rss:612344kB, file-rss:1200kB, shmem-rss:0kB
[Sat Jan 3 00:12:41 2026] oom_reaper: reaped process 28144 (worker), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Ce que ça signifie : Le noyau a tué le processus à cause de la limite mémoire du cgroup. C’est définitif.
Décision : Traitez comme un bug de capacité/limite/config. Ajustez les limites ou corrigez le comportement mémoire de l’appli ; ajoutez une alerte sur les événements OOMKill.
Tâche 5 : Vérifier la pression mémoire de l’hôte (le prélude « tout le monde est lent »)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 31Gi 28Gi 620Mi 512Mi 2.4Gi 1.1Gi
Swap: 4.0Gi 1.9Gi 2.1Gi
Ce que ça signifie : La mémoire disponible est faible et le swap est utilisé. Si le swap change activement, la latence augmente probablement.
Décision : Réduire la pression mémoire : déplacer des charges, augmenter la RAM hôte, limiter les caches, ajuster les limites. Considérez le risque de pagination comme digne d’un incident.
Tâche 6 : Identifier les plus gros consommateurs mémoire par conteneur
cr0x@server:~$ docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}'
NAME MEMUSAGE / LIMIT MEMPERC CPUPERC
api-7c9d 420MiB / 1GiB 41.02% 85.31%
worker-2a11 980MiB / 1GiB 95.70% 12.01%
redis-01 210MiB / 2GiB 10.25% 1.10%
Ce que ça signifie : Le worker atteint la limite mémoire. Il va de nouveau OOM.
Décision : Réduire la concurrence du worker, ajouter du backpressure, ou augmenter la limite. Si vous augmentez la limite, vérifiez d’abord la marge disponible sur l’hôte.
Tâche 7 : Vérifier l’occupation disque là où Docker vit
cr0x@server:~$ df -h /var/lib/docker
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 200G 189G 11G 95% /
Ce que ça signifie : La racine Docker est à 95 %. C’est un « incident imminent », pas « ça va ».
Décision : Commencez le nettoyage (images non utilisées, conteneurs arrêtés, cache de build). Identifiez aussi la source de croissance (logs, overlay2, volumes).
Tâche 8 : Trouver l’épuisement d’inodes (la panne disque sournoise)
cr0x@server:~$ df -i /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 13107200 13010000 97200 100% /
Ce que ça signifie : L’espace disque peut exister, mais les inodes sont quasiment épuisés. Créer des fichiers échouera ; les conteneurs peuvent se casser de façon étrange.
Décision : Identifier les répertoires avec un grand nombre de fichiers (souvent cache de build, couches unpacked, ou petits fichiers de logs). Nettoyer et envisager un réglage du système de fichiers.
Tâche 9 : Mesurer ce qui bouffe le disque Docker (volumes vs layers vs logs)
cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
1.2G /var/lib/docker/containers
6.8G /var/lib/docker/volumes
29G /var/lib/docker/buildkit
150G /var/lib/docker/overlay2
189G /var/lib/docker
Ce que ça signifie : overlay2 est énorme, le build cache est non négligeable, et le répertoire containers (logs) est plutôt petit ici.
Décision : Si overlay2 domine, cherchez des couches écrites volumineuses et une prolifération d’images. Si buildkit est gros, prunez-le. Si containers est gros, corrigez la rotation des logs.
Tâche 10 : Pruner en toute sécurité (et comprendre le périmètre)
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 9 68.2GB 52.4GB (76%)
Containers 21 12 3.4GB 1.1GB (32%)
Local Volumes 18 11 6.8GB 1.9GB (27%)
Build Cache 163 0 29.0GB 29.0GB
Ce que ça signifie : Il y a beaucoup d’espace récupérable, surtout le build cache et des images anciennes.
Décision : Utilisez d’abord un pruning ciblé. Évitez de supprimer des volumes à l’aveugle à moins d’en être certain.
cr0x@server:~$ docker builder prune -f
Deleted build cache objects:
3yq9m3c2kz7qf2n2o6...
Total reclaimed space: 28.7GB
Ce que ça signifie : Le build cache a été nettoyé avec succès.
Décision : Revérifiez le disque. Si c’est encore élevé, prunez les images inutilisées avec précaution.
Tâche 11 : Vérifier la taille des fichiers de log par conteneur (driver json-file)
cr0x@server:~$ sudo ls -lh /var/lib/docker/containers/*/*-json.log | sort -k5 -h | tail -n 5
-rw-r----- 1 root root 1.2G Jan 3 00:10 /var/lib/docker/containers/9c1.../9c1...-json.log
-rw-r----- 1 root root 2.8G Jan 3 00:11 /var/lib/docker/containers/aa4.../aa4...-json.log
-rw-r----- 1 root root 3.5G Jan 3 00:11 /var/lib/docker/containers/b7d.../b7d...-json.log
-rw-r----- 1 root root 6.1G Jan 3 00:12 /var/lib/docker/containers/cc9.../cc9...-json.log
-rw-r----- 1 root root 9.4G Jan 3 00:12 /var/lib/docker/containers/f01.../f01...-json.log
Ce que ça signifie : Un ou plusieurs conteneurs génèrent des logs énormes. Cela peut remplir le disque et aussi ralentir l’hôte à cause de l’IO.
Décision : Corrigez la verbosité des logs et configurez la rotation. En urgence, tronquez prudemment le pire coupable (en acceptant la perte).
Tâche 12 : Vérifier la configuration de rotation des logs Docker (et l’appliquer)
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
Ce que ça signifie : Les logs json-file vont tourner à 50MB, en conservant 5 fichiers. C’est le minimum pour éviter la mort par logs.
Décision : Si absent, ajoutez-le et redémarrez Docker pendant une fenêtre de maintenance. Si présent mais que c’est encore énorme, vos conteneurs peuvent précéder le réglage ; recréez-les.
Tâche 13 : Détecter le throttling CPU (le signe « limites trop serrées »)
cr0x@server:~$ CID=$(docker ps -qf name=api-7c9d); docker inspect -f '{{.Id}}' $CID
b3e2f0a1c9f7f4c2b3a0d0c2d1e9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f
cr0x@server:~$ cat /sys/fs/cgroup/cpu/docker/$CID/cpu.stat
nr_periods 124030
nr_throttled 38112
throttled_time 921837239112
Ce que ça signifie : Un nr_throttled élevé et un throttled_time important indiquent que le conteneur voulait souvent du CPU mais a été throttlé.
Décision : Augmentez la limite CPU, réduisez le nombre de conteneurs par nœud, ou optimisez les hotspots CPU. Si les pics de latence corrèlent, le throttling est votre preuve accablante.
Tâche 14 : Vérifier la pression IO de l’hôte (quand « tout devient lent »)
cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 01/03/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.21 0.00 6.02 21.33 0.00 54.44
Device r/s w/s rkB/s wkB/s await aqu-sz %util
nvme0n1 85.1 410.2 8120.0 65211.1 18.4 2.31 94.7
Ce que ça signifie : Iowait élevé et %util proche de la saturation. Le disque est le goulot ; le CPU attend l’IO.
Décision : Réduire l’amplification d’écriture (logs, flushs BD), déplacer des charges, ou scaler l’IO. N’« ajoutez pas de CPU ». Ça n’aidera pas.
Tâche 15 : Vérifier la saturation de conntrack (le générateur de timeouts réseau aléatoires)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 245112
net.netfilter.nf_conntrack_max = 262144
Ce que ça signifie : Conntrack est presque plein. De nouvelles connexions peuvent échouer de manière intermittente et vilaine.
Décision : Augmentez le max conntrack (en tenant compte de la mémoire), réduisez le churn de connexions, ajustez les timeouts, et vérifiez les tempêtes de retries.
Blague n°2 : Conntrack est l’endroit où les connexions vont être rappelées pour toujours, ce qui est romantique jusqu’à ce que votre nœud manque de mémoire.
Playbook de diagnostic rapide : premières/deuxièmes/troisièmes vérifications
Quand la latence monte ou que les erreurs explosent, vous devez répondre vite à une question : quelle ressource est saturée ou échoue en premier ?
Voici un playbook qui fonctionne même quand votre monitoring est en retard, absent, ou mensonger.
Première vérification : Est-ce une boucle de crash, un OOM, ou une régression de déploiement ?
- docker ps : cherchez Restarting, unhealthy, ou conteneurs récemment démarrés.
- docker inspect : codes de sortie, flag OOMKilled, statut de santé.
- docker logs –tail : 50–200 dernières lignes pour erreurs fatales et config de démarrage.
Décision rapide : Si vous voyez des redémarrages augmenter après un changement, geler les déploiements et rollbacker. Le débogage vient après la stabilité.
Deuxième vérification : Pression disque et inodes (parce que ça casse tout)
- df -h sur
/var/lib/dockeret les montages de logs. - df -i pour l’épuisement des inodes.
- du pour trouver quel sous‑répertoire Docker croît.
Décision rapide : Si disque > 90 % et en croissance, commencez à récupérer de l’espace immédiatement. C’est l’un des rares cas où le « nettoyage » est une réponse d’incident valide.
Troisième vérification : Saturation des ressources hôtes (mémoire, IO, throttling CPU)
- free -h et activité swap : confirme la pression mémoire.
- iostat -xz : montre la saturation IO et l’iowait.
- cpu.stat throttling : confirme que les limites CPU sont trop faibles.
Décision rapide : La saturation signifie qu’il faut réduire la charge, déplacer des conteneurs, ou changer les limites—déboguer l’appli ne réparera pas la physique.
Quatrième vérification : Épuisement des tables réseau et symptômes DNS
- compte/max conntrack : être proche du plein cause des timeouts.
- ss -s (non montré ci‑dessus) : états des sockets ; beaucoup de TIME-WAIT peut indiquer du churn.
- logs applicatifs : timeouts vers les dépendances ; corrélez avec conntrack et retransmissions.
Cinquième vérification : Santé du daemon Docker
- logs dockerd : erreurs du driver de stockage, échecs de pull, messages « no space left ».
- docker info : confirme driver de stockage, driver cgroup, répertoire racine.
Une citation, parce que c’est toujours la meilleure façon de cadrer le travail d’incident :
L’espoir n’est pas une stratégie.
— James Cameron
Trois mini-histoires du monde corporate
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne exploitait un ensemble d’hôtes Docker pour des API internes. L’équipe supposait que les conteneurs étaient « assez isolés » et traitait l’hôte comme un substrat stupide.
Ils avaient des alertes pour les 5xx HTTP et le CPU de base. Disque ? Ils le regardaient « parfois ».
Un vendredi, une nouvelle fonctionnalité a été déployée avec des logs debug verbeux laissés activés par erreur. Le service est resté up ; les taux d’erreur étaient normaux. Mais les logs ont crû rapidement,
et au petit matin le système de fichiers racine Docker était presque plein. Le premier symptôme n’était pas « disque plein ». C’était des déploiements plus lents, car les pulls d’images
et les extractions de couches se sont mis à thrash. Ensuite les clients de la base de données ont commencé à timeout parce que l’IO du nœud était saturé.
L’équipe a passé des heures à se disputer sur la « instabilité réseau » parce que les pings étaient corrects et le CPU n’était pas saturé. Finalement quelqu’un a lancé df -h et a vu 99 % utilisé.
Ils ont tronqué un énorme log json et ont immédiatement vu le service récupérer.
Après l’incident, l’hypothèse erronée était évidente : « Si l’appli est saine, l’hôte est sain. » Docker n’empêche pas le disque de se remplir ; il vous donne juste plus
d’endroits pour cacher l’utilisation du disque. La correction était aussi évidente : alertes sur la croissance disque et rotation des logs par défaut, appliquées via la gestion de configuration.
Mini-histoire 2 : L’optimisation qui a mal tourné
Une autre organisation voulait un meilleur bin-packing et réduire les coûts cloud. Ils ont serré les limites CPU pour plusieurs services sensibles à la latence,
en raisonnant que l’usage moyen du CPU était bas. Ils ont obtenu le gain de coût. Puis ils ont obtenu les graphiques.
Après le changement, les clients ont signalé une « lenteur sporadique ». Les services n’étaient pas down. Les taux d’erreur étaient modestes. L’on‑call voyait l’usage CPU
autour de 40–50 % et a déclaré le nœud sain. Pendant ce temps la latence p99 a doublé pendant les pics et est ensuite « mystérieusement » revenue à la normale.
Le vrai coupable était le throttling CPU. Les charges en rafales frappaient leurs limites et étaient throttlées précisément quand elles avaient besoin de brefs bursts pour maintenir les files vides.
L’OS a fait exactement ce qu’on lui avait demandé. L’équipe n’avait pas de métriques de throttling, donc c’était un fantôme.
L’optimisation qui a mal tourné n’était pas « les limites sont mauvaises ». C’était « des limites sans visibilité sur le throttling sont mauvaises ». Ils ont ajouté des alertes sur le temps throttlé,
augmenté les limites CPU pour quelques services, et ajusté la concurrence pour correspondre à ce que les conteneurs étaient réellement autorisés à faire.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une plateforme liée à la finance exploitait des hôtes Docker avec une base stricte : rotation des logs définie dans /etc/docker/daemon.json, nettoyage nocturne du cache de build,
et alertes sur le temps-avant-saturation pour /var/lib/docker. Personne n’en parlait. C’était juste « la règle ».
Lors d’un pic de fin de trimestre, un service worker a commencé à produire beaucoup plus de logs que d’habitude à cause d’un timeout d’une dépendance en amont. Le service était bruyant,
mais n’a pas fait tomber l’hôte. Les logs ont tourné. L’utilisation disque a augmenté, mais l’alerte temps-avant-saturation s’est déclenchée assez tôt pour enquêter sans panique.
L’équipe a tracé le vrai problème jusqu’à une dépendance et a corrigé la tempête de retries. L’important est ce qui ne s’est pas passé : l’hôte Docker n’a pas atteint 100 % de disque,
les conteneurs n’ont pas commencé à échouer à écrire des fichiers temporaires, et l’incident n’est pas devenu une cascade.
« Ennuyeux mais correct » ne fait pas des postmortems passionnants. Ça évite d’avoir des postmortems.
Erreurs courantes : symptôme → cause racine → correctif
1) Symptôme : Les conteneurs redémarrent toutes les quelques minutes, pas d’erreur applicative claire
Cause racine : OOM kills dus à une limite mémoire trop basse ou une fuite mémoire ; les logs de l’appli ne le montrent pas.
Correctif : Confirmer via docker inspect OOMKilled et dmesg noyau. Augmenter la limite ou réduire la concurrence ; ajouter des alertes OOM et suivre le working set.
2) Symptôme : « No space left on device » mais df montre de l’espace libre
Cause racine : Épuisement d’inodes ou blocs réservés par filesystem ; parfois pression metadata overlay2.
Correctif : Vérifier df -i. Supprimer des répertoires à fort nombre de fichiers (cache de build, fichiers temporaires), pruner les artefacts Docker, régler le ratio d’inodes si approprié.
3) Symptôme : Pics de latence aléatoires, le CPU semble correct
Cause racine : Throttling CPU dû à des limites strictes ; le conteneur veut du CPU mais on lui refuse.
Correctif : Surveiller le temps throttlé. Augmenter la limite CPU ou réduire la concurrence ; cesser d’utiliser seulement « CPU% » comme signal de capacité.
4) Symptôme : Déploiements bloquent ou les pulls d’images sont lents, puis les services se dégradent
Cause racine : Saturation IO disque ou thrash du driver de stockage Docker ; souvent couplé à un disque presque plein.
Correctif : Utiliser iostat et vérifier l’espace disque. Réduire l’IO (logs, rebuild storms), ajouter de la marge disque, et éviter les builds lourds sur les nœuds prod.
5) Symptôme : Timeouts réseau aléatoires sur beaucoup de conteneurs, surtout en sortie
Cause racine : Table conntrack proche du plein ; trafic NAT avec churn élevé.
Correctif : Vérifier le compte/max conntrack ; ajuster nf_conntrack_max et les timeouts ; réduire le churn de connexions (keepalives, pooling), et éviter les tempêtes de retries.
6) Symptôme : Les logs d’un conteneur sont gigantesques ; le disque du nœud continue à se remplir
Cause racine : Logs json-file sans rotation ; verbosité excessive ; ou boucles d’erreur répétées.
Correctif : Configurer la rotation des logs Docker ; réduire le volume des logs ; ajouter une limitation de débit ; expédier les logs hors hôte si une rétention est nécessaire.
7) Symptôme : Le conteneur est « Up » mais le service est mort ou renvoie des erreurs
Cause racine : Pas de healthcheck, ou healthcheck trop faible (vérifie le processus mais pas la fonctionnalité).
Correctif : Ajouter un HEALTHCHECK qui valide la connectivité aux dépendances ou un vrai endpoint readiness ; alerter sur le statut unhealthy avant les redémarrages.
8) Symptôme : L’utilisation disque croît mais le pruning n’aide pas beaucoup
Cause racine : Volumes qui accumulent des données, ou application écrivant dans la couche écrivable du conteneur ; pas seulement des images inutilisées.
Correctif : Identifier avec docker system df et du du filesystem. Déplacer les écritures vers des volumes avec gestion de cycle de vie ; implémenter des politiques de rétention.
Checklists / plan étape par étape
Plan d’implémentation d’observabilité minimale (une semaine, réaliste)
- Choisir les alertes minimales (temps-avant-plein disque, temps-avant-zero inodes, OOM kills, boucles de redémarrage, throttling CPU, saturation conntrack, latence+erreurs applicatives).
- Standardiser le logging Docker : définir
max-size/max-filedans/etc/docker/daemon.json; appliquer via la gestion de configuration. - Étiqueter les services proprement : métriques étiquetées par service et environnement, pas par ID de conteneur. Garder l’ID de conteneur pour le debug, pas pour l’alerte.
- Exposer les signaux d’or applicatifs : taux de requêtes, taux d’erreur, percentiles de latence, saturation. Si vous ne pouvez faire qu’une chose, faites taux d’erreur + p99 latence.
- Collecter les métriques hôte pour disque, inodes, IO, mémoire, swap, conntrack. Sans métriques hôte, les métriques conteneur vous gaslighteront.
- Collecter les métriques conteneur (usage CPU, throttling, working set mémoire, redémarrages). Valider en comparant avec
docker statspendant une charge. - Écrire une page runbook par alerte : quoi vérifier, commandes à lancer, et étapes de rollback/mitigation.
- Organiser un game day : simuler un remplissage disque (en environnement sûr), simuler un OOM, simuler une pression conntrack. Valider les alertes et le playbook.
Checklist de sécurité disque (parce que le disque vous trahira)
- Rotation des logs Docker configurée et vérifiée sur les nouveaux conteneurs
- Alerte sur le taux de croissance du disque/temps-avant-saturation pour la racine Docker
- Alerte sur le taux de consommation d’inodes/temps-avant-zero
- Nettoyage nocturne du cache de build pour les nœuds de build (pas forcément prod)
- Rétention explicite pour les volumes (les bases de données et queues ne sont pas des poubelles)
Checklist CPU/mémoire
- Alerte sur OOM kills et working set mémoire approchant les limites
- Alerte sur throttling CPU, pas seulement sur l’usage CPU
- Limites définies avec de vrais tests de charge ; concurrence liée aux budgets CPU/mémoire
- Swap hôte surveillé ; swap-in soutenu est un signal d’avertissement à respecter
Checklist logging (sanité minimale)
- Logs structurés avec request IDs et champs d’erreur
- Erreurs répétitives limitées en débit
- Pas de corps de requête/réponse par défaut en production
- Séparer les logs d’audit des logs applicatifs debug si nécessaire
FAQ
1) Quelle est l’alerte Docker la plus importante ?
Le temps-avant-plein disque (et le temps-avant-zero inodes, son frère tout aussi ennuyeux). Les pannes liées au disque provoquent des défaillances en cascade et corrompent la timeline d’incident.
Si vous n’ajoutez qu’une seule chose, faites‑le : « nous serons pleins dans X heures ».
2) Dois‑je alerter sur les redémarrages de conteneurs ?
Oui, mais alertez sur le taux de redémarrages par service, pas sur chaque événement de redémarrage. Un déploiement en rolling provoque des redémarrages ; une boucle de crash provoque un taux soutenu.
Incluez aussi les codes de sortie ou les signaux OOMKilled dans le contexte de l’alerte.
3) Pourquoi mes logs applicatifs ne montrent-ils pas les OOM kills ?
Parce que le noyau tue le processus. Votre appli n’a pas la chance de logger un joli message d’adieu. Confirmez avec docker inspect et dmesg.
4) Le logging json-file est-il acceptable en production ?
C’est acceptable si vous configurez la rotation et comprenez que les logs résident sur le système de fichiers hôte. Si vous avez besoin d’une rétention plus longue, expédiez les logs ailleurs.
Le json-file non tourné est une machine à remplir le disque avec déni plausible.
5) Le CPU n’est qu’à 40 %, pourquoi la latence est terrible ?
Vérifiez le throttling CPU et l’iowait. « CPU 40 % » peut signifier que la moitié de votre quota CPU est refusée, ou que le CPU est idle parce qu’il attend le disque.
Les graphiques d’usage ne montrent pas le refus ; le throttling le fait.
6) Comment distinguer problèmes d’espace disque et problèmes IO disque ?
Les problèmes d’espace apparaissent dans df -h et les erreurs « no space left ». Les problèmes IO apparaissent comme un iowait élevé et une forte utilisation de périphérique dans iostat.
Ils se produisent souvent ensemble, mais la saturation IO peut arriver avec beaucoup d’espace libre.
7) Pourquoi « docker system prune » ne libère-t-il pas beaucoup d’espace ?
Parce que votre usage disque peut être dans des volumes, des couches écrites de conteneurs, ou des images actives. docker system df vous dit ce qui est récupérable.
Si les volumes posent problème, le pruning ne les touchera pas sauf suppression explicite (ce qui peut être catastrophique).
8) Ai‑je besoin de tracing distribué complet pour l’observabilité minimale Docker ?
Pas pour le minimum. Le tracing est excellent pour des chemins de requête complexes, mais il ne vous sauvera pas des disques pleins, des OOM kills, ou de la saturation conntrack.
Prenez d’abord les signaux hôte/conteneur ennuyeux. Ensuite ajoutez des traces là où elles rapportent.
9) Quel est un réglage raisonnable pour la rotation des logs ?
Une base commune est max-size=50m et max-file=5 pour les services généraux. Les services à fort volume peuvent nécessiter des tailles plus petites ou des drivers différents.
Le réglage « correct » est celui qui empêche l’épuisement disque tout en laissant assez d’historique pour déboguer.
Conclusion : prochaines étapes qui changent réellement la donne
Les pannes Docker sont rarement mystérieuses. Elles sont généralement dues à la pression sur les ressources, des limites mal réglées, des logs incontrôlés, ou l’épuisement des tables réseau—plus une mince couche de déni humain.
L’ensemble minimal d’observabilité est la façon de couper rapidement à travers ce déni.
Prochaines étapes pratiques :
- Implémenter la rotation globale des logs Docker et vérifier que les nouveaux conteneurs en héritent.
- Ajouter des alertes pour le temps-avant-plein disque et le temps-avant-zero inodes sur le système racine Docker.
- Alerter sur les OOM kills et les taux de boucle de redémarrage par service.
- Commencer à suivre le throttling CPU ; arrêter de prétendre que CPU% dit toute l’histoire.
- Ajouter la visibilité sur la saturation conntrack si vous exécutez des charges NAT‑lourdes ou à grand nombre de connexions.
- Écrire un runbook d’une page qui inclut les commandes exactes que vous lancerez sous pression (utilisez les tâches ci‑dessus).
Si vous ne faites que cela, vous détecterez la majorité des pannes Docker tôt. Et vous passerez moins de temps à regarder des tableaux de bord calmes pendant que la production brûle en silence.