## Introduction Lorsqu'un service conteneurisé échoue en production, la première question est rarement « Qu'est-ce qui est cassé ? » mais « Comment savons-nous que c'est cassé ? » Les healthchecks Docker répondent à cette question en définissant une commande que le conteneur exécute périodiquement pour rapporter son état interne. Pourtant, de nombreuses équipes traitent le healthcheck comme une simple case à cocher dans le Dockerfile ou le fichier Compose, et ne découvrent ses angles morts qu'au moment d'un incident. Cet article est une check-list opérationnelle pour les healthchecks Docker en production. Il s'adresse aux développeurs, ingénieurs DevOps et équipes techniques de startups qui exécutent des services dans Docker et ont besoin d'une méthode reproductible pour vérifier que ces services ne font pas que tourner, mais sont réellement sains. L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des variables d'environnement plutôt que des secrets, vérifier le résultat et documenter comment récupérer lorsque l'état attendu n'est pas atteint. Chaque section combine un élément de check-list avec au moins une commande concrète, sa sortie attendue, les signaux d'échec courants et la décision de reprise qui en découle. La check-list est ordonnée approximativement comme vous aborderiez un système de production en opérations normales, puis pendant un incident. ## Inventaire des versions et de l'environnement Avant de toucher à quoi que ce soit, sachez exactement ce que vous exécutez. Un healthcheck ne vaut que ce que vaut le moteur d'exécution qui l'exécute, et une inadéquation entre ce que vous pensez être déployé et ce qui est réellement déployé est la cause racine de nombreuses pannes déroutantes. **Élément de check-list 1 : Enregistrez les versions du moteur Docker et du plugin Compose.** Exécutez : ```bash docker version --format '{{.Server.Version}}' docker compose version ``` Exemple de sortie attendue : ```text 24.0.7 Docker Compose version v2.23.3 ``` Si le moteur est antérieur à 20.10, le support des healthchecks est absent ou se comporte différemment. Certaines fonctionnalités d'orchestration nécessitent également des versions récentes de Compose. Notez ces valeurs dans votre runbook avant d'effectuer tout changement. **Élément de check-list 2 : Énumérez les conteneurs en cours d'exécution et leur état de santé.** Exécutez : ```bash docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" ``` Exemple de sortie attendue : ```text NAMES STATUS PORTS api Up 2 hours (healthy) 0.0.0.0:8080->8080/tcp worker Up 2 hours (unhealthy) redis Up 2 hours 0.0.0.0:6379->6379/tcp ``` Remarquez que `worker` affiche `unhealthy` tandis que `redis` n'affiche aucun état de santé car aucun healthcheck n'est défini. C'est votre premier signal de triage : un conteneur non sain est un problème connu, alors qu'un healthcheck manquant est un angle mort. **Élément de check-list 3 : Confirmez où les données sont stockées avant tout remplacement de conteneur.** Un conteneur qui semble sain peut toujours perdre des données s'il écrit dans le système de fichiers du conteneur plutôt que dans un volume monté. Inspectez les montages : ```bash docker inspect api --format '{{ range .Mounts }}{{ .Type }} {{ .Name }} {{ .Source }} -> {{ .Destination }}{{ println }}{{ end }}' ``` Exemple de sortie attendue : ```text volume app_data /var/lib/docker/volumes/app_data/_data -> /var/lib/app ``` Pour les projets Compose, exécutez : ```bash docker compose config ``` Cela imprime la configuration résolue, y compris les volumes et les définitions de healthcheck. Si vous voyez un montage de type bind comme `./data:/var/lib/app`, rappelez-vous qu'il mappe directement un répertoire hôte. Cela fonctionne sur cette machine, mais si le chemin hôte n'existe pas sur une autre machine, le conteneur démarrera avec un répertoire vide ou échouera. Les volumes nommés sont plus portables et plus faciles à sauvegarder. **Élément de check-list 4 : Effectuez un test de redémarrage local avant de modifier la production.** Sur un environnement de staging ou local qui imite la production, exécutez ce qui suit : ```bash docker compose stop api docker compose up -d api docker compose ps docker exec api ls /var/lib/app ``` Si la liste des fichiers est vide après le redémarrage, le service écrivait dans le système de fichiers du conteneur. Les données de production sont déjà en danger ; corrigez le montage du volume avant toute autre chose. **Responsable et fréquence de revue :** Le responsable de plateforme ou d'infrastructure (par exemple, « Alex Chen, responsable infrastructure ») est responsable de la tenue de l'inventaire de l'environnement. Revoyez l'inventaire mensuellement ou à chaque déploiement d'un nouveau service. ## Chemin de configuration sûr La configuration du healthcheck vit dans le Dockerfile ou le fichier Compose. Un healthcheck mal configuré peut amener l'orchestrateur à redémarrer sans fin un conteneur sain, ou à marquer comme sain un conteneur non sain et à continuer de lui envoyer du trafic. Modifiez cette configuration délibérément. **Élément de check-list 1 : Établissez une base de référence de la configuration actuelle du healthcheck.** Lisez les définitions actuelles avant de les modifier : ```bash docker inspect api --format '{{json .Config.Healthcheck}}' ``` Exemple de sortie attendue : ```json {"Test":["CMD-SHELL","curl -f http://localhost:8080/health || exit 1"],"Interval":30000000000,"Timeout":5000000000,"Retries":3,"StartPeriod":15000000000} ``` Notez que Docker stocke les intervalles en nanosecondes. Une erreur courante est d'interpréter `30000000000` comme 30 millisecondes ; c'est en réalité 30 secondes. Vérifiez toujours avec `docker inspect` plutôt que de supposer à partir du fichier source. **Élément de check-list 2 : Modifiez le healthcheck uniquement dans un fichier contrôlé.** Si vous devez modifier le healthcheck, éditez le Dockerfile ou le fichier Compose, pas le conteneur en cours d'exécution. Par exemple, dans `docker-compose.yml` : ```yaml services: api: image: myapp:2.4 healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/ready || exit 1"] interval: 10s timeout: 3s retries: 3 start_period: 40s ``` Appliquez ensuite avec : ```bash docker compose up -d --no-deps api ``` L'option `--no-deps` évite de redémarrer inutilement les services dépendants, limitant ainsi le rayon d'impact. **Élément de check-list 3 : Vérifiez que le nouveau healthcheck prend effet.** Après l'application, vérifiez l'état au fil du temps : ```bash docker ps --format '{{.Names}}: {{.Status}}' ``` Séquence attendue : ```text api: Up 10 seconds (health: starting) api: Up 45 seconds (healthy) ``` Si le conteneur bascule entre `starting` et `unhealthy` de manière répétée, la période de démarrage est trop courte ou la commande de healthcheck échoue pour des raisons sans rapport avec l'état de préparation. Inspectez les journaux : ```bash docker logs api --tail 50 ``` Recherchez la sortie propre de la commande de healthcheck si elle journalise quelque chose. **Élément de check-list 4 : Testez délibérément la détection de panne.** Une fois qu'un conteneur est sain, simulez une panne pour confirmer que le healthcheck la détecte réellement. Pour un service web, vous pouvez casser temporairement le point de terminaison en déplaçant un fichier : ```bash docker exec api mv /app/health /app/health.disabled ``` Observez ensuite le changement d'état dans l'intervalle configuré : ```bash docker ps --format '{{.Names}}: {{.Status}}' ``` Sortie attendue après quelques intervalles : ```text api: Up 2 minutes (unhealthy) ``` Cela prouve que l'orchestrateur verra la panne. Restaurez le fichier et confirmez la récupération. Ce test est peu coûteux et devrait faire partie de toute revue de modification du healthcheck. **Responsable et fréquence de revue :** Le responsable du service (par exemple, « Priya Shah, responsable ingénierie ») est responsable de la configuration du healthcheck de son service. Revoyez toute modification avant la fusion, et réévaluez les seuils du healthcheck trimestriellement ou après tout changement de code significatif. ## Vérification et diagnostic Un healthcheck est un signal binaire : sain ou non sain. Mais lorsqu'il échoue, vous devez savoir pourquoi. Cette section porte sur la collecte de diagnostics sans altérer le système. **Élément de check-list 1 : Lisez l'historique d'exécution propre de la commande de healthcheck.** Docker ne journalise pas séparément la sortie du healthcheck, mais vous pouvez inspecter les journaux du conteneur pour trouver des indices. Cependant, une meilleure approche consiste à exécuter manuellement la commande de healthcheck à l'intérieur du conteneur : ```bash docker exec api curl -f http://localhost:8080/health ``` Si cela renvoie un code de sortie non nul, le healthcheck lui-même est précis et le service est effectivement en échec. Si cela renvoie 0 mais que le conteneur est marqué non sain, le problème est probablement un problème de synchronisation (intervalle trop court, délai d'attente trop bas ou période de démarrage insuffisante). **Élément de check-list 2 : Vérifiez l'utilisation des processus et des ressources à l'intérieur du conteneur.** Exécutez : ```bash docker stats --no-stream api ``` Exemple de sortie attendue : ```text CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS f1c1a1a1a1a1 api 78.43% 512.3MiB / 1GiB 50.04% 12.3MB / 45.2MB 0B / 0B 23 ``` Une pression élevée sur le CPU ou la mémoire peut entraîner l'expiration des commandes de healthcheck même si l'application fonctionne. Comparez avec les métriques de base d'avant l'incident. **Élément de check-list 3 : Validez les dépendances de la commande de healthcheck.** De nombreux healthchecks reposent sur des outils comme `curl`, `wget` ou `pg_isready`. Si l'image ne les inclut pas, le healthcheck échoue toujours. Exécutez : ```bash docker exec api which curl ``` Si la commande n'est pas trouvée, vous avez deux options : soit remplacer le healthcheck par une commande utilisant des outils intégrés (par exemple, `node -e "require('http').get('http://localhost:8080/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"` pour Node.js), soit ajouter l'outil manquant à l'image. La première option est souvent meilleure pour la production car elle évite d'augmenter la taille de l'image pour une seule vérification. **Élément de check-list 4 : Pour les services Compose, passez en revue l'état de santé de toute la pile.** Exécutez : ```bash docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Service}}" ``` Exemple de sortie attendue : ```text Name Status Service myapp_api_1 Up 2 hours (healthy) api myapp_worker_1 Up 2 hours (unhealthy) worker myapp_redis_1 Up 2 hours redis ``` Un worker non sain peut ne pas affecter directement l'API, mais il peut signaler un problème avec une dépendance partagée comme la base de données. Consultez les journaux du worker : ```bash docker compose logs --tail 200 worker ``` Recherchez des traces de pile, des erreurs de connexion ou des variables d'environnement manquantes. **Responsable et fréquence de revue :** L'ingénieur d'astreinte ou l'équipe de fiabilité est responsable de l'exécution des diagnostics pendant un incident. Après résolution, la revue post-incident doit vérifier que les seuils et la commande du healthcheck sont toujours appropriés et qu'aucun nouvel angle mort n'a été introduit. ## Modes de défaillance et reprise Les healthchecks eux-mêmes échouent rarement ; c'est le service derrière eux qui échoue. Mais un healthcheck mal conçu peut créer son propre mode de défaillance : un healthcheck trop strict provoque des redémarrages inutiles, tandis qu'un healthcheck trop laxiste permet à des services cassés de continuer à recevoir du trafic. Voici les modes de défaillance courants et comment les corriger. **Mode de défaillance 1 : Flapping du healthcheck et boucles de redémarrage.** **Pourquoi cela arrive :** L'intervalle du healthcheck est plus court que le temps de démarrage de l'application, ou le délai d'attente est trop bas pour que la vérification se termine sous charge normale, ou la commande de healthcheck échoue par intermittence en raison de dépendances transitoires. **Comment détecter :** L'état du conteneur alterne entre `unhealthy` et `starting` de manière répétée. Les journaux montrent des redémarrages fréquents. **Comment récupérer :** 1. Déterminez le temps de démarrage réel en observant le conteneur à partir d'un démarrage propre. 2. Augmentez `start_period` à au moins 1,5 fois le temps de démarrage observé. 3. Si la vérification échoue encore pendant le fonctionnement normal, augmentez `timeout` ou diminuez `interval`. 4. Exemple d'ajustement : ```yaml healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"] interval: 10s timeout: 5s retries: 5 start_period: 90s ``` **Mode de défaillance 2 : Conteneurs orphelins marqués sains après la mort du processus.** **Pourquoi cela arrive :** Si le processus principal meurt mais que le conteneur ne se termine pas (par exemple, un processus zombie ou un PID 1 qui ignore les signaux), le healthcheck peut toujours réussir s'il vérifie un point de terminaison statique. Le service est effectivement en panne, mais Docker le signale comme sain. **Comment détecter :** La surveillance externe montre des échecs alors que Docker signale sain. Inspectez le processus principal : ```bash docker inspect api --format '{{.State.Status}} {{.State.Pid}}' ``` Si le PID est 1 ou un zombie, le processus principal ne tourne pas. **Comment récupérer :** Re-concevez le healthcheck pour vérifier la logique applicative réelle, pas seulement un fichier statique. Par exemple, faites en sorte que le point de terminaison de santé effectue une requête triviale en base de données ou vérifie un état critique en mémoire. Alternativement, utilisez un superviseur de processus comme PID 1 (comme `tini` ou `dumb-init`) afin que lorsque le processus principal meurt, le conteneur se termine et l'orchestrateur le redémarre. **Mode de défaillance 3 : Le healthcheck réussit mais le service est dégradé.** **Pourquoi cela arrive :** Le healthcheck teste seulement un point de terminaison simple, mais une dépendance en aval (base de données, cache, API externe) est en panne. Le service peut répondre aux vérifications de santé mais ne peut pas traiter de vraies requêtes. **Comment détecter :** Les taux d'erreur augmentent dans les journaux applicatifs, mais l'état de santé reste `healthy`. **Comment récupérer :** Implémentez un healthcheck plus profond qui inclut un test léger de chaque dépendance critique. Par exemple : ```bash curl -f http://localhost:8080/health/deep ``` Le point de terminaison pourrait renvoyer 200 uniquement si toutes les dépendances sont joignables dans un court délai. Alternativement, utilisez deux healthchecks : un superficiel pour la vivacité (utilisé par l'orchestrateur) et un plus profond pour l'état de préparation (utilisé par un équilibreur de charge) si votre orchestration le prend en charge. **Mode de défaillance 4 : Commande de healthcheck manquante dans l'image.** **Pourquoi cela arrive :** L'image a été construite sur une base minimale qui n'inclut pas `curl`, `wget` ou d'autres outils supposés par le healthcheck. Le healthcheck échoue toujours avec le code de sortie 127 (commande introuvable). **Comment détecter :** Le conteneur est immédiatement marqué non sain après le démarrage, et l'inspection des journaux montre quelque chose comme `OCI runtime exec failed: exec failed: unable to start container process: exec: "curl": executable file not found in $PATH`. **Comment récupérer :** Remplacez le healthcheck par une commande qui utilise des outils intégrés au langage ou au système d'exploitation. Pour Python : ```yaml healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()"] ``` Pour Node.js : ```yaml healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080/health', r=>process.exit(r.statusCode===200?0:1)).on('error', ()=>process.exit(1))"] ``` Ces solutions évitent d'ajouter des paquets supplémentaires à l'image. **Responsable et fréquence de revue :** Le responsable du service est chargé de corriger le healthcheck en fonction du mode de défaillance. Après récupération, mettez à jour le runbook avec la nouvelle configuration du healthcheck et les leçons apprises. Revoyez l'efficacité du healthcheck trimestriellement. ## Check-list opérationnelle Voici une check-list opérationnelle condensée quotidienne/hebdomadaire pour les équipes qui exécutent des healthchecks Docker en production. Elle suppose que vous avez terminé les étapes de configuration et de vérification ci-dessus. **Vérifications quotidiennes (ingénieur d'astreinte) :** - [ ] Exécutez `docker ps --format "table {{.Names}}\t{{.Status}}"` et confirmez qu'il n'y a pas d'états `unhealthy` ou `starting` inattendus. - [ ] Pour chaque service avec un healthcheck, confirmez que l'état est stable depuis la dernière heure (utilisez des tableaux de bord de surveillance si disponibles). - [ ] Vérifiez les journaux des conteneurs pour les erreurs liées au healthcheck : `docker logs --since 1h | grep -i health` - [ ] Si un conteneur est non sain, suivez les diagnostics de la section Vérification et diagnostic et ouvrez un incident si nécessaire. **Vérifications hebdomadaires (responsable du service) :** - [ ] Revoyez les configurations des healthchecks par rapport au comportement actuel du service. Portez attention aux nouveaux points de terminaison ou aux délais modifiés. - [ ] Exécutez un test d'injection de panne sur un service : cassez intentionnellement le healthcheck et vérifiez que l'orchestrateur marque le conteneur non sain, puis restaurez. - [ ] Confirmez que tous les services critiques ont un healthcheck défini. Identifiez tout conteneur sans état de santé et ajoutez-en un si approprié. - [ ] Mettez à jour le runbook avec tout changement concernant les commandes, intervalles ou seuils des healthchecks. **Vérifications mensuelles (responsable infrastructure) :** - [ ] Vérifiez que les versions du moteur Docker et du plugin Compose sont toujours prises en charge et compatibles avec les fonctionnalités de healthcheck. - [ ] Auditez l'utilisation des volumes et des montages de type bind pour tous les conteneurs afin d'éviter des pertes de données cachées. - [ ] Revoyez les modes de défaillance des healthchecks du mois passé et identifiez des schémas. - [ ] Assurez-vous que les commandes de healthcheck ne dépendent pas d'outils qui ne sont pas garantis dans l'image de base. **Responsable et fréquence de revue :** Chaque élément de check-list a un responsable nommé comme indiqué. Les vérifications quotidiennes sont la propriété de l'ingénieur d'astreinte, les vérifications hebdomadaires du responsable du service et les vérifications mensuelles du responsable infrastructure. La check-list elle-même est revue et mise à jour après chaque incident majeur ou au moins trimestriellement. ## Pièges courants Même avec une check-list solide, les équipes tombent à plusieurs reprises dans les mêmes pièges. Voici les plus courants et comment les éviter. **Piège 1 : Utiliser les healthchecks pour l'état de préparation au lieu de la vivacité.** Le healthcheck de Docker est un mécanisme de vivacité et de préparation de base, mais ce n'est pas une sonde de préparation complète. Si vous l'utilisez pour conditionner le trafic vers un service qui met longtemps à chauffer, vous risquez soit de router le trafic trop tôt, soit de redémarrer trop souvent. Utilisez `start_period` pour couvrir le préchauffage initial, et fiez-vous à un équilibreur de charge ou à un maillage de services pour des vérifications de préparation plus nuancées. **Piège 2 : Coder en dur des secrets dans les commandes de healthcheck.** Un healthcheck dans un fichier Compose ou Dockerfile peut nécessiter des identifiants pour appeler un point de terminaison. Évitez d'intégrer directement des secrets ; utilisez des variables d'environnement ou des secrets Docker, et assurez-vous que la valeur n'est pas imprimée dans les journaux. Exemple : ```yaml healthcheck: test: ["CMD-SHELL", "curl -f -H 'Authorization: Bearer $HEALTH_TOKEN' http://localhost:8080/health || exit 1"] ``` Définissez `HEALTH_TOKEN` via l'environnement ou un registre de secrets. **Piège 3 : Ignorer le healthcheck pour les auxiliaires sans état.** Certaines équipes sautent les healthchecks pour les workers sans état ou les sidecars parce qu'ils « ne peuvent pas être non sains ». Mais un worker qui ne peut pas se connecter à la base de données est non sain en pratique. Définissez une vérification simple, par exemple : ```yaml healthcheck: test: ["CMD-SHELL", "pg_isready -h db -U user -d app || exit 1"] interval: 30s timeout: 5s retries: 3 ``` **Piège 4 : Ne pas tester le healthcheck dans la CI.** Le healthcheck est du code, et il peut casser. Incluez une étape dans votre pipeline CI qui construit l'image, démarre le conteneur et vérifie qu'il devient sain. Un simple test de fumée : ```bash docker run -d --name test_health myapp:latest for i in $(seq 1 30); do status=$(docker inspect --format '{{.State.Health.Status}}' test_health) if [ "$status" = "healthy" ]; then exit 0; fi sleep 2 done exit 1 ``` **Piège 5 : Définir des intervalles trop agressifs.** Un intervalle d'une seconde avec un délai d'attente d'une seconde peut causer plus de mal que de bien. Le healthcheck lui-même consomme du CPU et peut ajouter de la charge au service. Choisissez un intervalle qui a du sens pour votre service : pour une application web typique, 10 à 30 secondes est raisonnable. Moins fréquemment pour les traitements par lots. ## Conclusion Les healthchecks Docker sont une petite partie de votre environnement d'exécution de conteneurs, mais ils constituent la première ligne de défense pour la disponibilité des services. Un healthcheck bien conçu vous dit la vérité sur votre service sans le surcharger, et donne à l'orchestrateur le signal dont il a besoin pour maintenir le trafic vers les instances saines. Un healthcheck mal conçu soit vous ment, soit provoque du churn. Cet article a parcouru une check-list opérationnelle pratique : commencez par un inventaire de l'environnement pour savoir ce que vous avez, suivez un chemin de configuration sûr pour les modifications, diagnostiquez les problèmes avec des commandes concrètes, comprenez les modes de défaillance courants et comment récupérer, et intégrez les vérifications dans les opérations quotidiennes, hebdomadaires et mensuelles. La section sur les pièges courants devrait vous aider à éviter les erreurs que d'autres ont déjà commises. La prochaine étape consiste à choisir un service à faible risque et à exécuter les commandes d'inventaire des versions et de l'environnement de cet article. Enregistrez l'état actuel, vérifiez la définition du healthcheck et injectez délibérément une panne pour voir comment votre système réagit. Décidez ensuite si vos seuils et commandes de healthcheck correspondent toujours à la réalité de votre service. Si ce n'est pas le cas, ajustez-les en utilisant le chemin de configuration sûr décrit ici. Rappelez-vous : un healthcheck n'est utile que si vous faites confiance à son signal en cas d'urgence. Construisez cette confiance maintenant, avant d'en avoir besoin.