## Introduction

Déboguer une application conteneurisée ressemble souvent à résoudre un mystère. Les processus sont isolés de l'hôte, les systèmes de fichiers sont éphémères et le réseau ajoute des couches d'indirection. Lorsque votre application web ne répond plus ou plante, le chemin vers la résolution est rarement évident. Pour déboguer efficacement, vous devez comprendre l'architecture sous-jacente au débogage des conteneurs Docker : les composants impliqués, la circulation des données entre eux et les défaillances courantes. Ce guide fournit cette vue architecturale, accompagnée d'exemples pratiques, de commandes et d'extraits de configuration que vous pouvez utiliser immédiatement.

Le débogage des conteneurs Docker n'est pas un outil unique, mais une combinaison de fonctionnalités intégrées de Docker et d'utilitaires externes. Les composants principaux comprennent l'interface de ligne de commande Docker, le démon Docker, l'environnement d'exécution de conteneurs (runc ou containerd), les pilotes de journalisation, les commandes exec et inspect, ainsi que les outils de surveillance. En comprenant l'interaction entre ces éléments, vous pourrez diagnostiquer les problèmes plus rapidement et éviter les pièges courants. Ce guide couvre l'inventaire des versions et de l'environnement, la configuration sécurisée, la vérification et les diagnostics, les modes de défaillance et une liste de contrôle opérationnelle.

Tout au long de cet article, nous utiliserons un conteneur d'application web simple comme exemple concret. Il s'agit d'une application Node.js qui plante occasionnellement à cause d'exceptions non gérées, et nous la déboguerons étape par étape. Toutes les commandes ont été testées sur Linux (Ubuntu 22.04) avec Docker Engine 24.0, mais elles devraient fonctionner avec des modifications mineures sur d'autres systèmes.

## Inventaire des versions et de l'environnement

Avant de déboguer un conteneur, établissez une base de référence claire. Connaissez la version de Docker, l'image et l'environnement d'exécution du conteneur, ainsi que le système d'exploitation de l'hôte. Ces informations sont souvent essentielles lors de la recherche de problèmes connus ou de la détermination de la compatibilité.

### Prérequis

- Docker Engine 20.10 ou version ultérieure (nous utilisons 24.0).
- Connaissance de base de la ligne de commande.
- Un conteneur à déboguer (nous utilisons webapp:1.0).
- Accès aux journaux du conteneur et capacité d'exécuter des commandes à l'intérieur des conteneurs.

### Vérification de la version et des informations de Docker

Exécutez les commandes suivantes sur l'hôte :

```bash
docker version --format '{{.Server.Version}}'
```

Sortie attendue (la vôtre peut différer) :

```
24.0.5
```

```bash
docker info --format '{{.OperatingSystem}} {{.Architecture}} {{.CgroupDriver}}'
```

Sortie attendue :

```
Ubuntu 22.04.3 LTS x86_64 systemd
```

### Inspection du conteneur

Identifiez le conteneur à déboguer :

```bash
docker ps -a --filter "name=webapp" --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"
```

Exemple de sortie :

```
CONTAINER ID   IMAGE        STATUS                     NAMES
3f4a5b6c7d8e   webapp:1.0   Up 10 minutes (unhealthy)  webapp
```

Notez le statut « (unhealthy) » ; cela indique que le contrôle de santé du conteneur échoue. Nous examinerons cela plus tard.

### Topologie

Dans une configuration de débogage Docker typique, on trouve :

- **Hôte** : exécute le démon Docker et fournit les ressources.
- **Démon Docker (dockerd)** : gère les conteneurs, les images, les réseaux et les volumes.
- **Environnement d'exécution de conteneurs (containerd / runc)** : crée et exécute réellement les conteneurs.
- **Conteneur** : le processus applicatif et ses dépendances.
- **Pilote de journalisation** : capture la sortie standard/erreur du conteneur et la transmet à une destination (json-file par défaut).
- **Outils de débogage** : docker exec, docker logs, docker inspect, outils système comme strace, tcpdump, etc.

Flux de données : l'application écrit sur la sortie standard/erreur -> environnement d'exécution du conteneur -> pilote de journalisation -> fichier journal ou point de terminaison distant. Flux de contrôle : l'utilisateur lance docker exec -> CLI Docker -> démon -> environnement d'exécution -> exécute la commande dans l'espace de noms du conteneur.

