Dépannage : Diagnostiquer rapidement les erreurs courantes des conteneurs Docker
Maîtrisez l'art du dépannage rapide des conteneurs Docker avec ce guide essentiel. Apprenez le processus structuré pour diagnostiquer les échecs de démarrage à l'aide des commandes Docker de base. Nous détaillons comment utiliser `docker ps -a` pour identifier les plantages, extraire des informations critiques avec `docker logs`, et effectuer une analyse avancée de la configuration avec `docker inspect`. Cet article fournit des exemples pratiques et des résolutions ciblées pour les problèmes fréquents, y compris les erreurs de code de sortie 127, les conflits de ports et les événements OOMKilled, vous permettant d'identifier rapidement la cause première et de restaurer le service.
Dépannage : Diagnostiquer rapidement les erreurs courantes des conteneurs Docker
Lorsqu'un conteneur Docker se ferme immédiatement, ne commencez pas par reconstruire l'image ou modifier des indicateurs aléatoires. Commencez par trouver ce que Docker sait : l'état du conteneur, le code de sortie, les journaux et la commande exacte que Docker a tenté d'exécuter. Ces quatre éléments réduisent généralement rapidement le problème.
Un conteneur n'est qu'un processus avec une isolation autour de lui. Si le processus principal se ferme, le conteneur se ferme. Cela peut être un plantage, un exécutable manquant, un travail par lots terminé, une dépendance de santé défaillante ou le noyau tuant le processus parce qu'il a utilisé trop de mémoire. Les commandes ci-dessous vous aident à distinguer ces cas.
Trouvez d'abord le conteneur arrêté
docker ps n'affiche que les conteneurs en cours d'exécution. Les conteneurs qui ont échoué au démarrage sont généralement cachés, sauf si vous demandez tous les conteneurs :
docker ps -a
Regardez STATUS, COMMAND et NAMES :
CONTAINER ID IMAGE COMMAND STATUS NAMES
2d3f4b5c6e7a my-app:latest "/usr/bin/start" Exited (127) 2 minutes ago web-service
91aa34c0db22 worker:latest "python worker.py" Exited (0) 10 minutes ago nightly-worker
Exited (0) signifie souvent que le processus s'est terminé avec succès. C'est normal pour les tâches ponctuelles. Pour un service web, cela peut signifier que la commande a été exécutée et terminée au lieu de rester au premier plan.
Les codes de sortie non nuls indiquent un échec, mais traitez-les comme des indices plutôt que comme des réponses définitives. Le code de sortie 127 signifie généralement que la commande est introuvable. 126 signifie généralement qu'elle a été trouvée mais pas exécutable. 137 signifie souvent que le processus a reçu SIGKILL ; dans les conteneurs, cela est fréquemment, mais pas toujours, lié à la pression mémoire. Confirmez toujours avec les journaux et la sortie d'inspection.
Lisez les journaux avant de modifier quoi que ce soit
Docker capture stdout et stderr du processus principal du conteneur pour le pilote de journalisation par défaut. Utilisez :
docker logs web-service
Options utiles :
docker logs --tail 100 web-service
docker logs --since 15m web-service
docker logs -t web-service
docker logs -f web-service
Si les journaux disent fichier de configuration introuvable, vérifiez les montages et l'environnement. S'ils montrent une trace de pile d'application, déboguez l'application. S'ils sont vides, le processus a peut-être échoué avant de produire une sortie, ou le point d'entrée de l'image peut être incorrect.
Pour une boucle de plantage, évitez d'utiliser docker logs -f comme seul outil. Cela peut donner l'impression que l'échec est actif sans vous donner l'état. Associez les journaux à docker inspect.
Inspectez l'état du conteneur
docker inspect renvoie un grand document JSON. Vous en avez rarement besoin en totalité. Commencez par les champs formatés :
docker inspect -f 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}' web-service
Inspectez ensuite la commande et la configuration de l'image :
docker inspect -f 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{.Config.User}}' web-service
Vérifiez les montages lorsque l'erreur implique des fichiers :
docker inspect -f '{{json .Mounts}}' web-service
Si le conteneur a été tué pour cause de mémoire, .State.OOMKilled est le champ important. S'il est true, augmenter la mémoire peut aider, mais la meilleure question suivante est de savoir pourquoi la mémoire a augmenté. Une limite plus grande peut cacher une fuite assez longtemps pour échouer plus tard.
Reproduisez avec un shell interactif lorsque c'est possible
Si l'image contient un shell, remplacez le point d'entrée et inspectez le système de fichiers :
docker run --rm -it --entrypoint /bin/sh my-app:latest
Certaines images ont Bash :
docker run --rm -it --entrypoint /bin/bash my-app:latest
À l'intérieur, vérifiez les fichiers et les chemins de commande :
ls -l /usr/bin/start
id
env
Les images minimales peuvent ne pas inclure de shell. Dans ce cas, utilisez les outils disponibles de l'image, reconstruisez une variante de débogage temporaire ou inspectez le Dockerfile et la sortie de construction. N'ajoutez pas définitivement de paquets de débogage à une image de production simplement parce que le dépannage a été gênant une fois.
Commande introuvable : exit 127
Le code de sortie 127 signifie généralement que Docker n'a pas pu trouver l'exécutable nommé par ENTRYPOINT ou CMD, ou qu'un script de démarrage a tenté d'exécuter une commande manquante.
Causes courantes :
- L'exécutable n'a jamais été copié dans l'image.
- Le chemin est correct sur l'hôte mais pas à l'intérieur de l'image.
- Le script utilise
/bin/bash, mais l'image n'a que/bin/sh. - La commande dépend de
PATH, etPATHdiffère de ce que vous attendez.
Vérifiez la commande de l'image :
docker inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' web-service
Si le point d'entrée est un script, vérifiez son shebang et ses fins de ligne. Un script avec des fins de ligne Windows CRLF peut échouer avec des messages "introuvable" déroutants car le chemin de l'interpréteur contient effectivement un retour chariot.
Permission refusée : exit 126 ou erreurs de fichier
Le code de sortie 126 signifie souvent que Docker a trouvé la commande mais n'a pas pu l'exécuter. Pour les scripts, le fichier peut manquer du bit exécutable :
COPY start.sh /usr/local/bin/start.sh
RUN chmod 0755 /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]
Pour les fichiers montés en volume, rappelez-vous que les permissions de l'hôte s'appliquent. Si un conteneur s'exécute en tant qu'UID 1000 et que le répertoire hôte appartient à root sans permission d'écriture, le conteneur ne peut pas y écrire simplement parce qu'il est "à l'intérieur de Docker".
Vérifiez l'utilisateur d'exécution :
docker inspect -f 'user={{.Config.User}}' web-service
S'il est vide, de nombreuses images s'exécutent en tant que root par défaut, mais pas toutes. Les images officielles et renforcées en sécurité utilisent souvent un utilisateur non root.
Port déjà alloué
Une erreur de liaison apparaît généralement lorsque vous publiez un port hôte déjà utilisé :
docker run -p 8080:80 nginx
Docker peut signaler quelque chose comme bind: address already in use. Trouvez le conflit :
docker ps --format 'table {{.Names}}\t{{.Ports}}'
lsof -iTCP:8080 -sTCP:LISTEN
Arrêtez ensuite le processus conflictuel ou choisissez un autre port hôte :
docker run -p 8081:80 nginx
Le port du conteneur peut rester le même. Le port hôte est la partie avant les deux-points.
Fichiers manquants et mauvais montages
Si les journaux disent qu'un fichier de configuration est manquant, comparez ce que l'application attend avec ce que Docker a monté :
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Une erreur courante consiste à monter un répertoire hôte sur un chemin qui contenait déjà des fichiers dans l'image. Le montage cache le contenu de l'image à cette destination. Si l'image contient /app/config/default.yml et que vous montez un répertoire hôte vide sur /app/config, le fichier par défaut disparaît de la vue du conteneur.
Vérifiez également les chemins relatifs. -v ./config:/app/config dépend du répertoire à partir duquel vous avez exécuté docker run, pas du répertoire où se trouve le Dockerfile.
Les échecs de vérification de santé ne sont pas toujours des plantages de conteneur
Un conteneur peut être en cours d'exécution mais malsain :
docker ps
Vous pourriez voir Up 2 minutes (unhealthy). Inspectez la sortie de santé :
docker inspect -f '{{json .State.Health}}' web-service
Les vérifications de santé échouent souvent parce que l'application écoute sur un port différent, se lie uniquement à 127.0.0.1, prend plus de temps à démarrer que ne le permet la vérification de santé, ou a besoin d'une base de données qui n'est pas encore prête. Ne confondez pas un conteneur malsain avec un conteneur fermé ; le chemin de diagnostic est différent.
Une séquence de dépannage rapide
Utilisez cet ordre lorsque vous avez besoin d'une réponse rapidement :
docker ps -apour trouver le conteneur et le code de sortie.docker logs --tail 100 <nom>pour lire l'erreur de l'application.docker inspect -f ...pour vérifier l'état, la commande, l'utilisateur et les montages.- Exécutez un shell temporaire dans l'image si la commande ou le système de fichiers est suspect.
- Vérifiez les conflits hôtes pour les ports et les permissions des répertoires montés.
- Reconstruisez seulement après avoir su si le problème est le contenu de l'image, les indicateurs d'exécution ou la configuration de l'application.
Cette séquence maintient l'investigation ancrée. Docker a généralement suffisamment de preuves ; l'astuce est de les lire avant de modifier la scène.
Vérifiez la politique de redémarrage avant de vous fier à ce que vous voyez
Une politique de redémarrage peut donner l'impression qu'un conteneur échoue constamment ou récupère constamment. Vérifiez-la :
docker inspect -f 'restart={{json .HostConfig.RestartPolicy}}' web-service
Si la politique est always ou unless-stopped, Docker peut redémarrer le conteneur après chaque plantage. docker ps peut le montrer comme en cours d'exécution pendant quelques secondes, puis redémarrant à nouveau. Dans ce cas, utilisez les journaux avec des horodatages et inspectez le nombre de redémarrages :
docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' web-service
Un nombre élevé de redémarrages signifie généralement que le processus principal se ferme rapidement. La solution n'est rarement de "modifier la politique de redémarrage". La politique ne fait que révéler l'échec sous-jacent.
Distinguez les problèmes de construction des problèmes d'exécution
Si un fichier est manquant à l'intérieur du conteneur, demandez-vous quand il aurait dû apparaître. Les fichiers copiés dans le Dockerfile sont des préoccupations de construction. Les fichiers montés avec -v ou les volumes Compose sont des préoccupations d'exécution.
Vérifications de construction :
docker image inspect my-app:latest
docker run --rm --entrypoint /bin/sh my-app:latest -c 'ls -la /app'
Vérifications d'exécution :
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
Cette division fait gagner du temps. Reconstruire l'image ne corrigera pas un mauvais montage hôte. Modifier un indicateur de volume ne corrigera pas un Dockerfile qui n'a jamais copié le binaire.
Les variables d'environnement et les secrets peuvent échouer silencieusement
De nombreuses applications se ferment parce qu'une variable d'environnement requise est manquante, mais l'erreur Docker dit seulement que le processus s'est fermé avec le code 1. Inspectez attentivement l'environnement configuré :
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service
Soyez prudent quant à l'endroit où vous exécutez cette commande ; elle peut imprimer des secrets. Dans les journaux partagés, imprimez uniquement les noms de variables :
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service | sed 's/=.*//'
Si vous utilisez --env-file, vérifiez les fins de ligne CRLF, les espaces non cités et les fichiers manquants. Les fichiers env Docker ne sont pas des scripts shell complets. Gardez-les simples : des lignes KEY=value, des commentaires là où votre version Docker les prend en charge, et aucune hypothèse selon laquelle une expansion shell se produira à l'intérieur du fichier.
Une revue de passage dans le monde réel avant de livrer
Avant de déclarer un script ou une configuration de conteneur terminée, lisez-la une fois comme si vous étiez la personne suivante qui doit la déboguer à 2 heures du matin. Cela change ce que vous remarquez. Une invite qui avait du sens lors de l'écriture du script peut être ambiguë lorsqu'elle apparaît dans un journal CI. Un nom de service Docker qui semblait évident peut ne pas correspondre au nom de variable dans l'application. Une valeur par défaut Bash peut être sûre pour le développement et dangereuse pour la production.
J'aime faire un court essai à sec avec des valeurs délibérément maladroites. Utilisez un chemin avec des espaces. Utilisez une valeur facultative vide. Essayez un nom de fichier qui commence par un tiret. Exécutez le script à partir d'un répertoire de travail différent. Démarrez le conteneur sans une variable d'environnement attendue. Ces tests ne sont pas sophistiqués, mais ils attrapent les hypothèses qui se brisent généralement en premier.
Vérifiez également le message d'échec. Si la seule sortie est échec, les conseils de l'article n'ont pas été intégrés dans l'implémentation. Un échec utile indique quelle valeur a été utilisée, quelle vérification a échoué et ce que l'opérateur peut modifier. Cela ne signifie pas vider chaque variable d'environnement ou imprimer des secrets. Cela signifie être spécifique là où la spécificité aide : le chemin de configuration, le nom de commande manquant, le nom de réseau, le nom d'hôte du service ou le port que le processus a tenté de lier.
La dernière habitude est de garder les exemples proches de la façon dont le système est réellement exécuté. Si la production utilise Compose, testez avec Compose. Si un script est lancé par systemd, testez-le avec systemd ou avec un environnement tout aussi minimal. Si une commande est censée être sûre pour le copier-coller, incluez les guillemets, les séparateurs -- et la validation dans l'exemple lui-même. Les lecteurs copient les modèles de travail plus souvent qu'ils ne copient les avertissements.
Cette revue de passage n'est pas de la bureaucratie. C'est ainsi que la petite automatisation reste ennuyeuse. L'ennui est ce que vous voulez des invites shell, des chargeurs de configuration, de l'expansion de variables, des diagnostics de conteneur et de la mise en réseau Docker. Moins le comportement est surprenant, plus il est facile pour l'opérateur suivant de lui faire confiance.
Pour les échecs Docker, enregistrez la commande exacte qui a créé le conteneur lorsque vous le pouvez. docker inspect peut montrer la configuration actuelle, mais une note d'incident qui inclut la commande docker run originale, le service Compose, l'étiquette d'image et le nom de fichier env est beaucoup plus facile à reproduire plus tard. Évitez d'utiliser latest lors d'un débogage sérieux. Une étiquette mobile peut transformer un échec en deux enquêtes différentes parce que l'image change pendant que vous lisez encore les journaux.
Si vous diagnostiquez un problème de type production localement, tirez le même digest ou étiquette d'image, utilisez la même disposition de volume et passez la même forme d'environnement non secret. La reproduction bat la spéculation.