Concepts de Docker et des conteneurs
63 concepts expliqués simplement, chacun relié aux exercices qui l'entraînent.
Conteneurs et images
- Conteneur Un conteneur est un processus ordinaire du serveur, isolé par le noyau : il voit ses propres fichiers, son propre réseau et sa propre liste de processus, et reçoit une part du CPU et de la mémoire. Il démarre en une fraction de seconde car il n'y a aucun système d'exploitation à démarrer.
- Image Une image est un paquet en lecture seule de tout ce dont un programme a besoin pour tourner : fichiers, bibliothèques, variables d'environnement et commande de démarrage. On la construit une fois, puis on la lance partout, autant de fois qu'on veut.
- Image ou conteneur L'image est la recette, le conteneur est le plat : une image peut lancer autant de conteneurs qu'on veut, chacun avec son propre état. Supprimer un conteneur laisse l'image intacte, et modifier un conteneur ne modifie jamais l'image.
- Couche inscriptible Chaque conteneur reçoit une fine couche inscriptible au-dessus de son image. Tous les fichiers qu'il crée ou modifie y atterrissent, et cette couche est supprimée avec le conteneur.
- Cycle de vie d'un conteneur Un conteneur passe par les états créé, en marche, arrêté et supprimé. docker stop, start et restart le font passer de l'un à l'autre ; seul docker rm le supprime, avec sa couche inscriptible.
- Processus principal (PID 1) Un conteneur vit exactement aussi longtemps que son processus principal, celui lancé par CMD ou ENTRYPOINT. Quand ce processus se termine, le conteneur s'arrête, quoi qu'il tourne d'autre à l'intérieur.
- Codes de sortie Quand un conteneur s'arrête, docker ps -a affiche le code de sortie de son processus principal. Ce nombre dit pourquoi il s'est arrêté : 0 il a terminé, 1 l'application a échoué, 137 il a été tué, 143 on lui a demandé de s'arrêter.
Construire des images
- Dockerfile Un Dockerfile est la recette d'une image : une image de base, puis des instructions qui copient des fichiers, lancent des commandes et disent comment le conteneur démarre. docker build en fait une image, avec une couche pour chaque instruction qui modifie des fichiers.
- CMD et ENTRYPOINT CMD et ENTRYPOINT disent à un conteneur quoi lancer. ENTRYPOINT est le programme fixe, CMD les arguments par défaut, que docker run peut remplacer.
- Couches d'image Une image est une pile de couches en lecture seule, une par instruction qui modifie des fichiers. Les images construites sur la même base partagent ses couches, sur le disque comme au téléchargement.
- Cache de build docker build réutilise une couche quand son instruction et ses entrées n'ont pas changé depuis le dernier build. La première instruction qui change invalide le cache de toutes les instructions qui la suivent.
- Contexte de build Le contexte de build est le dossier donné à docker build, souvent le point final. Ses fichiers sont envoyés au builder, et ce sont les seuls que COPY peut lire.
- .dockerignore Un fichier .dockerignore à la racine du contexte de build liste les fichiers qui ne sont jamais envoyés au builder. Il garde les images légères et les secrets dehors.
- Builds multi-étapes Un Dockerfile multi-étapes contient plusieurs lignes FROM : les premières étapes compilent avec toute la chaîne d'outils, la dernière ne copie que le résultat. Compilateurs et sources n'arrivent jamais dans l'image finale.
- Images de base L'image de FROM détermine l'essentiel de la taille finale, les outils disponibles et les correctifs de sécurité dont tu hérites. Les variantes complète, slim, alpine et distroless échangent du confort contre de la taille.
- Binaires statiques Un binaire lié statiquement embarque toutes les bibliothèques dont il a besoin : il tourne donc sur scratch ou distroless sans rien d'autre dans l'image. Go en produit un avec CGO_ENABLED=0.
Données et disque
- Volumes Un volume est un stockage géré par Docker qui vit en dehors de tout conteneur. Monte-le là où une application écrit ses données, et ces données survivent à docker rm, aux mises à jour et aux conteneurs recréés.
- Montages de dossier (bind mounts) Un bind mount place un dossier du serveur à l'intérieur du conteneur. Un changement d'un côté est vu aussitôt de l'autre, ce qui est idéal en développement et risqué en production.
- Espace disque de Docker Images, conteneurs arrêtés, volumes et cache de build s'accumulent sur le disque du serveur jusqu'à ce que quelque chose manque de place. docker system df montre ce qui prend de la place et ce qui peut être récupéré.
- Images orphelines (dangling) Une image orpheline (dangling) est une ancienne image dont le tag a été déplacé vers un build plus récent. Elle apparaît en <none>:<none>, plus rien ne la référence, et on peut la supprimer sans risque.
- Rotation des journaux Docker garde tout ce qu'un conteneur affiche, par défaut dans un fichier json-file qui ne disparaît qu'avec le conteneur. La rotation le plafonne : max-size et max-file, par conteneur ou pour tout le démon.
- Mettre à jour un conteneur de base de données Une version mineure lit les mêmes fichiers de données : change le tag, garde le volume. Une version majeure ne le peut pas : exporte les données avec l'ancienne version et restaure-les dans la nouvelle.
Réseau
- Publication de ports Les ports d'un conteneur sont fermés au monde extérieur tant que tu ne les publies pas. -p 8080:80 redirige le port 8080 du serveur vers le port 80 du conteneur.
- Réseau bridge par défaut Les conteneurs lancés sans --network rejoignent le réseau bridge par défaut. Ils peuvent se joindre par adresse IP, mais pas par leur nom.
- Réseaux créés par l'utilisateur Un réseau créé avec docker network create offre un DNS à ses conteneurs : chacun est joignable par son nom. Il les sépare aussi des conteneurs des autres réseaux.
- EXPOSE ou publier EXPOSE dans un Dockerfile ne fait que documenter le port d'écoute de l'application ; il n'ouvre rien. C'est la publication avec -p ou ports: qui rend un port joignable depuis l'extérieur du serveur.
- Proxys et adresses des conteneurs Le DNS de Docker (127.0.0.11) renvoie l'adresse actuelle des conteneurs d'un service. Un proxy qui a résolu le nom une seule fois garde l'ancienne adresse après la recréation du conteneur.
Faire tourner des conteneurs
- Journaux de conteneur Tout ce que le processus principal écrit sur stdout et stderr devient le journal du conteneur. docker logs est la première chose à lire quand un conteneur se comporte mal.
- docker exec docker exec lance une commande supplémentaire dans un conteneur en marche, avec les mêmes fichiers et le même réseau. C'est ainsi qu'on regarde autour de soi, qu'on teste une connexion ou qu'on ouvre un shell.
- Variables d'environnement Les variables d'environnement configurent un conteneur au démarrage sans reconstruire son image : la même image tourne en préproduction et en production avec des valeurs différentes. -e, --env-file et environment: dans Compose les définissent.
- Politiques de redémarrage Une politique de redémarrage dit à Docker quoi faire quand un conteneur s'arrête : rien (no), le relancer après un plantage (on-failure), ou le garder en marche malgré les plantages et les redémarrages du serveur (unless-stopped, always).
- Healthchecks Un healthcheck est une commande que Docker exécute régulièrement dans le conteneur pour savoir si l'application fonctionne vraiment. L'état du conteneur devient starting, healthy ou unhealthy.
- Arrêt propre docker stop envoie SIGTERM au PID 1, attend le délai de grâce (10 secondes par défaut), puis envoie SIGKILL. Un arrêt propre demande que l'application reçoive le signal, et ait le temps de finir son travail.
Mémoire et CPU
- Limites de ressources Par défaut, un conteneur peut utiliser toute la mémoire et tout le CPU du serveur. --memory et --cpus le plafonnent, pour qu'un service qui s'emballe ne fasse pas tomber tout le serveur.
- OOM killer Quand un conteneur dépasse sa limite de mémoire, ou que le serveur manque de mémoire, l'OOM killer du noyau tue un processus pour libérer de la mémoire. Le conteneur s'arrête avec le code 137.
- Swap Le swap est de l'espace disque que le noyau utilise comme mémoire supplémentaire. Il retarde les arrêts par manque de mémoire, mais une mémoire sur disque est des centaines de fois plus lente : tous les services du serveur ralentissent.
Docker Compose
- Docker Compose Compose décrit toute une application, ses services, réseaux et volumes, dans un seul fichier compose.yaml. docker compose up crée et démarre l'ensemble, dans le bon ordre, en une commande.
- Réseaux Compose Compose crée un réseau pour chaque projet et y attache chaque service. Les services se joignent par leur nom de service, comme db ou cache, sur leurs propres ports.
- Fichiers de surcharge Compose Compose fusionne plusieurs fichiers : compose.yaml pour ce qui est commun, compose.override.yaml pour le développement, et un fichier de production donné avec -f. Chaque environnement ne change que ce qui diffère.
- Ordre de démarrage depends_on démarre la base avant l'API, mais démarré ne veut pas dire prêt : PostgreSQL a besoin de quelques secondes avant d'accepter des connexions. condition: service_healthy attend qu'elle les accepte vraiment.
- Parité dev/prod Le développement et la production doivent faire tourner la même image avec des réglages différents. Le code monté depuis le poste, les ports de débogage et le rechargement à chaud n'appartiennent qu'au développement.
Sécurité
- Garder les secrets hors des images Tout ce qui est écrit dans une image, par ENV, ARG, COPY ou un RUN, peut être lu par quiconque peut la télécharger, même si une couche suivante le supprime. Les secrets sont donnés au conteneur au moment où il démarre.
- Ne pas tourner en root Les conteneurs tournent en root sauf indication contraire. Avec USER dans le Dockerfile, une application compromise n'a que les droits d'un utilisateur ordinaire.
- Droits des fichiers dans les conteneurs Dans un conteneur, les droits Linux fonctionnent comme sur n'importe quel serveur : un processus ne peut écrire que là où son utilisateur y est autorisé. Les utilisateurs sont des nombres (UID), partagés entre le conteneur et le serveur.
- Secrets de build Certains builds ont besoin d'un identifiant : un jeton de registre privé, une clé SSH. Les secrets de BuildKit le donnent à un seul RUN, sans l'écrire dans aucune couche, aucun historique ni aucune clé de cache.
- Système de fichiers racine en lecture seule read_only: true monte l'image du conteneur en lecture seule : rien ne peut être ajouté ni modifié en dehors des volumes et des montages tmpfs, ni par l'application, ni par un attaquant qui en prend le contrôle.
- Capacités Linux Les pouvoirs de root sont découpés en capacités : CHOWN, SETUID, NET_RAW, etc. Docker en donne 14 à chaque conteneur par défaut ; la plupart des services n'en ont besoin d'aucune.
Livrer des images
- Registres d'images Un registre stocke des images pour que d'autres machines puissent les tirer. Le nom d'une image est son adresse : hôte du registre, projet, dépôt et tag, comme registry.shop.internal/shop/api:1.4.0.
- Construire une fois, déployer partout Une image est construite une seule fois, par la CI, et c'est cette même image qui va dans chaque environnement et sur chaque serveur. Les serveurs la tirent ; ils ne la construisent jamais.
- Digests d'images Un digest (sha256:...) est l'empreinte du manifeste d'une image : il désigne exactement une image, pour toujours. Un tag est une étiquette que toute personne autorisée à pousser peut déplacer vers une autre image.
Orchestration avec Swarm
- Le mode Swarm docker swarm init transforme des moteurs Docker en cluster : les managers gardent l'état souhaité et planifient le travail, les workers l'exécutent. Tu décris des services ; le cluster les maintient en marche sur les nœuds.
- Services et tâches Un service est un état souhaité (une image, un nombre de répliques, des contraintes) ; les managers le traduisent en tâches, un conteneur chacune, placées sur les nœuds qui peuvent les faire tourner, et remplacent toute tâche qui s'arrête.
- Réseaux overlay et routing mesh Les réseaux overlay s'étendent sur tous les nœuds du swarm. Chaque service reçoit une IP virtuelle qui répartit sur ses tâches saines, et un port publié répond sur chaque nœud et transfère vers une tâche, où qu'elle tourne.
- Mises à jour progressives docker service update remplace les tâches d'un service lot par lot, en suivant deploy.update_config : combien à la fois, dans quel ordre, combien de temps surveiller chacune. Bien réglée, une livraison passe inaperçue pour les clients.
- Retour arrière automatique update_config.failure_action décide de ce que fait Swarm quand une nouvelle tâche échoue pendant sa fenêtre de surveillance : pause (par défaut), continue, ou rollback vers la spécification précédente avec rollback_config.
- Les secrets Swarm docker secret create stocke une valeur chiffrée dans le journal Raft des managers ; un service qui la liste la reçoit comme un fichier dans /run/secrets, en mémoire, et nulle part ailleurs. docker service inspect n'affiche jamais sa valeur.
- Contraintes de placement deploy.placement.constraints limite les nœuds sur lesquels un service peut tourner : par rôle, nom d'hôte, ou étiquettes que tu poses sur les nœuds. C'est ainsi qu'un service avec état reste là où sont ses données.
- Managers et quorum Les managers se mettent d'accord par Raft : chaque changement demande une majorité de l'ensemble des managers. 3 managers tolèrent 1 panne, 5 en tolèrent 2 ; 2 managers n'en tolèrent aucune.
Concevoir une plateforme
- Sécurité de la chaîne d'approvisionnement Chaque image que tu fais tourner est construite sur des images et des paquets écrits par d'autres. La sécurité de la chaîne d'approvisionnement consiste à s'assurer que ce que tu construis et déploies est exactement ce que tu as relu, même si un registre, un miroir ou un compte est compromis.
- Images multi-plateformes Une image est construite pour une architecture de processeur. Un index d'images en regroupe plusieurs, une par plateforme (linux/amd64, linux/arm64), sous un seul tag : chaque machine tire celle qu'elle peut exécuter.
- Planification de capacité Planifier la capacité, c'est s'assurer que le cluster peut tenir ce dont les services ont besoin, y compris après la perte d'un nœud (N+1), et dire à l'ordonnanceur ce dont chaque tâche a besoin pour qu'il les place en conséquence.
- Exercices de reprise après sinistre Un plan de reprise dit comment tout reconstruire ailleurs à partir de ce qui survit : le dépôt, le registre, le coffre et les sauvegardes. Le RTO est le temps que ça prend, le RPO la quantité de données qu'on peut perdre. Seul un exercice prouve les deux.
Aucun concept ne correspond à ces filtres.