## Chemin de configuration sécurisé

Avant d'apporter des modifications, mettez en place une configuration de débogage sécurisée. Cela implique d'activer une journalisation appropriée, de configurer des contrôles de santé et de s'assurer de pouvoir exécuter des commandes dans le conteneur sans perturber la production si possible. Nous procéderons de manière ciblée et réversible.

### Configuration des pilotes de journalisation

Le pilote de journalisation par défaut de Docker est json-file, qui stocke les journaux sur l'hôte. Pour le débogage, il est souvent utile de configurer un autre pilote ou d'ajuster les options. Nous conserverons json-file mais définirons une taille maximale pour éviter de remplir le disque.

Créez un conteneur avec les options de journalisation :

```bash
docker run -d --name webapp \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  -p 8080:8080 \
  webapp:1.0
```

Cela limite les fichiers journaux à 10 Mo chacun et conserve 3 fichiers après rotation. Si vous devez modifier la journalisation d'un conteneur existant, vous devez le recréer. Pour voir la configuration de journalisation actuelle :

```bash
docker inspect --format '{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}' webapp
```

Sortie :

```
json-file map[max-file:3 max-size:10m]
```

### Mise en place des contrôles de santé

Un contrôle de santé indique à Docker si l'application fonctionne correctement. Pour notre application Node.js, nous pouvons ajouter un point de terminaison HTTP de santé. Modifiez le Dockerfile pour inclure HEALTHCHECK :

```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1
CMD ["node", "server.js"]
```

Reconstruisez l'image :

```bash
docker build -t webapp:1.1 .
```

Exécutez le nouveau conteneur :

```bash
docker run -d --name webapp-healthy \
  --log-driver json-file --log-opt max-size=10m --log-opt max-file=3 \
  -p 8080:8080 \
  webapp:1.1
```

Vérifiez l'état de santé après une minute :

```bash
docker ps --format '{{.HealthStatus}}' --filter name=webapp-healthy
```

Attendu : healthy.

### Activation de l'accès exec

Pour le débogage, vous devez exécuter des commandes à l'intérieur du conteneur. Assurez-vous que le conteneur n'est pas exécuté avec --read-only ou un profil de sécurité restreint qui empêche exec. Nous utiliserons docker exec pour un débogage interactif.

## Vérification et diagnostics

Maintenant que le conteneur fonctionne avec une journalisation et des contrôles de santé appropriés, nous pouvons diagnostiquer les problèmes. Simulons une défaillance dans notre application Node.js : elle plante lorsqu'un certain point de terminaison est sollicité, à cause d'un rejet de promesse non géré.

### Observation des journaux

Tout d'abord, vérifiez les journaux :

```bash
docker logs webapp-healthy
```

Vous pourriez voir quelque chose comme :

```
Listening on port 8080
...
(node:1) UnhandledPromiseRejectionWarning: Error: Something went wrong
    at /app/server.js:15:11
...
```

Cela nous indique où se trouve l'erreur. Nous pouvons également suivre les journaux en direct :

```bash
docker logs -f --tail 50 webapp-healthy
```

### Exécution de commandes dans le conteneur

Nous pouvons inspecter le processus en cours d'exécution et l'environnement :

```bash
docker exec webapp-healthy ps aux
```

Sortie attendue (simplifiée) :

```
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.5 723456 45678 ?        Ssl  12:34   0:00 node server.js
root        12  0.0  0.0  15568  2024 ?        Rs   12:35   0:00 ps aux
```

Si le conteneur est en mauvaise santé, nous pouvons inspecter les journaux du contrôle de santé :

```bash
docker inspect --format '{{json .State.Health}}' webapp-healthy
```

La sortie comprend les entrées de journal des exécutions du contrôle de santé.

### Utilisation de docker inspect pour la configuration

docker inspect révèle une mine d'informations :

```bash
docker inspect webapp-healthy
```

Champs clés : NetworkSettings.IPAddress, Mounts, Config.Env, HostConfig.Binds, etc. Pour le débogage, vérifiez les paramètres réseau pour assurer le mappage des ports :

