Dépannage des Fermetures Immédiates de Conteneurs Docker

DockerBeginner
Pratiquer maintenant

Introduction

Dans ce laboratoire pratique, vous apprendrez à identifier et à résoudre un problème courant de Docker : les conteneurs qui se ferment immédiatement après le démarrage. Ce problème déroute souvent les débutants et peut survenir pour diverses raisons, allant des erreurs de configuration aux problèmes d'application.

En complétant ce laboratoire, vous comprendrez le cycle de vie des conteneurs Docker, apprendrez à diagnostiquer les fermetures immédiates de conteneurs, maîtriserez les techniques de débogage et mettrez en œuvre les meilleures pratiques pour garantir que vos conteneurs s'exécutent de manière fiable. Ces compétences sont essentielles pour toute personne travaillant avec Docker dans des environnements de développement ou de production.

Comprendre les bases des conteneurs Docker

Commençons par explorer les fondamentaux des conteneurs Docker et nous familiariser avec les commandes de base utilisées pour gérer les conteneurs.

Qu'est-ce qu'un conteneur Docker ?

Un conteneur Docker est un package logiciel léger, autonome et exécutable qui comprend tout ce qui est nécessaire pour exécuter une application :

  • Code
  • Runtime (environnement d'exécution)
  • Outils système
  • Bibliothèques
  • Paramètres

Les conteneurs s'exécutent en isolation du système hôte et des autres conteneurs, offrant une cohérence entre différents environnements.

Explorer Docker sur votre système

Tout d'abord, vérifions que Docker est correctement installé et en cours d'exécution sur votre système :

docker --version

Vous devriez voir une sortie similaire à :

Docker version 20.10.21, build baeda1f

Ensuite, vérifions si des conteneurs sont actuellement en cours d'exécution :

docker ps

Cette commande liste les conteneurs en cours d'exécution. Puisque nous n'en avons pas encore démarré, vous ne devriez voir que les en-têtes de colonnes :

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Pour afficher tous les conteneurs, y compris ceux qui sont arrêtés :

docker ps -a

Comprendre le cycle de vie des conteneurs

Le cycle de vie d'un conteneur Docker se compose de plusieurs états :

  1. Created (Créé) : Un conteneur est créé mais pas encore démarré
  2. Running (En cours d'exécution) : Le conteneur exécute ses processus définis
  3. Paused (Mis en pause) : Les processus du conteneur sont temporairement suspendus
  4. Stopped (Arrêté) : Le conteneur s'est arrêté ou a été arrêté
  5. Deleted (Supprimé) : Le conteneur a été supprimé du système

Exécutons un conteneur simple et observons son cycle de vie :

docker run hello-world

Vous devriez voir une sortie similaire à :

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
...

Notez que ce conteneur s'est exécuté et s'est arrêté immédiatement après avoir affiché le message. C'est en fait un comportement normal pour le conteneur hello-world, car il est conçu pour simplement afficher un message et se fermer.

Vérifiez le conteneur dans la liste de tous les conteneurs :

docker ps -a

Vous devriez voir votre conteneur hello-world dans la liste avec le statut "Exited (Arrêté)" :

CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
a1b2c3d4e5f6   hello-world   "/hello"   30 seconds ago   Exited (0) 30 seconds ago             peaceful_hopper

Le code de sortie (0) indique que le conteneur s'est arrêté avec succès, sans erreurs.

Commandes Docker clés pour la gestion des conteneurs

Voici quelques commandes Docker essentielles que vous utiliserez tout au long de ce laboratoire :

  • docker run [OPTIONS] IMAGE [COMMAND] : Créer et démarrer un conteneur
  • docker ps : Lister les conteneurs en cours d'exécution
  • docker ps -a : Lister tous les conteneurs (y compris ceux qui sont arrêtés)
  • docker logs [CONTAINER_ID] : Afficher les journaux du conteneur
  • docker inspect [CONTAINER_ID] : Obtenir des informations détaillées sur le conteneur
  • docker exec -it [CONTAINER_ID] [COMMAND] : Exécuter une commande dans un conteneur en cours d'exécution
  • docker stop [CONTAINER_ID] : Arrêter un conteneur en cours d'exécution
  • docker rm [CONTAINER_ID] : Supprimer un conteneur

Maintenant que vous comprenez les bases des conteneurs Docker et leur cycle de vie, nous allons passer au dépannage des conteneurs qui se ferment de manière inattendue.

Identifier les problèmes de fermeture des conteneurs

Dans cette étape, nous allons créer un conteneur Docker qui se ferme immédiatement et apprendre à diagnostiquer le problème.

Exécuter un conteneur qui se ferme immédiatement

Essayons d'abord d'exécuter un conteneur Ubuntu :

docker run ubuntu

Vous remarquerez quelque chose d'intéressant - la commande se termine instantanément et revient à votre invite. Où est notre conteneur Ubuntu ? Vérifions :

docker ps

Aucun conteneur n'est en cours d'exécution. Vérifiez maintenant tous les conteneurs, y compris ceux qui sont arrêtés :

docker ps -a

Vous verrez une sortie similaire à :

CONTAINER ID   IMAGE         COMMAND       CREATED          STATUS                      PORTS     NAMES
f7d9e7f6543d   ubuntu        "/bin/bash"   10 seconds ago   Exited (0) 10 seconds ago             focused_galileo
a1b2c3d4e5f6   hello-world   "/hello"      10 minutes ago   Exited (0) 10 minutes ago             peaceful_hopper

Le conteneur Ubuntu a démarré puis s'est fermé immédiatement avec un code d'état de 0, indiquant qu'il s'est fermé sans erreurs. C'est le comportement attendu, mais cela peut être déroutant pour les débutants.

Comprendre pourquoi les conteneurs se ferment

Les conteneurs sont conçus pour exécuter une commande ou un processus spécifique. Lorsque ce processus se termine ou se ferme, le conteneur s'arrête. C'est un principe fondamental de la conception de Docker.

Le conteneur Ubuntu s'est fermé immédiatement parce que :

  1. La commande par défaut pour l'image Ubuntu est /bin/bash
  2. Lorsqu'il est exécuté sans les indicateurs -it (interactif, terminal), il n'y a aucune entrée pour le shell bash
  3. Sans entrée et sans commande spécifique à exécuter, bash se ferme immédiatement
  4. Lorsque le processus principal (bash) se ferme, le conteneur s'arrête

Affichage des journaux et des informations du conteneur

Examinons les journaux de notre conteneur Ubuntu fermé pour comprendre ce qui s'est passé. Tout d'abord, trouvez l'ID du conteneur à partir de votre sortie docker ps -a, puis :

docker logs CONTAINER_ID

Remplacez CONTAINER_ID par votre ID de conteneur réel. Vous ne verrez probablement aucune sortie car le conteneur n'a produit aucun journal avant de se fermer.

Pour des informations plus détaillées sur le conteneur :

docker inspect CONTAINER_ID

Cela affichera un grand objet JSON avec la configuration du conteneur et les informations d'état.

Concentrons-nous sur le code de sortie :

docker inspect CONTAINER_ID --format='{{.State.ExitCode}}'

Vous devriez voir :

0

Cela confirme que le conteneur s'est fermé normalement, et non en raison d'une erreur.

Maintenir un conteneur en cours d'exécution

Pour maintenir le conteneur Ubuntu en cours d'exécution, nous devons soit :

  1. Fournir une session interactive, ou
  2. Remplacer la commande par défaut par un processus de longue durée

Essayons l'approche interactive :

docker run -it ubuntu

Vous êtes maintenant à l'intérieur du conteneur avec une invite bash :

root@3a4b5c6d7e8f:/#

Le conteneur reste en cours d'exécution tant que cette session bash est active. Tapez exit ou appuyez sur Ctrl+D pour quitter le conteneur.

exit

Alternativement, nous pouvons maintenir un conteneur en cours d'exécution en fournissant une commande qui ne se termine pas immédiatement :

docker run -d ubuntu sleep 300

Cela exécute le conteneur Ubuntu et exécute la commande sleep 300, qui maintiendra le conteneur en cours d'exécution pendant 300 secondes (5 minutes).

Vérifiez que le conteneur est en cours d'exécution :

docker ps

Vous devriez voir votre conteneur dans l'état en cours d'exécution :

CONTAINER ID   IMAGE     COMMAND       CREATED          STATUS          PORTS     NAMES
9a8b7c6d5e4f   ubuntu    "sleep 300"   10 seconds ago   Up 10 seconds             hopeful_hopper

Lors du diagnostic des conteneurs qui se ferment immédiatement, rappelez-vous ces points clés :

  1. Les conteneurs se ferment lorsque leur processus principal se termine
  2. Vérifiez les journaux et les codes de sortie pour comprendre pourquoi ils se sont arrêtés
  3. Si un conteneur doit rester en cours d'exécution, assurez-vous que son processus principal ne se ferme pas

Dépannage des problèmes courants de fermeture des conteneurs

Maintenant que nous comprenons pourquoi les conteneurs se ferment immédiatement, explorons les causes courantes des fermetures de conteneurs inattendues et comment les dépanner.

Création d'un conteneur avec un problème

Créons une situation simple où un conteneur se ferme de manière inattendue. Tout d'abord, créez un répertoire pour nos fichiers de test :

mkdir -p ~/project/docker-exit-test
cd ~/project/docker-exit-test

Maintenant, créez un script Python simple avec une erreur :

nano app.py

Ajoutez le code suivant :

import os

## Attempt to read a required environment variable
database_url = os.environ['DATABASE_URL']

print(f"Connecting to database: {database_url}")
print("Application running...")

## Rest of the application code would go here

Enregistrez et quittez le fichier (appuyez sur Ctrl+O, Entrée, puis Ctrl+X).

Maintenant, créez un Dockerfile pour construire une image avec cette application :

nano Dockerfile

Ajoutez le contenu suivant :

FROM python:3.9-slim

WORKDIR /app

COPY app.py .

CMD ["python", "app.py"]

Enregistrez et quittez le fichier.

Construisez l'image Docker :

docker build -t exit-test-app .

Vous devriez voir une sortie indiquant que l'image a été construite avec succès :

Successfully built a1b2c3d4e5f6
Successfully tagged exit-test-app:latest

Maintenant, exécutez le conteneur :

docker run exit-test-app

Vous devriez voir le conteneur se fermer immédiatement avec une erreur :

Traceback (most recent call last):
  File "/app/app.py", line 4, in <module>
    database_url = os.environ['DATABASE_URL']
  File "/usr/local/lib/python3.9/os.py", line 679, in __getitem__
    raise KeyError(key) from None
KeyError: 'DATABASE_URL'

Le conteneur s'est fermé car notre script Python attendait une variable d'environnement qui n'était pas fournie.

Diagnostic du problème

Lorsqu'un conteneur se ferme de manière inattendue, suivez ces étapes de dépannage :

  1. Vérifiez le code de sortie pour déterminer s'il s'agit d'une erreur :
docker ps -a

Recherchez votre conteneur et notez le code de sortie. Il devrait être différent de zéro, indiquant une erreur :

CONTAINER ID   IMAGE           COMMAND            CREATED          STATUS                     PORTS     NAMES
b1c2d3e4f5g6   exit-test-app   "python app.py"   20 seconds ago   Exited (1) 19 seconds ago           vigilant_galileo
  1. Examinez les journaux du conteneur :
docker logs $(docker ps -a -q --filter ancestor=exit-test-app --latest)

Cela récupère les journaux du conteneur le plus récent créé à partir de notre image.

Le message d'erreur montre clairement le problème : le script Python essaie d'accéder à une variable d'environnement nommée DATABASE_URL qui n'existe pas.

Correction du problème

Maintenant, corrigeons le problème en fournissant la variable d'environnement manquante :

docker run -e DATABASE_URL=postgresql://user:password@db:5432/mydatabase exit-test-app

Vous devriez maintenant voir le conteneur s'exécuter avec succès :

Connecting to database: postgresql://user:password@db:5432/mydatabase
Application running...

Le conteneur se ferme toujours, mais cette fois, c'est parce que notre script atteint la fin et se termine normalement. Si nous voulions que le conteneur continue de s'exécuter, nous devrions modifier notre script pour inclure une boucle infinie ou un processus de longue durée.

Causes courantes de fermeture des conteneurs

Voici plusieurs raisons courantes pour lesquelles les conteneurs peuvent se fermer de manière inattendue :

  1. Variables d'environnement manquantes : Comme nous venons de le démontrer
  2. Dépendances absentes : Bibliothèques ou packages système manquants
  3. Échecs de connexion : Impossible d'atteindre une base de données ou un autre service
  4. Problèmes de permissions : Permissions insuffisantes pour accéder aux fichiers ou aux ressources
  5. Limitations de ressources : Le conteneur manque de mémoire ou de CPU
  6. Plantages d'application : Bugs dans le code de l'application

Techniques de dépannage

Pour chacun de ces problèmes, voici des approches de dépannage efficaces :

  1. Revoir les journaux : Vérifiez toujours d'abord les journaux du conteneur avec docker logs
  2. Remplacer le point d'entrée (entrypoint) : Utilisez docker run --entrypoint /bin/sh -it my-image pour entrer dans le conteneur et enquêter
  3. Ajouter des instructions de débogage : Modifiez votre application pour ajouter plus de journalisation
  4. Vérifier l'utilisation des ressources : Utilisez docker stats pour surveiller l'utilisation des ressources du conteneur
  5. Inspecter l'environnement : Exécutez docker exec -it CONTAINER_ID env pour vérifier les variables d'environnement

Essayons la technique de remplacement du point d'entrée avec notre image :

docker run --entrypoint /bin/sh -it exit-test-app

Vous êtes maintenant à l'intérieur du conteneur avec un shell. Vous pouvez explorer l'environnement :

ls -la
cat app.py
echo $DATABASE_URL

Vous verrez que DATABASE_URL n'est pas défini. Quittez le conteneur lorsque vous avez terminé :

exit

En comprenant pourquoi les conteneurs se ferment et en appliquant ces techniques de dépannage, vous pouvez rapidement diagnostiquer et résoudre la plupart des problèmes de fermeture des conteneurs.

Mise en œuvre des meilleures pratiques pour la fiabilité des conteneurs

Maintenant que nous comprenons comment dépanner les fermetures de conteneurs, mettons en œuvre les meilleures pratiques pour rendre nos conteneurs plus fiables et robustes. Nous allons améliorer notre exemple d'application avec une gestion des erreurs appropriée, des contrôles d'intégrité et une journalisation.

1. Amélioration de la gestion des erreurs

Modifions notre application Python pour gérer les variables d'environnement manquantes avec élégance :

cd ~/project/docker-exit-test
nano app.py

Mettez à jour le contenu avec :

import os
import time
import sys

## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')

print(f"Connecting to database: {database_url}")

## Simulate a long-running process
try:
    print("Application running... Press Ctrl+C to exit")
    counter = 0
    while True:
        counter += 1
        print(f"Application heartbeat: {counter}")
        time.sleep(10)
except KeyboardInterrupt:
    print("Application shutting down gracefully...")
    sys.exit(0)

Enregistrez et quittez le fichier.

Cette version améliorée :

  • Utilise os.environ.get() avec une valeur par défaut au lieu de lever une exception
  • Implémente une boucle de longue durée pour maintenir le conteneur en vie
  • Gère l'arrêt en douceur lors de la terminaison

Reconstruisons l'image :

docker build -t exit-test-app:v2 .

Et exécutez le conteneur amélioré :

docker run -d --name improved-app exit-test-app:v2

Vérifiez que le conteneur est en cours d'exécution :

docker ps

Vous devriez voir votre conteneur en cours d'exécution :

CONTAINER ID   IMAGE              COMMAND            CREATED          STATUS          PORTS     NAMES
c1d2e3f4g5h6   exit-test-app:v2   "python app.py"   10 seconds ago   Up 10 seconds             improved-app

Affichez les journaux pour confirmer que cela fonctionne :

docker logs improved-app

Vous devriez voir une sortie comme :

Connecting to database: sqlite:///default.db
Application running... Press Ctrl+C to exit
Application heartbeat: 1
Application heartbeat: 2

2. Mise en œuvre d'un contrôle d'intégrité dans Dockerfile

Les contrôles d'intégrité permettent à Docker de surveiller l'état de votre conteneur. Mettons à jour notre Dockerfile pour inclure un contrôle d'intégrité :

nano Dockerfile

Mettez-le à jour avec :

FROM python:3.9-slim

WORKDIR /app

## Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY app.py .

## Add a health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

## Expose port for the health check endpoint
EXPOSE 8080

CMD ["python", "app.py"]

Maintenant, nous devons ajouter un point de terminaison de contrôle d'intégrité à notre application :

nano app.py

Remplacez le contenu par :

import os
import time
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler

## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')

## Simple HTTP server for health checks
class HealthRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/health':
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.wfile.write(b'OK')
        else:
            self.send_response(404)
            self.end_headers()

def run_health_server():
    server = HTTPServer(('0.0.0.0', 8080), HealthRequestHandler)
    print("Starting health check server on port 8080")
    server.serve_forever()

## Start health check server in a separate thread
health_thread = threading.Thread(target=run_health_server, daemon=True)
health_thread.start()

print(f"Connecting to database: {database_url}")

## Main application loop
try:
    print("Application running... Press Ctrl+C to exit")
    counter = 0
    while True:
        counter += 1
        print(f"Application heartbeat: {counter}")
        time.sleep(10)
except KeyboardInterrupt:
    print("Application shutting down gracefully...")
    sys.exit(0)

Enregistrez et quittez le fichier.

Reconstruisez l'image avec notre contrôle d'intégrité :

docker build -t exit-test-app:v3 .

Exécutez le conteneur avec la nouvelle version :

docker run -d --name healthcheck-app -p 8080:8080 exit-test-app:v3

Après environ 30 secondes, vérifiez l'état de l'intégrité :

docker inspect --format='{{.State.Health.Status}}' healthcheck-app

Vous devriez voir :

healthy

Vous pouvez également tester le point de terminaison d'intégrité directement :

curl http://localhost:8080/health

Cela devrait renvoyer OK.

3. Utilisation des politiques de redémarrage Docker

Docker fournit des politiques de redémarrage pour redémarrer automatiquement les conteneurs lorsqu'ils se ferment ou rencontrent des erreurs :

docker run -d --restart=on-failure:5 --name restart-app exit-test-app:v3

Cette politique redémarrera le conteneur jusqu'à 5 fois s'il se ferme avec un code différent de zéro.

Politiques de redémarrage disponibles :

  • no : Ne jamais redémarrer (par défaut)
  • always : Toujours redémarrer quel que soit l'état de sortie
  • unless-stopped : Toujours redémarrer sauf s'il est arrêté manuellement
  • on-failure[:max-retries] : Redémarrer uniquement en cas de sortie différente de zéro

4. Définition des limites de ressources

Pour éviter les plantages de conteneurs dus à l'épuisement des ressources, définissez des limites de ressources appropriées :

docker run -d --name resource-limited-app \
  --memory=256m \
  --cpus=0.5 \
  exit-test-app:v3

Cela limite le conteneur à 256 Mo de mémoire et à la moitié d'un cœur de processeur.

5. Configuration de la journalisation appropriée

Pour un meilleur débogage, configurez votre application pour qu'elle produise des journaux structurés :

docker run -d --name logging-app \
  --log-driver=json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  exit-test-app:v3

Cela configure le conteneur pour utiliser le pilote de journal JSON avec la rotation des journaux (maximum 3 fichiers de 10 Mo chacun).

Résumé des meilleures pratiques

En mettant en œuvre ces meilleures pratiques, vous avez considérablement amélioré la fiabilité de vos conteneurs Docker :

  1. Gestion gracieuse des erreurs avec des valeurs par défaut
  2. Contrôles d'intégrité des conteneurs pour la surveillance
  3. Politiques de redémarrage appropriées pour la récupération automatique
  4. Limites de ressources pour éviter l'épuisement des ressources
  5. Configuration de journalisation appropriée pour un dépannage plus facile

Ces techniques vous aideront à créer des conteneurs résilients qui peuvent se remettre des pannes et offrir une meilleure observabilité en cas de problèmes.

Résumé

Dans ce laboratoire, vous avez acquis des compétences essentielles pour le dépannage et la résolution des problèmes de fermeture des conteneurs Docker :

  • Comprendre le cycle de vie des conteneurs Docker et pourquoi les conteneurs se ferment lorsque leur processus principal se termine
  • Identifier les causes courantes des fermetures immédiates de conteneurs grâce aux journaux et aux codes de sortie
  • Mettre en œuvre une gestion robuste des erreurs dans les applications conteneurisées
  • Ajouter des contrôles d'intégrité des conteneurs pour surveiller l'état de l'application
  • Configurer des politiques de redémarrage pour la récupération automatique après les pannes
  • Définir des limites de ressources appropriées pour éviter les plantages de conteneurs
  • Mettre en œuvre des stratégies de journalisation appropriées pour faciliter le débogage

Ces compétences constituent la base de déploiements de conteneurs Docker fiables. Au fur et à mesure que vous continuerez à travailler avec Docker dans des scénarios réels, vous trouverez ces techniques de dépannage inestimables pour maintenir des applications conteneurisées stables et résilientes.

Rappelez-vous que les causes les plus courantes des fermetures immédiates de conteneurs sont :

  1. Le processus principal terminant sa tâche (par conception)
  2. Variables d'environnement ou configuration manquantes
  3. Erreurs ou exceptions d'application
  4. Contraintes de ressources ou problèmes de connectivité

En appliquant les techniques de diagnostic et les meilleures pratiques couvertes dans ce laboratoire, vous pouvez rapidement identifier et résoudre ces problèmes, garantissant ainsi que vos conteneurs Docker s'exécutent de manière fiable dans n'importe quel environnement.