## Introduction

L'architecture de la CLI Docker expliquée avec des exemples pratiques aide les opérateurs à passer d'un problème observé à un résultat vérifié. Commencez par identifier la version installée, la topologie de déploiement, les prérequis et le composant exact inspecté.

Cet article se concentre sur l'architecture de la CLI Docker pour les développeurs, les consultants DevOps et les équipes techniques de startups. Il relie les composants de la CLI Docker, le flux de données de la CLI Docker, la conception de la CLI Docker et les opérations de la CLI Docker à des commandes, des sorties attendues, des signaux d'échec et des décisions de récupération adaptées à la technologie choisie.

L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés au lieu de secrets, vérifier le résultat et documenter comment récupérer si l'état attendu n'est pas atteint.

## Inventaire de la version et de l'environnement

Avant de dépanner ou de modifier une configuration Docker, établissez une image claire de l'environnement. Commencez par la version Docker installée et la séparation client/serveur. Exécutez `docker version` pour voir les détails du client et du serveur, y compris la version de l'API et l'OS/Arch. Une sortie typique ressemble à ceci :

```
Client: Docker Engine - Community
 Version:           24.0.5
 API version:       1.43
 Go version:        go1.20.6
 Git commit:        ced0996
 Built:             Fri Jul 21 12:50:07 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.5
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.6
  Git commit:       a61e2b4
  Built:            Fri Jul 21 12:50:07 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.21
  GitCommit:        3dce8eb055cbb6872793272b4f20ed16117344f8
 runc:
  Version:          1.1.7
  GitCommit:        v1.1.7-0-g860f061
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

Vérifiez également `docker info` pour le pilote de stockage, le pilote de journalisation, la version de cgroup et les détails du noyau. Cela vous indique si le démon utilise overlay2, quel est le pilote de journalisation par défaut et s'il y a des avertissements concernant des configurations non prises en charge. Par exemple, si vous voyez `WARNING: No swap limit support`, vous savez que les contraintes de mémoire peuvent ne pas être appliquées comme prévu.

Ensuite, listez tous les conteneurs avec `docker ps -a` pour voir à la fois les conteneurs en cours d'exécution et arrêtés. Utilisez un format personnalisé pour garder la sortie lisible :

```bash
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
```

Cela montre le nom du conteneur, l'image, l'état actuel (par exemple, Up 2 hours, Exited (1) 5 minutes ago) et les mappages de ports. Un conteneur arrêté avec un code de sortie non nul indique souvent un problème.

Pour les projets Compose, exécutez `docker compose ps` pour voir l'état de tous les services du projet actuel. Si vous devez inspecter la configuration que Compose utiliserait, exécutez `docker compose config` pour afficher le fichier Compose effectif, y compris les variables d'environnement fusionnées et les valeurs par défaut.

Lorsque la persistance des données est impliquée, confirmez où les fichiers sont stockés avant de modifier les conteneurs. Un volume nommé tel que `app_data:/var/lib/app` est géré par Docker et est généralement plus facile à réutiliser lors des reconstructions de conteneurs. Un montage bind tel que `./data:/var/lib/app` mappe directement un répertoire hôte et est utile pour le développement local, mais il peut exposer des problèmes de permissions, de portabilité et de sauvegarde si le même chemin n'existe pas sur une autre machine. Utilisez `docker inspect <conteneur>` pour vérifier les montages :

```bash
docker inspect --format '{{ json .Mounts }}' <conteneur>
```

Cela renvoie un tableau JSON des montages, y compris le type, la source, la destination et les options. Par exemple :

```json
[
  {
    "Type": "volume",
    "Name": "app_data",
    "Source": "/var/lib/docker/volumes/app_data/_data",
    "Destination": "/var/lib/app",
    "Driver": "local",
    "Mode": "",
    "RW": true,
    "Propagation": ""
  }
]
```

Dans le cadre de l'inventaire de l'environnement, un petit test local de type production doit inclure un test de redémarrage : arrêtez le conteneur, recréez-le et confirmez que l'application voit toujours les fichiers attendus. Si les données disparaissent, le service écrivait probablement dans le système de fichiers du conteneur au lieu d'un volume ou d'un montage.

## Chemin de configuration sûr

Les modifications de configuration sûres dans Docker commencent par comprendre la configuration actuelle et la portée de tout changement. La première règle est de séparer l'observation de l'intervention. Capturez d'abord l'état actuel et les horodatages, protégez les identifiants et le matériel privé, puis modifiez un seul élément délimité uniquement lorsque son rayon d'impact et son chemin de récupération sont compris.

Pour la configuration, la CLI Docker offre plusieurs façons d'inspecter et de modifier les paramètres. Utilisez `docker inspect` pour voir la configuration complète d'un conteneur, d'une image, d'un réseau, d'un volume ou d'un autre objet. La sortie peut être volumineuse, filtrez-la donc avec `--format` et les modèles Go. Par exemple, pour obtenir uniquement les variables d'environnement d'un conteneur :

```bash
docker inspect --format '{{ range .Config.Env }}{{ println . }}{{ end }}' <conteneur>
```

Cela imprime chaque variable d'environnement sur sa propre ligne. Pour voir la politique de redémarrage :

```bash
docker inspect --format '{{ .HostConfig.RestartPolicy.Name }}' <conteneur>
```

Si vous devez modifier la configuration d'un conteneur, comme les variables d'environnement ou les limites de ressources, vous devez généralement recréer le conteneur. Avec docker run, vous devez arrêter, supprimer et exécuter à nouveau avec les nouveaux indicateurs. Avec Compose, vous pouvez modifier le fichier `docker-compose.yml` et exécuter `docker compose up -d` pour recréer uniquement les services affectés. Compose gère la différence et recrée les conteneurs dont la configuration a changé.

Voici un exemple de flux de travail de modification sûr pour un service Compose qui a besoin d'une nouvelle variable d'environnement :

1. Enregistrez l'état actuel : `docker compose ps` et `docker compose logs --tail 50 <service>`.
2. Sauvegardez le fichier Compose existant : `cp docker-compose.yml docker-compose.yml.bak-20230721`.
3. Modifiez le fichier pour ajouter la variable d'environnement sous `environment:` pour le service.
4. Validez la configuration : `docker compose config` pour vous assurer que la syntaxe et les valeurs fusionnées sont correctes.
5. Appliquez la modification : `docker compose up -d <service>`.
6. Vérifiez que le nouveau conteneur fonctionne : `docker compose ps` et inspectez l'environnement avec `docker inspect --format '{{ .Config.Env }}' <conteneur>`.
7. Si quelque chose ne va pas, revenez en arrière en restaurant la sauvegarde et en exécutant à nouveau `docker compose up -d`.

Pour les environnements de type production, testez toujours les modifications de configuration dans un environnement de préproduction d'abord. Utilisez des fichiers de substitution Compose spécifiques à l'environnement (par exemple, `docker-compose.prod.yml`) pour éviter d'appliquer accidentellement des paramètres de développement à la production.

Lors de la gestion des secrets, ne les passez jamais directement dans la ligne de commande et ne les codez pas en dur dans les fichiers Compose. Utilisez plutôt les secrets Docker (dans Swarm) ou des outils de gestion de secrets externes, ou au moins utilisez des variables d'environnement avec un espace réservé et injectez la valeur réelle au moment de l'exécution. Par exemple, dans Compose, vous pouvez utiliser `${DB_PASSWORD}` et définir la valeur réelle dans un fichier `.env` qui n'est pas validé dans le contrôle de version.

## Vérification et diagnostic

La vérification et le diagnostic nécessitent une approche systématique : observer, émettre des hypothèses, tester et confirmer. Docker fournit plusieurs commandes pour cela. Commencez par `docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"` pour voir ce qui fonctionne et leur état. Ensuite, utilisez `docker logs <conteneur> --tail 100` pour lire la sortie récente. Ajoutez `-f` pour suivre les journaux en temps réel, ou `--since 10m` pour voir les journaux des 10 dernières minutes. Pour les conteneurs qui journalisent vers stderr, utilisez `docker logs <conteneur> 2>&1 | tail -100` pour combiner les flux.