```bash
docker inspect --format '{{range $p, $conf := .NetworkSettings.Ports}}{{$p}} -> {{(index $conf 0).HostPort}}{{"\n"}}{{end}}' webapp-healthy
```

Sortie :

```
8080/tcp -> 8080
```

### Copie de fichiers pour analyse

Parfois, vous devez extraire des fichiers d'un conteneur, comme des cœurs de mémoire ou des journaux écrits sur disque. Utilisez docker cp :

```bash
docker cp webapp-healthy:/app/server.js ./server.js
```

### Débogage avec strace

Si le processus se comporte de manière étrange, strace peut tracer les appels système. D'abord, installez strace dans le conteneur (s'il n'est pas présent) :

```bash
docker exec -it webapp-healthy sh -c "apk add --no-cache strace"
```

Puis attachez-vous au processus :

```bash
docker exec -it webapp-healthy strace -p 1
```

Cela peut être lourd ; utilisez-le avec prudence sur les conteneurs de production.

## Modes de défaillance et récupération

Comprendre les modes de défaillance courants vous aide à vous préparer et à récupérer des problèmes. Nous couvrirons les problèmes typiques : conteneur qui se termine immédiatement, conteneur qui ne répond pas, disque plein à cause des journaux et mauvaise configuration réseau.

### Le conteneur se termine immédiatement

Si un conteneur démarre et se termine, vérifiez son code de sortie et ses journaux.

```bash
docker ps -a --filter "name=webapp" --format "{{.Status}}"
```

Exemple :

```
Exited (1) 10 seconds ago
```

Puis inspectez les journaux :

```bash
docker logs webapp
```

Souvent, c'est une dépendance manquante ou une erreur de configuration. Corrigez le Dockerfile ou le point d'entrée et redémarrez.

### Conteneur qui ne répond pas

Si un conteneur se bloque et ne répond pas aux commandes, il peut être à court de mémoire ou avoir un processus en interblocage. Vérifiez l'utilisation des ressources :

```bash
docker stats --no-stream webapp-healthy
```

Exemple de sortie :

```
CONTAINER ID   NAME              CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O   PIDS
3f4a5b6c7d8e   webapp-healthy    0.00%     45.2MiB / 3.8GiB      1.16%     2.1kB / 0B   0B / 0B     2
```

Si la mémoire est élevée, envisagez d'augmenter les limites ou de profiler l'application. Vous pouvez également utiliser docker exec pour exécuter des commandes de diagnostic, mais si le conteneur est vraiment bloqué, vous devrez peut-être le redémarrer.

### Disque plein à cause des journaux

Si le disque de l'hôte se remplit, la journalisation des conteneurs peut en être la cause. Vérifiez l'utilisation du disque des journaux Docker :

```bash
sudo du -sh /var/lib/docker/containers/*/*-json.log
```

Si un fichier est énorme, faites-le tourner ou supprimez-le. Mieux, configurez la rotation des journaux dès le départ. Pour effacer les journaux sans redémarrer le conteneur, utilisez :

```bash
truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log
```

Obtenez l'ID du conteneur avec docker ps -q.

### Mauvaise configuration réseau

Si l'application ne peut pas se connecter à d'autres services, inspectez le réseau :

```bash
docker network inspect bridge
```

Vérifiez si les conteneurs sont sur le même réseau. Pour les réseaux personnalisés, créez-en un :

```bash
docker network create mynet
```

Puis exécutez les conteneurs avec --network mynet. Utilisez docker exec pour tester la connectivité :

```bash
docker exec webapp-healthy curl http://database:5432
```

### Étapes de récupération

- Pour une récupération immédiate, redémarrez le conteneur : docker restart webapp-healthy.
- Si le problème persiste, recréez le conteneur avec une configuration actualisée.
- Pour les bugs de code, corrigez l'application et reconstruisez l'image.

### Retour en arrière

Conservez les versions d'image précédentes pour pouvoir revenir en arrière :

```bash
docker tag webapp:1.1 webapp:stable
docker run -d --name webapp-stable ... webapp:stable
```

Si la nouvelle version échoue, revenez à l'image stable.

## Pièges courants et comment les éviter

