Introduction
Les healthchecks Docker sont un moyen intégré de déterminer si un conteneur est réellement sain et prêt à servir du trafic, et pas seulement s'il est en cours d'exécution. Automatiser ces vérifications dans un pipeline CI/CD transforme une porte de déploiement manuelle et sujette aux erreurs en un filet de sécurité reproductible. Ce guide montre comment concevoir, mettre en œuvre et dépanner les healthchecks avec des commandes pratiques et des extraits de configuration que vous pouvez adapter à votre propre pile.
Cet article s'adresse aux développeurs, ingénieurs DevOps et équipes techniques de startups qui exécutent déjà des conteneurs et souhaitent rendre leurs déploiements plus sûrs. Nous supposons que Docker est installé et que vous avez une compréhension de base des Dockerfiles et des pipelines CI/CD. Les exemples utilisent Docker Engine 24.0 ou ultérieur et Docker Compose v2.20 ou ultérieur.
Vous apprendrez à observer avant de modifier, à limiter le rayon d'impact, à éviter les secrets codés en dur, à vérifier chaque étape et à documenter les chemins de récupération. À la fin, vous serez capable d'ajouter un healthcheck à n'importe quel conteneur, de l'intégrer dans un pipeline et de revenir en arrière en toute confiance lorsque quelque chose se passe mal.
Inventaire des versions et de l'environnement
Avant d'ajouter des healthchecks, sachez avec quoi vous travaillez. Identifiez la version du moteur Docker, l'outil de composition et le service cible. Exécutez ces commandes en lecture seule pour capturer l'état actuel :
docker version --format '{{.Server.Version}}'
docker compose version
La sortie attendue ressemble à :
24.0.7
Docker Compose version v2.23.0
Ensuite, voyez quels conteneurs sont en cours d'exécution et leur état de santé actuel. La commande docker ps affiche une colonne STATUS qui inclut la santé lorsqu'un healthcheck est défini :
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
Vous pourriez voir :
NAMES STATUS PORTS
web Up 2 minutes (healthy) 0.0.0.0:8080->80/tcp
db Up 5 minutes (healthy) 5432/tcp
worker Up 5 minutes
Notez que worker n'a pas d'état de santé, ce qui signifie qu'il n'a pas de healthcheck. Pour inspecter la configuration de santé d'un conteneur spécifique et les derniers résultats du healthcheck, utilisez :
docker inspect --format='{{json .State.Health}}' web | jq
Cela renvoie une structure comme :
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2025-03-10T10:00:00.123456789Z",
"End": "2025-03-10T10:00:00.234567891Z",
"ExitCode": 0,
"Output": "HTTP/1.1 200 OK\n"
}
]
}
Pour les projets basés sur Compose, docker compose ps affiche l'état de santé par service. docker compose logs --tail 50 <service> montre la sortie récente, y compris les résultats de la commande de healthcheck s'ils journalisent quelque chose. Pour déboguer un conteneur sans modifier l'image, exécutez docker compose exec <service> sh pour obtenir un shell à l'intérieur du conteneur en cours d'exécution.
Avant de modifier quoi que ce soit, confirmez où résident les données persistantes. Un volume nommé comme app_data:/var/lib/app est géré par Docker et survit à la recréation du conteneur. Un montage bind comme ./data:/var/lib/app mappe directement à un répertoire hôte ; c'est pratique pour le développement mais peut causer des problèmes de permissions et de portabilité si le même chemin n'existe pas sur d'autres machines ou runners CI. Vérifiez les volumes avec :
docker inspect -f '{{range .Mounts}}{{.Type}} {{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' db
Exemple de sortie :
volume pg_data /var/lib/docker/volumes/pg_data/_data -> /var/lib/postgresql/data
Un test local utile avant de toucher à la production : arrêtez le conteneur, recréez-le et vérifiez que l'application voit toujours ses fichiers. Si les données disparaissent, elles ont probablement été écrites dans la couche inscriptible du conteneur au lieu d'un volume. Par exemple :
docker compose stop db
docker compose up -d db
docker compose exec db ls /var/lib/postgresql/data
Chemin de configuration sûr
Les healthchecks doivent être intégrés à l'image de l'application ou au fichier compose, et non ajoutés de manière ad hoc. L'approche la plus sûre consiste à commencer par un changement à portée minimale : ajouter un healthcheck à un service, le tester localement, puis le valider.
Healthcheck dans le Dockerfile
Ajoutez une instruction HEALTHCHECK à votre Dockerfile. Pour un service web, utilisez un outil comme curl ou wget s'il est disponible dans l'image. Pour les images basées sur Alpine, wget est souvent présent. Exemple :
FROM nginx:alpine
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O /dev/null http://localhost/ || exit 1
Voici ce que signifie chaque drapeau :
--interval=30s: Exécute la vérification toutes les 30 secondes.--timeout=3s: La vérification doit se terminer dans les 3 secondes ; sinon, c'est un échec.--start-period=5s: Attend 5 secondes après le démarrage du conteneur avant de commencer les vérifications, pour permettre à l'application de démarrer.--retries=3: Échecs consécutifs nécessaires pour marquer le conteneurunhealthy.
Si la commande de healthcheck se termine avec le code 0, le conteneur est sain. Toute sortie non nulle signifie malsain. Construisez et exécutez localement pour vérifier :
docker build -t my-web .
docker run -d --name web-test -p 8080:80 my-web
docker ps
Dans les 30 secondes, le statut devrait afficher (healthy). S'il affiche (unhealthy), inspectez le journal du healthcheck :
docker inspect --format='{{json .State.Health}}' web-test | jq
Healthcheck dans le fichier Compose
Lorsque vous utilisez Docker Compose, vous pouvez définir les healthchecks dans le fichier compose, ce qui est souvent plus maintenable que de modifier le Dockerfile. Exemple pour une base de données et un service web :
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD:-devpassword}
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost/"]
interval: 30s
timeout: 3s
retries: 3
Remarquez depends_on avec condition: service_healthy (Compose v2.20+). Cela garantit que web ne démarre qu'après que db est sain, évitant les conditions de course. Validez le fichier compose avant de déployer :
docker compose config
Puis démarrez et surveillez l'état :
docker compose up -d
docker compose ps
Secrets et configuration
Ne codez jamais en dur des secrets comme les mots de passe de base de données dans les commandes de healthcheck ou les fichiers d'environnement. Utilisez des variables d'environnement avec des valeurs par défaut pour le développement local, et injectez les vrais secrets dans le CI/CD à partir d'un gestionnaire de secrets. Dans l'exemple compose, ${DB_PASSWORD:-devpassword} ne retombe sur une valeur de développement que lorsqu'aucune variable d'environnement n'est définie.
Gardez toujours les commandes de healthcheck minimales. Évitez la logique shell complexe qui pourrait masquer les échecs. Préférez des outils simples comme curl, wget ou des outils de préparation intégrés.
Vérification et diagnostics
Une fois les healthchecks configurés, vérifiez-les soigneusement avant de vous y fier en production. Des commandes comme docker ps et docker inspect --format='{{json .State.Health}}' <conteneur> sont vos principaux outils de diagnostic. Voici un exemple de sortie saine :
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2025-03-10T10:30:00.123456789Z",
"End": "2025-03-10T10:30:00.156789123Z",
"ExitCode": 0,
"Output": " % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n100 615 100 615 0 0 13159 0 --:--:-- --:--:-- --:--:-- 13369\n"
}
]
}
Si une vérification échoue, le champ Output contient souvent le message d'erreur. Par exemple, un healthcheck de base de données utilisant pg_isready pourrait produire :
/var/run/postgresql:5432 - no response
Cela indique que la base de données n'accepte pas les connexions, peut-être parce qu'elle est encore en cours de démarrage ou qu'elle a planté. Utilisez docker logs <conteneur> pour voir les journaux d'application pour le contexte.
Tester les scénarios d'échec
Ne supposez pas qu'un healthcheck fonctionne parce qu'il renvoie sain une fois. Simulez des échecs pour confirmer la détection. Pour un healthcheck web, arrêtez le serveur web à l'intérieur du conteneur tout en gardant le conteneur en cours d'exécution :
docker exec web nginx -s stop
Attendez l'intervalle du healthcheck (par exemple, 30 secondes) et exécutez docker ps. Le statut devrait devenir (unhealthy). Ensuite, redémarrez le conteneur pour récupérer :
docker restart web
Pour un healthcheck de base de données, vous pouvez arrêter temporairement le processus de base de données :
docker exec db pg_ctl stop -m fast
Là encore, le conteneur devrait éventuellement afficher (unhealthy). Cela vérifie que votre logique d'orchestration ou de CI/CD détectera l'échec.
Journalisation et alertes
Les diagnostics ne sont utiles que si quelqu'un les voit. Dans le CI/CD, capturez toujours l'état du healthcheck avant et après le déploiement. Par exemple, dans un pipeline basé sur shell, enregistrez l'état dans un fichier ou une variable d'environnement et faites échouer le pipeline en cas d'unhealthy.
En production, envisagez d'expédier les journaux de healthcheck des conteneurs vers un système de journalisation centralisé. Docker écrit la sortie du healthcheck dans les journaux du conteneur par défaut (si la commande produit quelque chose), qui peuvent être collectés par les pilotes de journalisation. Cependant, pour un état de santé concis, des outils comme cadvisor ou les métriques propres à Docker peuvent exposer l'état de santé à des systèmes de surveillance comme Prometheus.
Modes de défaillance et récupération
Les healthchecks peuvent échouer pour de nombreuses raisons. Comprendre les modes de défaillance courants vous aide à concevoir de meilleures vérifications et à récupérer plus rapidement.
Commande de healthcheck incorrecte
L'erreur la plus courante est une commande de healthcheck qui n'est pas disponible à l'intérieur du conteneur. Par exemple, utiliser curl dans une image Alpine légère qui n'a pas curl installé. Le healthcheck échouera à chaque fois, même si l'application fonctionne.
Symptôme : L'état du conteneur reste unhealthy, et docker inspect montre Output contenant exec: "curl": executable file not found.
Prévention : Vérifiez que la commande existe dans l'image. Testez manuellement :
docker run --rm -it nginx:alpine sh
# À l'intérieur du conteneur :
which wget || which curl || which busybox
Mieux, utilisez des commandes intégrées ou installez l'outil requis dans le Dockerfile. Par exemple, ajoutez RUN apk add --no-cache curl dans Alpine.
Problèmes de synchronisation : start_period et interval
Si start_period est trop court, le healthcheck peut s'exécuter avant que l'application ne soit prête, provoquant des faux négatifs. Le conteneur peut être marqué malsain et redémarré indéfiniment par l'orchestration.
Symptôme : Les journaux du healthcheck montrent des échecs précoces, puis des succès, mais le conteneur est déjà en cours de redémarrage.
Prévention : Définissez un start_period suffisamment long pour que l'application démarre complètement. Pour les bases de données ou les services qui chargent des données, cela peut être de 30 secondes ou plus. Surveillez le temps de démarrage réel avec docker logs -f.
Dépendances non prêtes
Si un service web dépend d'une base de données mais que le healthcheck ne vérifie que le serveur web, le conteneur web peut signaler sain alors que la base de données est en panne. L'application renvoie des erreurs 500.
Prévention : Faites en sorte que le healthcheck valide les dépendances critiques. Pour une application web, le healthcheck pourrait tenter une requête de base de données ou un chargement de page complet. Par exemple, un healthcheck d'application Python Flask pourrait utiliser :
HEALTHCHECK CMD python -c "import requests; r=requests.get('http://localhost/health'); exit(0 if r.status_code==200 else 1)"
où le point de terminaison /health vérifie la connectivité de la base de données.
Faux positifs : vérifications trop simples
Un healthcheck qui ne vérifie que l'existence d'un processus ou un fichier statique peut ne pas détecter les erreurs d'application. Par exemple, vérifier que nginx est en cours d'exécution mais que l'amont est mal configuré conduit à des erreurs 502 alors que le healthcheck dit sain.
Prévention : Le healthcheck doit effectuer une transaction minimale. Pour les applications web, demandez un point de terminaison dynamique qui exerce toute la pile. Pour les travailleurs, vérifiez que la longueur de la file d'attente est inférieure à un seuil ou que le processus peut se connecter à son courtier de messages.
Redémarrages de conteneurs et état
Lorsqu'un healthcheck échoue, Docker ne redémarre pas automatiquement le conteneur à moins que vous n'ayez défini une politique de redémarrage (par exemple, restart: unless-stopped). Sans politique de redémarrage, le conteneur reste actif mais malsain, ce qui peut ne pas être remarqué. Dans le CI/CD, associez toujours l'échec du healthcheck à l'échec du pipeline et à des alertes.
Chemin de récupération : En développement, docker restart <conteneur> réinitialise l'état de santé. En production, vous devrez peut-être revenir à une version d'image précédente. Assurez-vous que votre pipeline enregistre l'étiquette d'image précédente et dispose d'une étape de retour en arrière.
Problèmes de volume de données
Si un healthcheck repose sur des données persistantes et que le volume n'est pas monté correctement, le healthcheck peut échouer après une recréation du conteneur. Par exemple, un healthcheck de base de données pg_isready peut réussir, mais l'application signale des données manquantes.
Prévention : Comme mentionné, testez la persistance du volume avec un test de redémarrage. De plus, utilisez un healthcheck qui vérifie l'accessibilité des données, comme interroger une ligne de table connue.
Intégration CI/CD
Intégrer les healthchecks dans un pipeline CI/CD les transforme en portes automatisées. Ci-dessous, une stratégie générique utilisant un script shell et des commentaires pour l'adaptation à GitLab CI, GitHub Actions, Jenkins ou similaire.
Étapes du pipeline
Un pipeline typique pour une application conteneurisée avec healthchecks :
- Construire et pousser l'image du conteneur.
- Déployer dans un environnement de préproduction.
- Exécuter des tests de fumée incluant les healthchecks.
- S'il est sain, promouvoir en production.
- S'il est malsain, revenir automatiquement en arrière ou faire échouer le pipeline.
Exemple de script : attendre la santé
Ce script attend qu'un conteneur devienne sain dans un délai imparti. Il peut être utilisé dans n'importe quel système CI/CD comme étape.
#!/bin/bash
set -e
CONTAINER_NAME="web"
TIMEOUT=120
INTERVAL=5
elapsed=0
while [ $elapsed -lt $TIMEOUT ]; do
status=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "not found")
echo "[$elapsed s] Status: $status"
if [ "$status" = "healthy" ]; then
echo "Container $CONTAINER_NAME is healthy."
exit 0
fi
if [ "$status" = "unhealthy" ]; then
echo "Container $CONTAINER_NAME is unhealthy."
docker logs "$CONTAINER_NAME" --tail 50
exit 1
fi
sleep $INTERVAL
elapsed=$((elapsed + INTERVAL))
done
echo "Timeout waiting for $CONTAINER_NAME to become healthy."
docker logs "$CONTAINER_NAME" --tail 50
exit 1
Dans votre pipeline, après avoir déployé le conteneur, exécutez ce script. S'il sort non nul, le pipeline échoue et arrête la progression.
Stratégie de retour en arrière
Avant de déployer une nouvelle version d'image, enregistrez l'étiquette d'image actuelle. Dans compose, vous pouvez mettre à jour l'étiquette d'image dans le fichier compose ou utiliser des variables d'environnement. Par exemple :
web:
image: myregistry/web:${VERSION:-latest}
Dans le pipeline, définissez VERSION sur la nouvelle étiquette et déployez. Si le healthcheck échoue, revenez en arrière en définissant VERSION sur l'étiquette précédente et redéployez :
export VERSION=1.2.3
./deploy.sh
# le healthcheck échoue
./wait_for_health.sh || {
export VERSION=1.2.2
./deploy.sh
./wait_for_health.sh
}
Pour Kubernetes, utilisez kubectl rollout status et kubectl rollout undo.
Extraits de configuration de pipeline
Pour GitHub Actions, une étape de travail pourrait être :
- name: Wait for container health
run: |
docker compose up -d
./wait_for_health.sh
working-directory: deploy
Pour GitLab CI :
deploy:
stage: deploy
script:
- docker compose up -d
- ./wait_for_health.sh
Assurez-vous toujours que le runner CI a accès au démon Docker (par exemple, en utilisant le service docker:dind ou un runner privilégié).
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle avant et après la mise en œuvre des healthchecks pour vous assurer d'avoir tout couvert. Attribuez chaque élément à un responsable et passez en revue la liste au moins une fois par trimestre ou chaque fois que le processus de déploiement change.
| # | Tâche | Responsable | Fréquence |
|---|---|---|---|
| 1 | Vérifier que les versions Docker et Compose correspondent à l'environnement cible | Ingénieur DevOps (par exemple, Priya Shah) | Mensuelle |
| 2 | Confirmer que toutes les données persistantes sont dans des volumes ou des montages bind, pas dans la couche du conteneur | Développeur d'application | Trimestrielle |
| 3 | Tester manuellement la commande de healthcheck dans l'image | Développeur d'application | À chaque changement |
| 4 | Définir start_period, interval, timeout, retries en fonction du temps de démarrage mesuré | Ingénieur DevOps | Par service |
| 5 | Valider que le healthcheck détecte un échec de dépendance simulé | Ingénieur QA | À chaque version |
| 6 | S'assurer que le pipeline échoue en cas de malsain et dispose d'un chemin de retour en arrière | Mainteneur CI/CD (par exemple, Alex Chen) | Trimestrielle |
| 7 | Examiner les journaux de healthcheck après chaque déploiement pour détecter les faux positifs/négatifs | Ingénieur DevOps | À chaque déploiement |
| 8 | Mettre à jour la documentation avec les étapes de récupération pour chaque mode de défaillance | Rédacteur technique ou DevOps | À la demande |
De plus, gardez un runbook avec ces commandes de récupération :
- Inspecter la santé :
docker inspect --format='{{json .State.Health}}' <conteneur> | jq - Voir les journaux récents :
docker logs <conteneur> --tail 100 - Redémarrer le conteneur :
docker restart <conteneur> - Forcer la recréation :
docker compose up -d --force-recreate <service> - Revenir en arrière sur l'image : définir la version précédente et redéployer.
Pièges courants
Au-delà des modes de défaillance, voici des erreurs fréquentes lors de l'adoption des healthchecks en CI/CD :
Piège 1 : Commandes de healthcheck trop complexes. De longs scripts shell avec plusieurs tuyaux et conditions sont difficiles à lire et peuvent masquer la vraie défaillance. Gardez la commande simple et testez-la manuellement.
Piège 2 : Ne pas considérer l'isolation réseau. Dans certains environnements CI, les conteneurs peuvent ne pas avoir accès réseau aux services externes. Un healthcheck qui tente d'atteindre une URL externe échouera. Utilisez des points de terminaison locaux ou des outils intégrés.
Piège 3 : Ignorer le healthcheck dans swarm ou Kubernetes. Docker Swarm utilise les healthchecks pour replanifier les tâches, et Kubernetes utilise des sondes de vivacité et de préparation, pas les healthchecks Docker. Si vous passez à Kubernetes, traduisez les healthchecks Docker en sondes.
Piège 4 : Healthchecks trop fréquents. Un intervalle d'une seconde crée une charge et des journaux inutiles. Utilisez des intervalles raisonnables (10 à 30 secondes pour les services critiques, plus longs pour les autres).
Piège 5 : Ne pas nettoyer les anciennes images. Les déploiements échoués peuvent laisser des conteneurs dans un état malsain. Automatisez le nettoyage et assurez-vous que les vieux conteneurs sont supprimés après un retour en arrière.
Conclusion
Automatiser les healthchecks Docker en CI/CD est un moyen pratique d'augmenter la confiance dans les déploiements. En suivant les étapes ici, vous pouvez mettre en œuvre des healthchecks qui sont délimités par version, observables et réversibles. Commencez par un seul service, testez soigneusement et étendez progressivement.
N'oubliez pas d'observer avant de modifier, de limiter le rayon d'impact, de vérifier chaque étape et de documenter les chemins de récupération. Les healthchecks ne sont pas une solution miracle, mais combinés à de bonnes pratiques de pipeline, ils détectent les échecs tôt et réduisent les temps d'arrêt.
Prochaine étape : choisissez un conteneur dans votre pile actuelle, ajoutez un healthcheck en suivant les conseils ci-dessus, testez-le localement avec un échec simulé, puis intégrez-le dans votre pipeline. Revisitez votre configuration de healthcheck chaque trimestre pour ajuster la synchronisation et les commandes à mesure que votre application évolue.