Si un conteneur fonctionne mais que l'application ne répond pas, inspectez l'état de santé du conteneur si une vérification de santé est définie. Exécutez `docker inspect --format '{{ .State.Health.Status }}' <conteneur>` pour obtenir l'état de santé (starting, healthy, unhealthy). Pour voir les journaux de la vérification de santé, utilisez `docker inspect --format '{{ json .State.Health.Log }}' <conteneur>` et analysez la sortie.

Pour une inspection plus approfondie, utilisez `docker exec -it <conteneur> sh` pour obtenir un shell à l'intérieur du conteneur (si l'image a un shell). À partir de là, vous pouvez vérifier les processus avec `ps aux`, les connexions réseau avec `netstat -tulpn` ou `ss -tulpn`, et le contenu des fichiers. Cela est particulièrement utile pour le débogage sans modifier l'image.

Pour les projets Compose, utilisez `docker compose ps` pour voir l'état des services, `docker compose logs -f <service>` pour suivre les journaux, et `docker compose exec <service> sh` pour exécuter des commandes à l'intérieur d'un conteneur de service. Si vous devez tester une connexion réseau entre conteneurs, utilisez `docker compose exec <service> ping <autre_service>` ou `nc -zv <autre_service> <port>` si netcat est disponible.

Le diagnostic comprend également la surveillance de l'utilisation des ressources. `docker stats` affiche le CPU, la mémoire, les E/S réseau et les E/S bloc pour tous les conteneurs en cours d'exécution. Utilisez `docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"` pour un instantané unique. Cela aide à identifier les conteneurs qui consomment trop de ressources.