Même les utilisateurs expérimentés de Docker tombent dans des pièges prévisibles lors du débogage des conteneurs. Voici les plus courants, pourquoi ils surviennent et comment les éviter ou s'en remettre.

### Piège 1 : Négliger la rotation des journaux

**Pourquoi cela arrive** : La configuration de journalisation par défaut stocke toute la sortie du conteneur dans un seul fichier JSON sans limite de taille. Avec le temps, ce fichier peut remplir le disque de l'hôte, provoquant des défaillances à l'échelle du système.

**Comment éviter** : Définissez toujours des options de rotation des journaux lors de l'exécution des conteneurs, soit via les options --log-opt, soit dans le fichier de configuration du démon Docker (/etc/docker/daemon.json). Par exemple, ajoutez ceci à daemon.json :

```json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
```

Puis redémarrez le démon Docker.

**Récupération** : Si le disque est déjà plein, identifiez les gros fichiers journaux avec `sudo du -sh /var/lib/docker/containers/*/*-json.log` et tronquez-les comme indiqué dans la section précédente. Ensuite, mettez en place la rotation pour éviter la récidive.

### Piège 2 : Ignorer l'état du contrôle de santé

**Pourquoi cela arrive** : De nombreux développeurs ne configurent pas de contrôles de santé pour leurs conteneurs. Sans eux, Docker ne peut pas signaler si l'application fonctionne réellement, seulement si le processus est en cours d'exécution. Un conteneur peut être « Up » mais ne pas répondre.

**Comment éviter** : Définissez toujours un contrôle de santé dans votre Dockerfile ou lors de l'exécution du conteneur. Utilisez une commande légère qui vérifie la fonctionnalité essentielle de l'application, comme un point de terminaison HTTP ou un script simple. Par exemple :

```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1
```

**Récupération** : Si un conteneur est en mauvaise santé, inspectez les journaux de son contrôle de santé avec `docker inspect --format '{{json .State.Health}}' <container>` pour comprendre ce qui échoue.

### Piège 3 : Modifier un conteneur en cours d'exécution au lieu de le recréer

**Pourquoi cela arrive** : Il est tentant d'utiliser docker exec pour modifier un conteneur en cours d'exécution (par exemple, installer des paquets manquants) plutôt que de mettre à jour le Dockerfile et de recréer le conteneur. Cependant, ces modifications sont perdues lorsque le conteneur est redémarré ou remplacé, ce qui conduit à des environnements incohérents.

**Comment éviter** : Traitez les conteneurs comme immuables. Toute modification nécessaire doit être reflétée dans le Dockerfile ou le point d'entrée, et le conteneur doit être recréé. Utilisez docker exec uniquement pour les diagnostics, pas pour les changements persistants.

**Récupération** : Si vous avez déjà modifié un conteneur en cours d'exécution, documentez les modifications et reconstruisez immédiatement l'image avec ces modifications incorporées.

### Piège 4 : Négliger l'isolation réseau

**Pourquoi cela arrive** : Les conteneurs sur des réseaux différents ne peuvent pas communiquer par défaut. Les développeurs supposent souvent que tous les conteneurs peuvent se joindre, ce qui conduit à des erreurs de connexion refusée.

**Comment éviter** : Définissez explicitement des réseaux et attachez les conteneurs qui doivent communiquer. Utilisez des réseaux personnalisés pour une meilleure isolation et une résolution DNS. Par exemple :

```bash
docker network create backend
docker run -d --network backend --name db postgres
docker run -d --network backend --name webapp myapp
```

**Récupération** : Si un conteneur ne peut pas atteindre un autre service, vérifiez `docker network inspect <network>` pour voir si les deux conteneurs sont sur le même réseau. Sinon, recréez-en un avec le bon indicateur --network.

### Piège 5 : Exécuter des conteneurs en tant que root

**Pourquoi cela arrive** : De nombreuses images de base exécutent les processus en tant que root par défaut. Si l'application est compromise, l'attaquant obtient les privilèges root à l'intérieur du conteneur, ce qui peut potentiellement mener à une compromission de l'hôte.

**Comment éviter** : Créez toujours un utilisateur non root dans votre Dockerfile et exécutez l'application en tant que cet utilisateur. Par exemple :

```dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
```

