Introduction
Les concepts avancés des conteneurs Docker vont au-delà de l'exécution d'une simple image. Ils impliquent un flux de travail délibéré qui part d'un problème ou d'un objectif observé jusqu'à un résultat vérifié. Cet article propose une plongée approfondie, axée sur la pratique, dans les rouages internes des conteneurs Docker, leur architecture et les schémas opérationnels, avec des commandes concrètes, des sorties attendues et des étapes de récupération. Que vous soyez développeur, consultant DevOps ou membre d'une équipe de startup technique, vous apprendrez à gérer les conteneurs de manière sûre et efficace.
L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, protéger les données sensibles, vérifier les résultats et documenter les chemins de récupération. Chaque section se concentre sur un aspect différent de la gestion des conteneurs : inventorier votre environnement, configurer en toute sécurité, vérifier le comportement, gérer les échecs et suivre une liste de contrôle opérationnelle reproductible. Nous éviterons les conseils génériques et montrerons à la place des commandes et configurations Docker spécifiques que vous pouvez utiliser immédiatement.
Inventaire des versions et de l'environnement
Avant d'apporter des modifications, vous devez comprendre avec quoi vous travaillez. Commencez par inventorier la version de Docker, les conteneurs en cours d'exécution, leurs images et la topologie de déploiement. Cette étape en lecture seule évite les erreurs dues à des versions incompatibles ou à des états inconnus.
Vérification de la version et des informations de Docker
Exécutez :
docker version
Cela affiche à la fois les versions du client et du serveur. Par exemple :
Client: Docker Engine - Community
Version: 24.0.7
API version: 1.43
Go version: go1.20.10
Git commit: 311b9ff
Built: Tue Oct 24 14:17:39 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.7
API version: 1.43 (minimum version 1.12)
Go version: go1.20.10
Git commit: 311b9ff
Built: Tue Oct 24 14:17:39 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.26
GitCommit: 3dd1e886e55dd695541fdcd67420c2888645a495
runc:
Version: 1.1.10
GitCommit: v1.1.10-0-g18a0cb0
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Points clés : assurez-vous que les versions d'API du client et du serveur sont compatibles. Si elles ne le sont pas, mettez à niveau le client ou le démon. Notez le pilote de stockage et le pilote de journalisation à partir de docker info ; ils affectent les performances et la collecte des journaux.
Exécutez docker info pour voir les détails :
docker info
La sortie comprend le pilote de stockage (par exemple, overlay2), le pilote de journalisation (par exemple, json-file) et la version du noyau. Cela aide à diagnostiquer les problèmes de système de fichiers ou de journalisation.
Lister les conteneurs et leur état
Pour voir tous les conteneurs (en cours d'exécution et arrêtés) :
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
Exemple de sortie :
NAMES IMAGE STATUS PORTS
web-app nginx:1.25 Up 2 hours 0.0.0.0:8080->80/tcp
db postgres:16 Up 2 hours 5432/tcp
old-worker python:3.9 Exited (1) 5 minutes ago
Le conteneur Exited montre un échec qui doit être investigué. Utilisez docker logs pour voir la sortie récente :
docker logs old-worker --tail 50
Cela peut révéler l'erreur à l'origine de la sortie.
Inspecter les détails d'un conteneur
Lorsque vous avez besoin des montages, des réseaux, des variables d'environnement ou de l'état de santé, utilisez docker inspect :
docker inspect web-app
Cela produit un tableau JSON. Vous pouvez filtrer avec --format :
docker inspect web-app --format '{{.State.Status}} {{.HostConfig.Binds}} {{.NetworkSettings.IPAddress}}'
Exemple : running [/data:/var/lib/app] 172.17.0.2
Pour l'état de santé, si l'image définit un healthcheck, vérifiez {{.State.Health.Status}}.
Travailler avec des projets Docker Compose
Pour les configurations multi-conteneurs, utilisez Docker Compose :
docker compose ps
Affiche les services, leur état et les ports. Pour suivre les journaux :
docker compose logs -f web
Pour obtenir un shell dans un service en cours d'exécution sans modifier l'image :
docker compose exec web sh
C'est essentiel pour déboguer les problèmes de configuration.
Stockage des données : volumes vs montages bind
Comprendre où vivent les données est critique. Un volume nommé est géré par Docker et constitue le moyen recommandé pour les données persistantes :
services:
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Un montage bind mappe directement un répertoire hôte :
services:
app:
image: myapp:1.0
volumes:
- ./data:/var/lib/app
Les montages bind sont utiles pour le développement mais peuvent causer des problèmes de permissions et manquer de portabilité. Vérifiez toujours que le répertoire hôte existe et a les bonnes permissions.
Test de redémarrage
Pour garantir que les données persistent correctement, effectuez un test de redémarrage :
docker stop db
docker rm db
docker compose up -d
docker exec db ls /var/lib/postgresql/data
Si le répertoire de données est vide après le redémarrage, le conteneur écrivait probablement dans sa couche inscriptible au lieu du volume. Utilisez des volumes pour tout ce qui doit survivre à la recréation du conteneur.
Chemin de configuration sécurisé
Modifier la configuration d'un conteneur nécessite une approche structurée. Commencez par une observation de référence, effectuez un changement unique et limité, et vérifiez le résultat. Évitez les modifications multiples simultanées car elles rendent difficile l'identification de la cause et de l'effet.
Variables d'environnement et secrets
Ne codez jamais en dur des secrets dans les Dockerfiles ou les fichiers Compose. Utilisez des variables d'environnement avec des valeurs par défaut pour le développement, et des secrets pour la production.
Exemple de Dockerfile :
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV APP_ENV=production
CMD ["python", "app.py"]
Dans Compose, utilisez des variables d'environnement :
services:
app:
image: myapp:1.0
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
env_file:
- .env
Mais pour les secrets, utilisez les secrets Docker (Swarm) ou des montages bind avec des permissions restreintes. Ne mettez jamais de mots de passe en texte clair dans docker-compose.yml.
Limites de ressources et options de sécurité
Définissez des limites de CPU et de mémoire pour éviter les problèmes de voisin bruyant :
services:
app:
image: myapp:1.0
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Pour la sécurité, exécutez les conteneurs en tant qu'utilisateur non root. Dans le Dockerfile :
RUN useradd -m appuser
USER appuser
Supprimez les capacités et utilisez un système de fichiers racine en lecture seule lorsque c'est possible :
services:
app:
image: myapp:1.0
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
Configuration réseau
Utilisez des réseaux définis par l'utilisateur pour l'isolation et la découverte de services basée sur DNS :
docker network create mynet
Attachez les conteneurs :
services:
app:
networks:
- mynet
db:
networks:
- mynet
networks:
mynet:
external: true
Les conteneurs sur le même réseau peuvent se résoudre mutuellement par nom de service.
Étiquetage et versionnement des images
Utilisez toujours des étiquettes d'image spécifiques en production, pas latest. Par exemple, nginx:1.25.3 au lieu de nginx:latest. Cela garantit la reproductibilité et facilite le retour en arrière.
Apporter des modifications en toute sécurité
Lorsque vous devez modifier une configuration, suivez ces étapes :
- Enregistrez l'état actuel :
docker inspect container > before.json - Effectuez la modification unique dans le fichier Compose ou le Dockerfile.
- Appliquez la modification :
docker compose up -d --no-deps app(recrée uniquement le service app). - Vérifiez : consultez les journaux, l'état de santé et la fonctionnalité.
- Conservez l'état précédent pour un retour en arrière facile :
docker compose up -d --no-deps --force-recreate appavec l'ancienne étiquette d'image si nécessaire.
Vérification et diagnostics
La vérification consiste à confirmer qu'un conteneur est sain et se comporte comme prévu. Les diagnostics impliquent d'enquêter lorsque quelque chose ne va pas.
Healthchecks
Définissez un healthcheck dans votre Dockerfile ou votre fichier Compose pour surveiller automatiquement la santé du conteneur.
Exemple de Dockerfile :
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
Exemple Compose :
services:
web:
image: nginx:1.25
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
Vérifiez l'état de santé :
docker inspect --format='{{.State.Health.Status}}' web
Sortie : healthy ou unhealthy.
Journalisation et surveillance
Utilisez docker logs avec des options :
docker logs --since 10m --until 2m web
Pour des journaux structurés, configurez un pilote de journalisation. Dans Compose :
services:
app:
image: myapp:1.0
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Pour une surveillance en temps réel, utilisez docker stats :
docker stats --no-stream
Exemple de sortie :
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
abc123 web 0.50% 10.2MiB / 1.95GiB 0.51% 1.2kB / 0B 0B / 0B 5
Surveillez les fuites de mémoire ou une utilisation élevée du CPU.
Diagnostics réseau
Testez la connectivité entre les conteneurs :
docker exec app ping db
Mais toutes les images n'ont pas ping. Utilisez curl ou nc :
docker exec app curl http://db:5432
Ou utilisez docker run --rm --network mynet appropriate/curl curl -v http://db:5432
Vérifiez les mappages de ports :
docker port web
Sortie : 80/tcp -> 0.0.0.0:8080
Assurez-vous qu'il n'y a pas de conflits de ports avec docker ps et ss -tulpn sur l'hôte.
Système de fichiers et utilisation du disque
Vérifiez l'utilisation du disque :
docker system df
Sortie :
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 8 3.45GB 1.2GB (34%)
Containers 15 10 1.1GB 300MB (27%)
Local Volumes 8 6 2.8GB 1.5GB (53%)
Build Cache 20 0 1.6GB 1.6GB (100%)
Nettoyez les ressources inutilisées avec docker system prune (attention : cela supprime les conteneurs arrêtés, les réseaux inutilisés, les images sans étiquette et le cache de construction).
Débogage avec Exec et conteneurs temporaires
Obtenez un shell dans un conteneur en cours d'exécution :
docker exec -it web /bin/bash
Si le conteneur n'a pas de shell, exécutez un conteneur temporaire avec la même image et le même réseau :
docker run --rm -it --network container:web nicolaka/netshoot
Cela permet de dépanner le réseau à partir du même espace de noms réseau.
Modes de défaillance et récupération
Les conteneurs peuvent échouer pour diverses raisons. Cette section couvre les modes de défaillance courants, comment les identifier et comment récupérer.
Le conteneur se termine de manière inattendue
Symptôme : docker ps montre que le conteneur n'est pas en cours d'exécution ; docker ps -a montre Exited (code).
Diagnostic :
docker logs <container> --tail 100
Recherchez les messages d'erreur. Vérifiez le code de sortie :
- Code de sortie 1 : erreur d'application.
- Code de sortie 137 : tué par OOM (mémoire insuffisante).
- Code de sortie 143 : SIGTERM (arrêt gracieux).
Si OOM, augmentez la limite de mémoire ou optimisez l'application. Si erreur d'application, corrigez le code ou la configuration.
Récupération : après correction, recréez le conteneur : docker compose up -d
Politiques de redémarrage
Définissez des politiques de redémarrage pour redémarrer automatiquement les conteneurs sauf s'ils sont arrêtés manuellement :
services:
app:
image: myapp:1.0
restart: unless-stopped
Autres options : no, on-failure, always.
Perte de données due à un volume manquant
Symptôme : après avoir recréé le conteneur, les données ont disparu.
Diagnostic : vérifiez si le conteneur avait un volume monté. docker inspect -f '{{json .Mounts}}' container
Si les montages sont vides, les données ont été écrites dans la couche du conteneur (éphémère).
Récupération : si le conteneur existe toujours (arrêté), vous pouvez copier les données :
docker cp container:/path/to/data ./recovered
Ensuite, recréez avec un montage de volume approprié.
Prévention : définissez toujours des volumes pour les données persistantes.
Problèmes de connectivité réseau
Symptôme : les conteneurs ne peuvent pas communiquer entre eux ou avec des ressources externes.
Diagnostic :
- Vérifiez les réseaux :
docker network ls - Inspectez le réseau du conteneur :
docker inspect -f '{{json .NetworkSettings.Networks}}' container - Testez la résolution DNS :
docker exec app nslookup db(si disponible) - Vérifiez le pare-feu et les règles iptables de l'hôte.
Récupération : assurez-vous que les conteneurs sont sur le même réseau défini par l'utilisateur. Si vous utilisez le pont par défaut, passez à un réseau défini par l'utilisateur pour un DNS automatique.
Échecs de tirage ou de construction d'image
Symptôme : docker pull ou docker build échoue.
Diagnostic : vérifiez la connectivité réseau, l'état de Docker Hub et l'authentification. Si registre privé, assurez-vous de la connexion : docker login registry.example.com
Pour les échecs de construction, examinez les journaux de construction. Problèmes courants : dépendances manquantes, image de base incorrecte, erreurs de syntaxe.
Récupération : corrigez le Dockerfile et reconstruisez. Utilisez --no-cache pour forcer une construction fraîche si le cache est corrompu.
Épuisement des ressources
Symptôme : l'hôte devient lent, les conteneurs ne répondent plus.
Diagnostic : docker stats et free -m, top sur l'hôte. Vérifiez les conteneurs qui consomment un CPU ou une mémoire excessifs.
Récupération : limitez les ressources des conteneurs fautifs, ou scalez horizontalement.
Liste de contrôle opérationnelle
Cette liste de contrôle résume les étapes clés pour une gestion sûre et efficace des conteneurs Docker. Attribuez un propriétaire unique pour chaque décision et révisez périodiquement.
| Élément | Propriétaire | Fréquence | Commande/Action | Résultat attendu |
|---|---|---|---|---|
| Vérifier la compatibilité des versions Docker | Priya Shah, Responsable Ingénierie | Mensuel | docker version | Versions client et serveur dans la plage prise en charge |
| Examiner les conteneurs en cours d'exécution et leurs états | Ingénieur DevOps | Hebdomadaire | docker ps -a --format "table {{.Names}}\t{{.Status}}" | Aucun conteneur arrêté de manière inattendue |
| Vérifier les journaux des conteneurs pour les erreurs | Ingénieur DevOps | Quotidien | docker logs <container> --tail 100 | Aucune erreur répétée |
| Inspecter la persistance des données | Ingénieur DevOps | Après tout changement | docker inspect -f '{{json .Mounts}}' <container> | Volume ou montage bind présent pour les données persistantes |
| Tester la récupération après redémarrage | Ingénieur QA | Mensuel | docker stop <container> && docker start <container> | Le conteneur démarre et les données sont intactes |
| Surveiller l'utilisation des ressources | Ingénieur DevOps | En continu | docker stats --no-stream | Utilisation dans les limites |
| Examiner les configurations de sécurité | Équipe de sécurité | Trimestriel | docker inspect -f '{{.HostConfig.SecurityOpt}}' <container> | Utilisateur non root, aucun nouveau privilège |
| Mettre à jour les images et les dépendances | Ingénieur DevOps | Mensuel | docker pull <image> | Dernière version corrigée |
| Tester la procédure de sauvegarde et de restauration | Ingénieur DevOps | Trimestriel | Effectuer un exercice de sauvegarde et de restauration | Données récupérables dans le RTO |
| Examiner la segmentation du réseau | Administrateur réseau | Trimestriel | docker network inspect <network> | Isolation et connectivité appropriées |
Pour chaque élément, documentez le résultat réel et tout écart. Le propriétaire est responsable de la résolution des problèmes.
Pièges et erreurs courants
De nombreux problèmes de conteneurs proviennent d'une mauvaise compréhension des fondamentaux de Docker. Voici les pièges courants et comment les éviter.
Exécution en tant que root
Les conteneurs s'exécutent souvent en tant que root par défaut, ce qui constitue un risque de sécurité. Si un processus est compromis, l'attaquant obtient les privilèges root sur l'hôte (sauf si une isolation supplémentaire est utilisée).
Pourquoi cela arrive : De nombreuses images de base utilisent root par défaut et les développeurs ne le changent pas.
Comment éviter : Créez un utilisateur non root dans le Dockerfile :
RUN useradd -m appuser
USER appuser
Utilisation de l'étiquette latest
L'utilisation de l'étiquette latest peut entraîner des changements inattendus lors du tirage d'images, provoquant des ruptures ou des incohérences.
Pourquoi cela arrive : Commodité et absence de verrouillage de version.
Comment éviter : Utilisez des étiquettes spécifiques, par exemple, nginx:1.25.3.
Stockage des données dans la couche du conteneur
Toute donnée écrite dans la couche inscriptible du conteneur est perdue lorsque le conteneur est supprimé. C'est une cause fréquente de perte de données.
Pourquoi cela arrive : Ne pas comprendre le système de fichiers en couches de Docker ; supposer que les données persistent automatiquement.
Comment éviter : Utilisez des volumes ou des montages bind pour toutes les données persistantes. Testez avec un redémarrage.
Exposition de trop de ports
Exposer des ports inutiles augmente la surface d'attaque.
Pourquoi cela arrive : Les développeurs exposent des ports par commodité sans considérer la sécurité.
Comment éviter : Ne mappez que les ports nécessaires. Utilisez EXPOSE dans le Dockerfile comme documentation, mais la publication réelle se fait via -p ou ports dans Compose.
Ne pas définir de limites de ressources
Sans limites, les conteneurs peuvent consommer toutes les ressources de l'hôte, provoquant un déni de service pour les autres conteneurs.
Pourquoi cela arrive : Les valeurs par défaut permettent une utilisation illimitée des ressources.
Comment éviter : Définissez des limites de CPU et de mémoire dans Compose ou dans les commandes run.
Ignorer les healthchecks
Sans healthchecks, les outils d'orchestration ne peuvent pas déterminer la santé des conteneurs, ce qui entraîne l'envoi de trafic vers des conteneurs non sains.
Pourquoi cela arrive : Ne pas implémenter de healthchecks dans les images ou les fichiers Compose.
Comment éviter : Ajoutez des healthchecks pour les services critiques.
Utilisation excessive de docker exec pour les changements de configuration
Apporter des modifications à l'intérieur d'un conteneur en cours d'exécution (par exemple, installer des paquets, modifier des fichiers de configuration) n'est pas persistant et conduit à une dérive de configuration.
Pourquoi cela arrive : Corrections rapides sans mettre à jour l'image.
Comment éviter : Apportez les modifications dans le Dockerfile ou le fichier Compose, reconstruisez et recréez le conteneur.
Ne pas nettoyer les ressources inutilisées
Au fil du temps, les images, conteneurs et volumes inutilisés consomment de l'espace disque.
Pourquoi cela arrive : Négligence de la maintenance de routine.
Comment éviter : Exécutez régulièrement docker system prune (avec prudence) et surveillez l'utilisation du disque.
Conclusion
Les concepts avancés des conteneurs Docker exigent une approche disciplinée de l'observation, de la configuration, de la vérification et de la récupération. En suivant les flux de travail et les listes de contrôle de cet article, vous pouvez gérer les conteneurs en toute confiance et éviter les pièges courants.
Commencez par une vérification à faible risque : inventorier votre environnement avec docker version et docker ps -a, enregistrez la sortie et comparez-la avec l'état attendu. Ensuite, mettez en œuvre des pratiques de configuration sûres telles que des utilisateurs non root, des limites de ressources et des volumes persistants. Intégrez des healthchecks et une journalisation pour une vérification continue.
N'oubliez pas que Docker est un outil puissant, mais ses avantages ne se réalisent que lorsqu'il est utilisé correctement. Rendez les échecs visibles, protégez les données sensibles, limitez les changements et définissez des procédures de récupération avant qu'un incident ne survienne. Ce faisant, vous garantissez que vos applications conteneurisées restent fiables, sécurisées et maintenables.