Lors du dépannage d'un problème spécifique, suivez cette séquence :

1. Reproduisez le problème et capturez le message d'erreur exact.
2. Vérifiez l'état du conteneur et le code de sortie : `docker ps -a` et `docker inspect --format '{{ .State.ExitCode }}' <conteneur>`.
3. Lisez les journaux : `docker logs <conteneur>` avec les indicateurs appropriés.
4. Vérifiez la configuration du conteneur : `docker inspect <conteneur>`.
5. Testez la connectivité : depuis l'intérieur du conteneur ou depuis l'hôte vers les ports du conteneur.
6. Si nécessaire, utilisez strace ou des outils similaires à l'intérieur du conteneur pour tracer les appels système.

Documentez chaque constatation et les commandes utilisées, afin que le diagnostic soit reproductible.

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

Les systèmes Docker échouent de manière prévisible. Connaître ces modes de défaillance et comment récupérer est crucial pour minimiser les temps d'arrêt.

### Le conteneur se termine immédiatement après le démarrage

Cela indique souvent une commande mal configurée, une variable d'environnement manquante ou une erreur d'application. Vérifiez le code de sortie avec `docker inspect --format '{{ .State.ExitCode }}' <conteneur>` et les journaux avec `docker logs <conteneur>`. Codes de sortie courants : 1 erreur générale, 126 commande non exécutable, 127 commande introuvable. Par exemple, si le conteneur essaie d'exécuter un script qui n'est pas exécutable, vous pourriez voir le code de sortie 126. Récupération : corrigez le Dockerfile ou la commande, reconstruisez l'image et redémarrez.

### Le conteneur fonctionne mais l'application est inaccessible

Cela peut être dû à des erreurs de mappage de ports, une mauvaise configuration réseau ou l'application qui écoute sur la mauvaise interface. Vérifiez les mappages de ports avec `docker port <conteneur>` et les paramètres réseau du conteneur avec `docker inspect --format '{{ json .NetworkSettings.Ports }}' <conteneur>`. Assurez-vous que l'application à l'intérieur du conteneur écoute sur 0.0.0.0, pas seulement sur 127.0.0.1, car le localhost du conteneur est différent de celui de l'hôte. Récupération : ajustez la configuration de l'application ou la commande docker run pour mapper correctement les ports.

