Vous l’avez déjà vu : le conteneur est « Up », le PID est vivant et les tableaux de bord sont verts. Pendant ce temps, les clients reçoivent des 502,
la file d’attente gonfle dangereusement et votre personne d’astreinte apprend des jurons en trois langues.
Les healthchecks Docker sont censés prévenir précisément ce type d’illusion. Trop souvent, ils sont écrits comme le premier script de surveillance d’un stagiaire nerveux : « le processus tourne ? »
Ce n’est pas un healthcheck. C’est prendre le pouls d’un patient qui peut déjà être cliniquement inutile.
Définir « sain » sérieusement
« Sain » n’est pas synonyme de « en cours d’exécution ». Un conteneur peut être en cours d’exécution tout en ne faisant absolument rien d’utile : bloqué sur un mutex, coincé sur le DNS,
à court de descripteurs de fichiers, incapable d’atteindre sa base de données, ne retournant que des pages d’erreur ou laissant silencieusement tomber des messages. Votre travail consiste à
choisir une définition de santé qui s’aligne sur le succès visible par l’utilisateur.
Pensez en couches :
- Santé du processus : le binaire est vivant, ne boucle pas en crash, n’a pas été tué par OOM.
- Santé du service : il peut accepter des requêtes et produire des réponses correctes dans des limites (latence, codes d’état, validité du payload).
- Santé des dépendances : il peut atteindre ce qu’il doit atteindre (BD, cache, broker, DNS) et se dégrader volontairement quand il ne le peut pas.
- Santé de capacité : il n’est pas « vivant mais saturé » (pool de threads épuisé, file d’attente pleine, disque plein).
Pour Docker, les healthchecks servent surtout à répondre à une question : Faut‑il considérer ce conteneur comme apte à servir ?
C’est une question de readiness. Docker n’a pas de probe de readiness séparée comme Kubernetes, donc vous devez soit :
- Utiliser les healthchecks Docker comme « readiness », et ne pas les rendre trop agressifs pour qu’ils ne tuent pas un conteneur pour un incident passager.
- Ou les traiter comme « liveness », en acceptant alors que vous manquerez les cas « vivant mais cassé ».
Mon avis tranché : dans les piles uniquement Docker (Compose, Swarm, docker run simple), faites que les healthchecks se comportent comme de la readiness :
« puis‑je accomplir le minimum utile maintenant ? » Cela attrape les pannes qui importent aux utilisateurs.
Faits intéressants et un peu d’histoire
- Fait 1 : Les healthchecks Docker ont été introduits à l’ère Docker 1.12, principalement pour prendre en charge des comportements orchestrés sans outils externes.
- Fait 2 : Le statut de santé est stocké par conteneur et visible via
docker inspect; ce n’est pas une métrique de premier plan à moins que vous l’exportiez. - Fait 3 : Compose n’a pas d’emblée conditionné
depends_onsur la santé ; les versions modernes de Compose le peuvent, mais beaucoup supposent encore que c’était toujours le cas. - Fait 4 : Swarm utilise le statut de santé pour les décisions d’ordonnancement des services, mais « healthy » ne signifie pas automatiquement « dans le load balancer » dans tous les environnements.
- Fait 5 : Un healthcheck s’exécute à l’intérieur des namespaces du conteneur, il voit donc le DNS, le routage et le système de fichiers du conteneur—bon et mauvais.
- Fait 6 : La commande de healthcheck est exécutée avec
/bin/sh -cquand vous utilisezCMD-SHELL; les bugs de quoting et les surprises de PATH sont courants. - Fait 7 : Le code de sortie compte, pas stdout. Les humains aiment la sortie jolie ; Docker aime
0et « pas 0 ». - Fait 8 : Les premiers « endpoints de santé » dans les applications web sont devenus populaires parce que les load balancers avaient besoin d’un signal oui/non bon marché ; ils n’étaient jamais censés être une suite complète de monitoring.
- Fait 9 : Une « vérification » qui prend trop de temps est en pratique une attaque par déni de service contre votre propre conteneur si elle s’accumule ou consomme des ressources rares.
Comment Docker évalue réellement les healthchecks
Les healthchecks Docker sont une petite machine à états : démarrer en starting, puis passer à healthy si les checks réussissent,
et à unhealthy après un nombre configuré d’échecs consécutifs. C’est simple, ce qui est à la fois attrait et piège.
À quoi servent vraiment les réglages
- interval : la fréquence d’exécution du check.
- timeout : combien de temps attendre avant de considérer le check comme échoué.
- retries : nombre d’échecs consécutifs requis pour marquer unhealthy.
- start_period : fenêtre de grâce où les échecs ne comptent pas (mais les checks s’exécutent toujours).
Un healthcheck devrait être :
- Peu coûteux (millisecondes à dizaines de millisecondes quand tout va bien).
- Borné (timeouts stricts ; pas de blocage infini).
- Représentatif (teste ce dont les utilisateurs ont réellement besoin).
- Difficile à falsifier (ne se contente pas de vérifier qu’un fichier existe).
Une citation opérationnelle à garder sous la main :
« L’espoir n’est pas une stratégie. »
— Gene Kranz (souvent cité dans les contextes de fiabilité).
Principes de conception qui attrapent les vraies pannes
1) Sondez le chemin critique, pas le chemin heureux
Si votre service sert du HTTP soutenu par une base de données, le chemin critique est « accepter la requête → exécuter une requête BD légère → renvoyer ».
Votre healthcheck devrait exercer ce chemin à coût minimal.
Évitez une vérification qui n’atteint qu’un endpoint en mémoire qui ne touche jamais les dépendances. C’est ainsi que l’on obtient des healthchecks verts pendant que
la base de données est en feu.
2) Privilégiez les transactions synthétiques peu coûteuses aux “self tests” complets
Un bon check est une toute petite transaction avec un budget borné : une requête légère, un ping, un petit GET qui exerce le routage, l’auth et la sérialisation.
Un mauvais check est une suite d’intégration complète à l’intérieur des conteneurs de production.
3) Décidez quelles pannes doivent provoquer un redémarrage vs suppression du trafic
Les healthchecks Docker ne redémarrent pas automatiquement les conteneurs sauf si vous les combinez avec des politiques ou le comportement d’un orchestrateur. Même dans ce cas,
redémarrer à chaque coupure de dépendance est une manière classique de transformer « un hic DB transitoire » en « une panne complète ».
Si la dépendance est indisponible, vous voulez souvent rester en ligne et servir des réponses dégradées, ou au moins garder votre processus chaud en attendant.
Les healthchecks peuvent toujours servir à vous sortir de la rotation (ou à vous empêcher d’y être ajouté trop tôt), pas forcément à vous tuer.
4) Échouez rapidement sur l’épuisement des ressources
« Vivant mais saturé » est l’un des modes de panne les plus coûteux parce qu’il ressemble à un succès partiel. Votre check devrait détecter :
pools de threads bloqués, file d’attente de requêtes pleine, disque plein, ou incapacité à créer des fichiers/sockets.
Une astuce utile consiste à vérifier que vous pouvez effectuer une petite allocation ou ouvrir un socket, sans transformer le healthcheck en benchmark.
5) Rendez le check déterministe et local, mais pas stupide
Il doit s’exécuter rapidement et de façon consistante. Cela signifie :
- Utiliser des timeouts fixes (
curl --max-time,timeout). - Minimiser les sauts réseau.
- Éviter d’appeler des API tierces depuis les healthchecks. Si vous le faites, vous externalisez votre disponibilité à leurs limites de taux.
Blague #1 : Un healthcheck qui appelle une API externe, c’est comme demander à votre voisin si votre maison est en feu — par texto — pendant une tornade.
6) Retournez des codes de sortie signifiants et loggez assez de contexte
Docker enregistre les dernières sorties du healthcheck. Profitez-en. Imprimez une ligne de contexte en cas d’échec : quelle dépendance a échoué, quel timeout, quel statut.
N’imprimez pas des mégaoctets. Ce n’est pas un enregistreur de vol.
Patrons de healthcheck efficaces en production
Pattern A: Endpoint HTTP de santé avec échantillonnage des dépendances
Construisez un /healthz qui vérifie :
- l’app peut accepter des requêtes
- le pool de connexions BD peut emprunter une connexion et exécuter un
SELECT 1(ou équivalent) - connectivité cache/broker si ce sont des exigences strictes
Gardez-le peu coûteux. Si vous avez besoin d’une vérification approfondie, mettez‑la sur un endpoint différent (par ex. /readyz vs /healthz),
mais Docker ne vous donne qu’un levier, donc vous choisirez probablement la version « readiness-ish ».
Pattern B: Sonde directe des dépendances depuis le conteneur
Quand vous ne pouvez pas changer l’app, faites la meilleure chose suivante : sonder le service depuis l’intérieur du namespace du conteneur.
Par exemple : vérifier que le port HTTP local renvoie 200, et que le port TCP de la BD est joignable.
C’est moins riche sémantiquement que les checks au niveau applicatif, mais cela attrape la classe de pannes « le processus tourne, mais le socket n’écoute pas ».
Pattern C: Retard/arriéré de file (avec précaution)
Pour les consommateurs, « sain » peut vouloir dire « je fais des progrès ». Cela peut être :
- l’offset des messages avance
- profondeur de la file en dessous d’un seuil
- taux de dead letters qui n’explose pas
Le danger est le basculement fréquent : la profondeur peut monter naturellement. Utilisez des seuils avec retries et fenêtres temporelles, pas un seul échantillon.
Pattern D: Détecter les interblocages et blocages de boucle d’événements
Certaines des pires pannes sont des deadlocks et des stalls : le processus est vivant, le port est ouvert, mais les requêtes ne se terminent jamais.
Votre healthcheck doit inclure un budget strict de temps de réponse. Un « succès » lent est un échec déguisé.
Pattern E: Sanité du disque et du système de fichiers pour les conteneurs à état
Si le conteneur écrit sur un volume, vous devez savoir quand le système de fichiers est plein, monté en lecture seule ou que les permissions ont cassé après
un changement d’image. Vérifiez que vous pouvez écrire et fsyncer un petit fichier dans le chemin prévu — de temps en temps, pas à chaque seconde.
Pattern F: Sanité DNS (parce que les échecs DNS ressemblent à « tout est cassé »)
Un service qui ne peut pas résoudre des noms internes échouera de façons qui ressemblent à des pannes de dépendances. Un simple getent hosts
ou nslookup contre un nom requis peut vous faire gagner du temps.
Exemples de base (Dockerfile / Compose)
Dans un Dockerfile, utilisez les healthchecks seulement si l’image possède réellement le contrat d’exécution. Si la santé dépend de dépendances spécifiques au déploiement,
les healthchecks au niveau Compose sont souvent plus adaptés.
cr0x@server:~$ cat Dockerfile
FROM alpine:3.20
RUN apk add --no-cache curl
HEALTHCHECK --interval=10s --timeout=2s --retries=3 --start-period=20s \
CMD curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1
cr0x@server:~$ cat compose.yaml
services:
api:
image: example/api:1.9.3
healthcheck:
test: ["CMD-SHELL", "curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1"]
interval: 10s
timeout: 2s
retries: 3
start_period: 25s
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -h 127.0.0.1 || exit 1"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
Notez l’orientation : l’API se vérifie elle‑même sur localhost (pas d’ambiguïté réseau), et Postgres utilise son propre outil de readiness peu coûteux.
Si vous pouvez utiliser des sondes dédiées (pg_isready, redis-cli ping), faites‑le.
Tâches pratiques : commandes, sorties, décisions (12+)
Les healthchecks échouent pour des raisons rarement mystérieuses. C’est généralement « réseau », « DNS », « timeouts », « permissions » ou « épuisement des ressources ».
Voici des tâches concrètes qui vous évitent de deviner.
Tâche 1 : Voir le statut de santé du conteneur et la dernière sortie
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}'
NAMES STATUS
api Up 2 hours (unhealthy)
db Up 2 hours (healthy)
Ce que cela signifie : Docker pense que le healthcheck de api échoue à répétition.
Décision : Inspectez immédiatement les logs de santé ; ne redémarrez pas aveuglément pour l’instant.
cr0x@server:~$ docker inspect --format '{{json .State.Health}}' api | jq
{
"Status": "unhealthy",
"FailingStreak": 7,
"Log": [
{
"Start": "2026-02-04T09:44:00.123456789Z",
"End": "2026-02-04T09:44:01.126789012Z",
"ExitCode": 1,
"Output": "curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received\n"
}
]
}
Ce que cela signifie : Votre check expire, il s’agit d’un timeout, pas d’un simple code non‑200. C’est un stall, un problème de bind ou une saturation sévère.
Décision : Vérifiez si le service écoute et si les requêtes localhost se terminent dans le budget imparti.
Tâche 2 : Valider la commande de healthcheck exactement comme Docker l’exécute
cr0x@server:~$ docker inspect --format '{{json .Config.Healthcheck.Test}}' api
["CMD-SHELL","curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1"]
Ce que cela signifie : Elle est exécutée via le shell, donc la sémantique du shell s’applique.
Décision : Exécutez exactement cette commande à l’intérieur du conteneur pour confirmer que l’environnement et les outils correspondent aux hypothèses.
Tâche 3 : Exécuter la sonde manuellement dans le conteneur
cr0x@server:~$ docker exec -it api sh -lc 'curl -fsS --max-time 1 http://127.0.0.1:8080/healthz; echo "rc=$?"'
curl: (28) Operation timed out after 1000 milliseconds with 0 bytes received
rc=28
Ce que cela signifie : L’endpoint ne répond pas sous 1 seconde depuis l’intérieur du conteneur.
Décision : Déterminez s’il n’écoute pas, s’il est bloqué ou trop lent. Passez aux vérifications de socket et aux logs applicatifs.
Tâche 4 : Vérifier si le port écoute (sans supposer que netstat existe)
cr0x@server:~$ docker exec -it api sh -lc 'ss -lntp | head'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("api",pid=1,fd=7))
Ce que cela signifie : Le service écoute sur le port 8080.
Décision : S’il écoute mais que le healthcheck expire, suspectez un stall applicatif, l’épuisement des threads ou un blocage en aval.
Tâche 5 : Mesurer la latence et le code de statut avec un budget strict
cr0x@server:~$ docker exec -it api sh -lc 'curl -s -o /dev/null -w "code=%{http_code} time=%{time_total}\n" --max-time 2 http://127.0.0.1:8080/healthz'
code=200 time=1.873
Ce que cela signifie : Il renvoie 200, mais c’est lent—presque 2 secondes.
Décision : Soit augmentez prudemment le budget du healthcheck, soit corrigez la lenteur. Ne “corrigez” pas cela en portant les timeouts à 30 secondes.
Tâche 6 : Vérifier les logs du conteneur autour de la fenêtre de défaillance
cr0x@server:~$ docker logs --since 10m --tail 200 api
2026-02-04T09:41:12Z WARN db pool exhausted; waiting for connection
2026-02-04T09:41:13Z WARN db query timeout after 1500ms
2026-02-04T09:41:15Z ERROR /healthz dependency=db timeout=1500ms
Ce que cela signifie : L’endpoint de santé fait son travail : il rapporte des problèmes de BD. Le service n’est pas assez sain pour servir.
Décision : Arrêtez d’ajuster les healthchecks. Réparez la BD ou la taille du pool de connexions. Investiguer la charge BD et la connectivité.
Tâche 7 : Valider la connectivité BD depuis le conteneur applicatif
cr0x@server:~$ docker exec -it api sh -lc 'nc -vz -w 1 db 5432; echo "rc=$?"'
db (172.20.0.3:5432) open
rc=0
Ce que cela signifie : La connectivité TCP existe ; ce n’est pas une partition réseau basique.
Décision : Consultez la charge BD, l’auth, les paramètres de pool, la latence DNS ou les requêtes lentes—des choses au‑dessus de TCP.
Tâche 8 : Vérifier le temps de résolution DNS (tueur caché)
cr0x@server:~$ docker exec -it api sh -lc 'time getent hosts db'
172.20.0.3 db
real 0m0.003s
user 0m0.000s
sys 0m0.002s
Ce que cela signifie : La résolution DNS/hosts est rapide ici.
Décision : Si c’est lent (centaines de ms), réparez la configuration du résolveur, le DNS Docker ou les domaines de recherche. Ne blâmez pas la BD en premier.
Tâche 9 : Vérifier l’épuisement des descripteurs de fichiers
cr0x@server:~$ docker exec -it api sh -lc 'cat /proc/1/limits | grep -i "open files"'
Max open files 1024 1024 files
Ce que cela signifie : Limite FD faible. Sous charge, vous pouvez atteindre ce plafond et devenir « vivant mais inutile ».
Décision : Augmentez l’ulimit dans la définition du service ; vérifiez aussi les fuites de FD.
Tâche 10 : Rechercher des kills OOM et la pression mémoire
cr0x@server:~$ docker inspect --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' api
OOMKilled=false ExitCode=0
Ce que cela signifie : Pas d’OOM kill dans ce cycle de vie du conteneur.
Décision : Si vrai, corrigez les limites mémoire, les fuites ou le dimensionnement du heap JVM/node. Les healthchecks ne sauveront pas un processus que le noyau tue.
Tâche 11 : Confirmer que l’outil de healthcheck existe dans l’image
cr0x@server:~$ docker exec -it api sh -lc 'command -v curl || echo "curl missing"'
/usr/bin/curl
Ce que cela signifie : Le binaire existe à l’emplacement attendu.
Décision : Si absent, ne « corrigez » pas en utilisant des bizarreries busybox. Ajoutez l’outil ou réécrivez le check avec ce que vous livrez réellement.
Tâche 12 : Vérifier que le healthcheck ne bouffe pas le CPU ni ne crée des zombies
cr0x@server:~$ docker exec -it api sh -lc 'ps -o pid,ppid,stat,comm | head -n 10'
PID PPID STAT COMMAND
1 0 S api
42 1 S worker
77 1 S worker
201 1 S curl
Ce que cela signifie : Si vous voyez une pile grandissante de processus curl, votre healthcheck peut accrocher et s’empiler.
Décision : Ajoutez des timeouts (--max-time) et assurez‑vous que la commande de check ne peut pas bloquer indéfiniment.
Tâche 13 : Utiliser les events pour corréler « unhealthy » avec redémarrages et déploiements
cr0x@server:~$ docker events --since 30m --filter container=api
2026-02-04T09:31:00Z container health_status: healthy
2026-02-04T09:40:10Z container health_status: unhealthy
2026-02-04T09:40:11Z container exec_start: curl -fsS --max-time 1 http://127.0.0.1:8080/healthz || exit 1
Ce que cela signifie : Il est devenu unhealthy à un moment précis. C’est votre point pivot.
Décision : Vérifiez l’historique des déploiements, les rechargements de config, les fenêtres de maintenance BD, rotations de certificats, changements DNS à ce moment-là.
Tâche 14 : Inspecter le gating des dépendances Compose (vous basez‑vous sur un mensonge ?)
cr0x@server:~$ docker compose ps
NAME IMAGE COMMAND SERVICE STATUS
stack-api-1 example/api "..." api running (unhealthy)
stack-db-1 postgres:16 "docker-entrypoint..." db running (healthy)
Ce que cela signifie : Compose a démarré l’API, et elle tourne toujours mais est marquée unhealthy.
Décision : Si vous attendiez que Compose retarde le démarrage de l’API jusqu’à ce que la BD soit prête, vérifiez que vos conditions depends_on sont supportées et correctes.
Tâche 15 : Vérifier que « unhealthy » affecte le trafic (ce n’est peut‑être pas le cas)
cr0x@server:~$ docker inspect --format '{{.State.Health.Status}} {{.Name}}' api
unhealthy /api
Ce que cela signifie : Docker sait qu’il est unhealthy, mais votre reverse proxy peut continuer à lui envoyer du trafic.
Décision : Confirmez que votre load balancer/proxy s’intègre au statut de santé, ou implémentez un mécanisme d’upstream explicite.
Blague #2 : Si votre proxy ignore la santé des conteneurs, cette ligne « HEALTHCHECK » est essentiellement une plante décorative — vivante, verte, et inutile.
Playbook de diagnostic rapide
Quand le pager sonne et qu’un conteneur est « unhealthy », votre tâche est de trouver le goulot d’étranglement avant de « réparer » en aggravant la panne.
Voici l’ordre qui minimise le temps pour atteindre la vérité.
Première étape : prouver ce qui échoue exactement
- Inspecter la dernière sortie du healthcheck (
docker inspect ... .State.Health). Est‑ce un timeout, un non‑200, une erreur DNS, une erreur d’auth ? - Exécuter la même commande manuellement à l’intérieur du conteneur. Si elle passe en interactif, vous avez peut‑être des différences d’environnement, de PATH ou de timing.
- Vérifier si le service écoute sur le port attendu (
ss -lntp).
Deuxième étape : classer le mode de panne
- Timeout : soupçonnez deadlock, épuisement du pool de threads, pause GC, blocage en aval ou problèmes de ressources kernel.
- Connection refused : processus non à l’écoute, crashé, ou lié à la mauvaise interface.
- DNS failure : problèmes de résolveur, réseau erroné, domaines de recherche gonflés ou nom de service manquant.
- Non-200 : logique applicative, dépendance en échec, mauvaise config, migrations incomplètes.
Troisième étape : vérifier dépendances et ressources
- Accessibilité TCP aux dépendances (
nc -vz). - Logs applicatifs pour épuisement de pool, timeouts, échecs d’auth.
- Limites FD et utilisation actuelle (
/proc/1/limits,lsofsi disponible). - Occupation disque et état de montage (à l’intérieur du conteneur si écriture sur volumes).
Quand redémarrer vs quand tenir le cap
- Redémarrer aide si le processus est bloqué (deadlock) et que votre système supporte la perte du travail en cours.
- Redémarrer nuit si la dépendance est down et que l’app a besoin de caches chauds, de migrations ou de logique de backoff. Vous amplifierez la charge et allongerez la récupération.
Erreurs courantes : symptômes → cause racine → correction
Erreur 1 : Le healthcheck est « ps | grep »
Symptômes : La santé reste toujours verte jusqu’au crash du processus, mais les utilisateurs voient des erreurs pendant des minutes/heures.
Cause racine : Vous vérifiez l’existence, pas la fonctionnalité.
Correction : Sondez l’interface réelle du service (HTTP/TCP) avec un timeout strict ; incluez éventuellement un échantillonnage des dépendances.
Erreur 2 : Le healthcheck atteint un endpoint « OK statique »
Symptômes : La santé reste verte pendant les pannes BD ; les taux d’erreur montent en flèche.
Cause racine : L’endpoint ne valide pas les dépendances critiques.
Correction : Faites que /healthz inclue des vérifications de dépendances peu coûteuses (emprunter une connexion BD, ping du cache). Gardez‑le rapide et borné.
Erreur 3 : Pas de timeouts, donc les checks accrochent et s’empilent
Symptômes : Beaucoup de processus curl bloqués ; CPU en hausse ; conteneur devenu instable.
Cause racine : La commande de healthcheck bloque indéfiniment ; Docker continue de la programmer.
Correction : Utilisez curl --max-time ou timeout ; gardez timeout inférieur au timeout Docker du healthcheck.
Erreur 4 : Healthcheck trop strict et provoquant du flapping
Symptômes : Le conteneur oscille healthy/unhealthy ; redémarrages fréquents ; basculements de trafic constants.
Cause racine : Seuils trop sensibles ; intervalle trop court ; retries trop bas ; start_period trop petit.
Correction : Augmentez les retries, allongez l’intervalle, ajoutez un start_period. Corrigez aussi les pics de latence sous-jacents—ne vous contentez pas de bourrer les timeouts.
Erreur 5 : Healthcheck utilise des noms DNS externes non résolubles dans le conteneur
Symptômes : curl: (6) Could not resolve host ; comportement intermittent, spécifique à l’environnement.
Cause racine : Le DNS du conteneur diffère de l’hôte ; réseau manquant ; domaines de recherche incorrects.
Correction : Utilisez des noms de service sur le réseau Docker ; validez avec getent hosts et définissez explicitement le DNS si nécessaire.
Erreur 6 : Succès du healthcheck n’affecte pas le trafic
Symptômes : Conteneur unhealthy, mais les requêtes lui parviennent toujours ; utilisateurs impactés.
Cause racine : Le proxy/load balancer n’est pas lié au statut de santé des conteneurs.
Correction : Configurez le proxy pour utiliser ses propres checks upstream ou intégrez‑le au comportement de l’orchestrateur ; ne supposez pas que la santé Docker change automatiquement le routage.
Erreur 7 : Healthcheck inclut des requêtes BD coûteuses
Symptômes : La charge BD augmente avec le nombre de répliques ; les healthchecks échouent sous charge et aggravent l’incident.
Cause racine : Les healthchecks sont devenus un mini test de charge ; ils rivalisent avec le trafic de production.
Correction : Utilisez des requêtes à temps constant et peu coûteuses ; mettez en cache brièvement les résultats côté app si nécessaire ; réduisez la fréquence.
Erreur 8 : Healthcheck dépend d’outils absents dans les images minimales
Symptômes : Le healthcheck échoue toujours avec « command not found ».
Cause racine : Vous avez utilisé curl, bash ou nc mais vous avez livré une image scratch/distroless sans eux.
Correction : Ajoutez un petit binaire de sonde dédié, utilisez l’endpoint natif de l’app, ou incluez volontairement les outils minimaux.
Checklists / plan étape par étape
Étapes : écrire un healthcheck qui reflète la réalité
- Choisir le contrat visible par l’utilisateur. « Peut servir du HTTP en moins de 500ms et atteindre la BD. » Écrivez‑le.
- Décider du comportement readiness vs liveness. Dans Docker seul, par défaut optez pour le style readiness.
- Choisir la méthode de sonde. Privilégiez l’endpoint applicatif ; sinon TCP/HTTP local avec timeouts bornés.
- Ajouter des timeouts stricts. Chaque check doit se terminer vite, même quand le système est malade.
- Gérer correctement le warm‑up. Utilisez
start_periodpour éviter les faux négatifs pendant migrations et warm‑up des caches. - Garder le check peu coûteux. Un ping BD, pas une requête de rapport. Une requête HTTP, pas un login complet.
- Faire en sorte que les échecs s’expliquent. Sortie mono‑ligne comme
db timeoutouhttp 503. - Tester en charge votre endpoint de santé. Il doit rester rapide sous charge ; s’il ralentit en premier, ce n’est pas un signal fiable.
- Lier la santé aux décisions de trafic. Si votre proxy l’ignore, corrigez‑le ou ne prétendez pas que les healthchecks gèrent le routage.
- Revoir après les incidents. Chaque panne vous apprend ce que votre healthcheck n’a pas détecté.
Checklist : ne pas livrer tant que ceci n’est pas vrai
- La commande de healthcheck a un timeout strict inférieur au timeout Docker.
- Le healthcheck est testé à l’intérieur de l’image conteneur en cours d’exécution (pas sur votre laptop).
- L’endpoint de santé vérifie au moins une dépendance critique si le service ne peut pas fonctionner sans elle.
- Le start_period couvre le pire temps de démarrage en production (migrations, caches froids).
- Les échecs sont exploitables à partir de la dernière ligne du log de santé.
- Vous comprenez ce que « unhealthy » déclenche dans votre déploiement (redémarrage ? retrait du trafic ? rien ?).
Trois mini-récits d’entreprise depuis le terrain
Mini-récit 1 : L’incident causé par une fausse hypothèse
Une entreprise fintech de taille moyenne exécutait une stack Docker Compose derrière un reverse proxy. L’équipe a ajouté des healthchecks et s’est félicitée :
l’API attendait la base de données grâce à depends_on, donc le séquencement du démarrage était « résolu ».
Puis une mise à jour de routine de la base de données a redémarré l’hôte BD. Les conteneurs API sont restés en ligne, ont continué d’accepter des requêtes et ont commencé
à renvoyer des 500. Les healthchecks restaient verts parce que /health était un endpoint statique « OK ». Le proxy continuait de router le trafic
vers toutes les répliques, qui étaient devenues des usines à erreurs.
La personne d’astreinte pensait que Docker arrêterait le routage vers les conteneurs unhealthy, parce que c’est ce que « sain » signifie en langage humain.
Mais leur proxy ne lisait pas le statut de santé Docker ; il ne regardait que si le port TCP upstream était ouvert.
La correction n’a pas été dramatique. Ils ont modifié l’endpoint pour inclure un ping BD léger, et ont configuré le proxy pour faire son propre check upstream contre cet endpoint.
Ils ont aussi documenté, en langage clair, ce que le statut de santé affecte dans leur stack. L’important n’était pas le code. C’était de supprimer la mauvaise hypothèse avant qu’elle ne ruine leur samedi.
Mini-récit 2 : L’optimisation qui a eu l’effet inverse
Une société média avait une grande flotte d’APIs stateless. Quelqu’un a remarqué que les healthchecks généraient du trafic BD « inutile » : une requête rapide toutes les 5 secondes par conteneur cumulait.
L’équipe a « optimisé » en changeant le healthcheck pour vérifier seulement que le processus tournait et que le port était ouvert.
Un mois plus tard, ils ont eu un incident où la configuration du pool de connexions BD avait régressé. Sous charge, l’API acceptait les requêtes, puis bloquait en attendant une connexion BD jusqu’au timeout client.
Le port restait ouvert, le processus restait vivant. Les healthchecks étaient heureux. Les utilisateurs, pas du tout.
La panne a duré parce que les symptômes étaient confus : le CPU n’était pas saturé, la mémoire semblait correcte et les logs d’erreur étaient verbeux mais non définitifs.
L’« optimisation » avait supprimé le signal automatisé qui aurait classifié rapidement la panne : la saturation d’une dépendance.
Ils ont réintroduit un healthcheck conscient des dépendances, mais avec un budget plus malin : fréquence réduite, ping BD peu coûteux et un peu de cache côté endpoint pour éviter l’effet stampede.
Ils ont aussi appris une vérité ennuyeuse : si vous optimisez hors observabilité, vous paierez plus tard avec intérêts.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la journée
Un fournisseur SaaS d’entreprise avait l’habitude, apparemment fastidieuse : chaque service avait un « contrat de santé » écrit dans le repo.
Il spécifiait ce que vérifie /healthz, ce qu’il ne vérifie pas, et le budget temps à respecter. Il incluait aussi un extrait de runbook :
la commande exacte du healthcheck et comment la reproduire dans le conteneur.
Lors d’une rotation de certificats, un sous‑ensemble de conteneurs a commencé à échouer les connexions TLS sortantes vers une dépendance. L’endpoint de santé faisait
un appel minimal à la dépendance et a commencé à renvoyer 503 avec une raison mono‑ligne : tls handshake failed.
L’astreinte n’a pas eu à deviner si c’était le DNS, le routage ou le code. Ils ont exécuté la commande documentée dans un conteneur défaillant,
vu la même erreur TLS et comparé les différences d’environnement. Le problème a été tracé à un bundle CA obsolète dans une image de base utilisée par une ligne de service.
La correction a été de reconstruire avec le bon bundle CA et de redéployer.
Personne n’a écrit un postmortem héroïque parce que ce n’était pas héroïque. C’était prévisible, rapidement classifié et rapidement corrigé.
La pratique ennuyeuse — contrats standard et checks reproductibles — a empêché que l’incident devienne une histoire folklorique d’entreprise.
FAQ
1) Mon healthcheck Docker doit‑il redémarrer automatiquement le conteneur ?
Les healthchecks Docker ne redémarrent pas les conteneurs en eux‑mêmes. Les redémarrages proviennent des policies de restart ou des orchestrateurs. Décidez selon le mode de panne :
redémarrez pour deadlocks/crashes ; évitez les boucles de redémarrage quand les dépendances sont down.
2) Quelle est la différence entre les healthchecks Docker et liveness/readiness Kubernetes ?
Kubernetes sépare « faut‑il redémarrer ? » (liveness) de « faut‑il envoyer du trafic ? » (readiness). Docker vous donne un seul statut de santé,
donc vous devez choisir ce qu’il représente et vous assurer que votre couche de routage le respecte.
3) À quel point les timeouts doivent‑ils être stricts ?
Assez stricts pour détecter les stalls, mais pas tellement que la jitter normale vous marque unhealthy. Un pattern courant est 1–2 secondes de timeout,
intervalle de 10 secondes, 3 retries, avec un start_period qui correspond au pire temps de démarrage.
4) Les healthchecks doivent‑ils inclure des requêtes de base de données ?
Si le service ne peut pas fonctionner sans la BD, oui — utilisez une requête ou un ping peu coûteux avec un timeout strict. Si le service peut se dégrader
gracieusement, envisagez une vérification plus légère et exposez l’état des dépendances séparément pour l’alerte.
5) Mon endpoint de santé génère de la charge. Que faire ?
Rendez le travail constant‑time, mettez en cache brièvement les résultats et réduisez la fréquence. Ne supprimez pas complètement les vérifications de dépendances ; ajustez‑les.
Assurez‑vous aussi que les endpoints de santé sont peu coûteux à calculer (pas d’auth lourde, pas de sérialisation JSON massive).
6) Pourquoi mon healthcheck passe dans docker exec mais échoue dans le statut Docker ?
Causes courantes : shell différent, différences de PATH, variables d’environnement manquantes, ou timing (votre test manuel survient pendant un bon moment).
Comparez la commande exacte depuis .Config.Healthcheck.Test et exécutez‑la avec sh -lc.
7) Dois‑je utiliser CMD ou CMD-SHELL pour les healthchecks ?
Privilégiez CMD (forme exec) quand c’est possible car cela évite les problèmes de quoting du shell. Utilisez CMD-SHELL quand vous avez besoin de pipes,
de conditionnels ou de checks multiples. Si vous utilisez le shell, soyez explicite et prudent.
8) Comment prévenir le flapping pendant les migrations de démarrage ?
Utilisez start_period suffisamment long pour couvrir les pires migrations et cold starts. Faites aussi en sorte que l’endpoint de santé renvoie une
raison claire de « starting » pendant le warm‑up si possible.
9) Puis‑je faire vérifier l’espace disque ou les montages de volumes par les healthchecks ?
Oui, et vous devriez pour tout ce qui écrit sur des volumes. Vérifiez que le chemin est inscriptible et pas plein. Faites‑le de façon peu coûteuse et pas trop fréquente.
10) Est‑ce acceptable qu’un healthcheck appelle d’autres services ?
Seulement si ces services sont des exigences strictes pour la correction. Gardez les appels minimes et bornés. Évitez les appels tiers et les checks « profonds » coûteux sur le chemin critique.
Prochaines étapes réalisables cette semaine
Si vos healthchecks actuels sont « process running », vous ne vérifiez pas la santé — vous vérifiez si les lumières sont allumées.
Remplacez‑les par des sondes qui reflètent le travail minimum utile que votre service doit accomplir, avec des timeouts stricts et des sorties d’échec signifiantes.
Étapes pratiques :
- Choisissez un service critique et réécrivez son healthcheck pour viser HTTP localhost avec un budget de 1–2 secondes.
- Mettez à jour l’endpoint de santé pour échantillonner au moins une dépendance critique (peu coûteuse) et retourner un texte d’échec exploitable.
- Ajoutez un
start_periodbasé sur le temps de démarrage réel, pas sur l’optimisme. - Vérifiez ce que signifie « unhealthy » dans votre couche de routage ; faites‑en un impact, ou arrêtez de prétendre que ça impacte le routage.
- Après le prochain incident, mettez à jour le contrat de santé et le runbook pour que votre futur vous n’ait pas à redécouvrir le même mode de panne à 3h du matin.