**Récupération** : Si vous avez des conteneurs qui s'exécutent en tant que root, prévoyez de les reconstruire avec un utilisateur non root. C'est particulièrement important pour les environnements de production.

### Piège 6 : Utiliser l'étiquette latest en production

**Pourquoi cela arrive** : L'utilisation de l'étiquette « latest » facilite le tirage de l'image la plus récente, mais ce n'est pas déterministe. Le contenu de l'image peut changer sans avertissement, ce qui entraîne un comportement inattendu et rend le retour en arrière difficile.

**Comment éviter** : Utilisez des étiquettes de version explicites pour toutes les images, et envisagez d'utiliser des empreintes d'image pour une immuabilité absolue. Par exemple, utilisez `myapp:1.4.2` au lieu de `myapp:latest`.

**Récupération** : Si vous avez utilisé latest, commencez à étiqueter les images avec des versions spécifiques à l'avenir. Vous pouvez également utiliser `docker image inspect myapp:latest` pour trouver l'empreinte et l'étiqueter avec une version pour les déploiements actuels.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle pour le débogage et la maintenance de routine. Chaque élément a un propriétaire (un rôle responsable unique, pas un groupe) et une fréquence d'examen pour assurer la responsabilisation.

| Étape | Action | Commande/Extrait | Propriétaire | Fréquence d'examen |
|------|--------|------------------|-------|------------------|
| 1 | Vérifier la version et l'environnement de Docker | `docker version` et `docker info` | Ingénieur DevOps (par ex., Priya Shah) | Mensuelle ou après les mises à jour de l'hôte |
| 2 | Lister les conteneurs en cours d'exécution avec leur statut | `docker ps -a` | Ingénieur de garde | Quotidienne ou par incident |
| 3 | Vérifier les journaux du conteneur | `docker logs <container> --tail 100` | Propriétaire de l'application (par ex., Alex Chen) | À chaque déploiement ou incident |
| 4 | Inspecter la configuration du conteneur | `docker inspect <container>` | Ingénieur DevOps | Lorsque la configuration change |
| 5 | Vérifier l'état de santé | `docker inspect --format '{{.State.Health.Status}}' <container>` | Ingénieur de garde | Surveillance continue (avec alertes) |
| 6 | Voir l'utilisation des ressources | `docker stats --no-stream` | SRE (par ex., Maria Garcia) | Hebdomadaire ou en cas de problème de performance |
| 7 | Exécuter des commandes de diagnostic dans le conteneur | `docker exec -it <container> sh` | Développeur de garde | Uniquement pendant le débogage |
| 8 | Vérifier la configuration de journalisation | `docker inspect --format '{{.HostConfig.LogConfig.Type}}' <container>` | Ingénieur DevOps | Après la création du conteneur ou mensuellement |
| 9 | Tester la connectivité réseau depuis le conteneur | `docker exec <container> ping <target>` | Ingénieur réseau (si disponible) ou de garde | Lorsque des problèmes réseau sont signalés |
| 10 | Sauvegarder ou copier les fichiers importants du conteneur | `docker cp <container>:<path> <local-path>` | Propriétaire des données | Avant la suppression ou la migration du conteneur |
| 11 | Nettoyer les conteneurs et images inutilisés | `docker system prune` (avec prudence) | Ingénieur DevOps | Hebdomadaire (planifiée) |
| 12 | Documenter les conclusions et mettre à jour les procédures | - | Répondant à l'incident | Après chaque incident significatif |

## Conclusion

Le débogage des conteneurs Docker nécessite une approche systématique fondée sur la compréhension de l'architecture. En inventoriant votre environnement, en configurant la journalisation et les contrôles de santé de manière sécurisée, en utilisant les commandes de diagnostic de manière efficace et en vous préparant aux défaillances courantes, vous pouvez résoudre les problèmes rapidement et prévenir les futurs. Les commandes et configurations de ce guide fournissent une base solide. Commencez par appliquer ces techniques à vos conteneurs actuels et construisez une liste de contrôle de débogage adaptée à vos applications. N'oubliez pas de versionner les images pour un retour en arrière facile et de toujours surveiller les journaux et l'état de santé. Avec la pratique, le débogage des conteneurs devient une routine gérable plutôt qu'un mystère.