### Les données de volume ne persistent pas

Si les données disparaissent après la recréation du conteneur, le conteneur écrivait probablement dans sa couche inscriptible plutôt que dans un volume ou un montage bind. Vérifiez les montages avec `docker inspect --format '{{ json .Mounts }}' <conteneur>`. Récupération : mettez à jour le Dockerfile ou le fichier Compose pour utiliser un volume nommé ou un montage bind, et si nécessaire, copiez les données hors de l'ancien conteneur en utilisant `docker cp` avant de le recréer.

### Le démon ne répond pas

Si `docker ps` se bloque ou renvoie une erreur comme `Cannot connect to the Docker daemon at unix:///var/run/docker.sock`, le démon est peut-être arrêté. Vérifiez l'état du démon avec `systemctl status docker` (sur les systèmes systemd) ou `service docker status`. Consultez les journaux du démon avec `journalctl -u docker` ou `/var/log/docker.log`. Récupération : redémarrez le démon avec `systemctl restart docker` et recherchez la cause.

### Échec de l'extraction d'images

Les images peuvent ne pas être extraites en raison de problèmes réseau, d'authentification au registre ou de balises manquantes. Vérifiez la sortie de `docker pull` pour les erreurs spécifiques. Pour les registres privés, assurez-vous d'être connecté avec `docker login <registre>` et que les identifiants sont valides. Pour les limites de débit sur Docker Hub, envisagez d'utiliser un miroir ou d'augmenter la limite d'extraction en vous authentifiant. Récupération : résolvez les problèmes réseau ou d'authentification et réessayez.

### Épuisement des ressources

Les conteneurs peuvent être tués en raison de mémoire insuffisante (OOM) ou de limites CPU. Vérifiez `docker inspect --format '{{ .State.OOMKilled }}' <conteneur>` pour voir si OOM l'a tué. Vérifiez `docker events` pour les événements OOM. Récupération : augmentez les limites de mémoire avec les indicateurs `--memory` et `--memory-swap`, ou optimisez l'application.

Pour tous les modes de défaillance, ayez un plan de restauration. Pour les services sans état, revenir à une version d'image précédente est facile. Pour les services avec état, assurez-vous que les sauvegardes fonctionnent avant tout changement.

## Liste de contrôle des opérations

Utilisez cette liste de contrôle avant et après toute opération Docker pour garantir la cohérence et la sécurité.

Avant d'apporter des modifications :

1. Enregistrez l'état actuel : `docker ps -a`, `docker images`, `docker network ls`, `docker volume ls`.
2. Vérifiez la santé du démon : `docker info` et recherchez les avertissements.
3. Sauvegardez tous les fichiers de configuration critiques (fichiers Compose, daemon.json).
4. Si vous modifiez des conteneurs de données, sauvegardez les volumes : `docker run --rm -v <volume>:/data -v $(pwd):/backup alpine tar czf /backup/volume-backup.tar.gz -C /data .`
5. Déterminez le plus petit changement qui atteint l'objectif.
6. Définissez le résultat attendu et comment le vérifier.
7. Documentez la procédure de restauration.

Après avoir apporté des modifications :

1. Vérifiez que le changement a pris effet : par exemple, `docker inspect` ou `docker ps`.
2. Exécutez des tests fonctionnels : par exemple, `curl` le point de terminaison du service.
3. Vérifiez les journaux pour les erreurs : `docker logs --tail 100 <conteneur>`.
4. Surveillez l'utilisation des ressources pendant quelques minutes : `docker stats --no-stream`.
5. Mettez à jour la documentation avec le nouvel état.
6. Si quelque chose échoue, exécutez la procédure de restauration et analysez la cause.

Propriété et fréquence de révision : Pour chaque changement de configuration majeur, attribuez un seul propriétaire responsable (par exemple, un ingénieur DevOps ou un chef d'équipe) qui approuve le changement et s'assure que la liste de contrôle est suivie. Révisez cette liste de contrôle elle-même mensuellement pour intégrer les leçons apprises.

Exemple : Lors de la mise à jour d'un service web de production avec Compose, propriétaire : « Priya Shah, responsable de l'ingénierie ». Elle approuve la nouvelle étiquette d'image après les tests de préproduction. Le changement est appliqué pendant une fenêtre de maintenance, et l'équipe réexamine les résultats du déploiement lors de la prochaine révision hebdomadaire des incidents.

## Pièges courants et comment les éviter

### Exécuter des conteneurs avec `--privileged`

`--privileged` accorde au conteneur presque toutes les capacités de l'hôte, ce qui est dangereux. Il est souvent utilisé inutilement pour des tâches comme l'accès aux périphériques USB ou le débogage. Utilisez plutôt `--cap-add` pour ajouter uniquement les capacités nécessaires, ou utilisez des périphériques avec `--device`. Évitez `--privileged` en production.

### Utiliser l'étiquette `latest` en production

L'étiquette `latest` est une cible mouvante et peut entraîner des déploiements imprévisibles. Épinglez toujours à une version ou un condensé spécifique (par exemple, `myapp:1.2.3` ou `myapp@sha256:...`). Cela garantit la reproductibilité et facilite la restauration.

### Coder en dur des secrets dans les Dockerfiles ou les fichiers Compose

Les secrets dans les images peuvent être extraits même après avoir été supprimés dans une couche. Utilisez des variables d'environnement au moment de l'exécution, des secrets Docker ou des gestionnaires de secrets externes. Ne validez jamais de secrets dans le contrôle de version.

### Ne pas définir de limites de ressources

Sans `--memory` et `--cpus`, un conteneur peut consommer toutes les ressources de l'hôte et affamer d'autres processus. Définissez toujours des limites en production et envisagez de définir des limites par défaut dans la configuration du démon.

### Ignorer les codes de sortie des conteneurs

Un conteneur qui se termine avec un code non nul est un signal que quelque chose s'est mal passé. Vérifiez toujours le code de sortie et les journaux, et définissez des politiques de redémarrage appropriées (`--restart=unless-stopped` pour la plupart des services), mais évitez les boucles de redémarrage qui cachent les problèmes.

### Abuser des montages bind en production

Les montages bind dépendent du système de fichiers hôte et peuvent causer des problèmes de permissions et de portabilité entre environnements. Préférez les volumes nommés pour les données persistantes. Si des montages bind sont nécessaires, documentez les exigences de chemin hôte et assurez des permissions cohérentes.

### Ne pas utiliser de vérifications de santé

Sans vérification de santé, Docker ne peut pas déterminer si un conteneur est réellement sain, seulement s'il est en cours d'exécution. Définissez une vérification de santé dans le Dockerfile ou le fichier Compose pour permettre aux outils d'orchestration de réagir aux conteneurs malsains.

### Accumuler des images et volumes inutilisés

Au fil du temps, les images et volumes inutilisés consomment de l'espace disque. Utilisez `docker system prune` avec précaution pour supprimer les objets inutilisés. Planifiez un nettoyage régulier avec des filtres : `docker image prune -a --filter "until=168h"` pour supprimer les images de plus d'une semaine.

## Conclusion

L'architecture de la CLI Docker expliquée avec des exemples pratiques devient vraiment utile lorsque chaque recommandation est délimitée par version, observable et réversible là où la technologie le permet. Copier une commande sans vérifier les prérequis et la sortie attendue n'est pas une procédure d'exploitation ; c'est un pari.

Comme prochaine étape, choisissez une vérification à faible risque de cet article, enregistrez l'état actuel, exécutez la vérification documentée, comparez le résultat avec le signal attendu et examinez les dépendances telles que le contexte Docker, les images Docker et les conteneurs Docker.

Un flux de travail technique fiable rend les défaillances visibles, protège les valeurs sensibles, limite les modifications à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision. En suivant l'approche structurée décrite ici, vous pouvez exploiter Docker avec une plus grande confiance et réduire le risque de temps d'arrêt imprévu.