Concepts de base
Introduction
Docker est une plateforme de conteneurisation qui permet de déployer, gérer et exécuter des applications dans des environnements isolés appelés containers. Cette formation explore les concepts fondamentaux de Docker, en mettant l'accent sur les containers, les cgroups, les namespaces, la comparaison avec les machines virtuelles et l'architecture microservices.
Qu'est-ce qu'un container ?
Un container est une unité logicielle standardisée qui regroupe le code d'une application avec toutes ses dépendances nécessaires pour exécuter cette application. Il permet ainsi de garantir que l'application s'exécute de manière cohérente, peu importe l'environnement dans lequel elle est déployée.
- Isolation : Chaque container s'exécute dans un environnement isolé, minimisant les conflits entre applications et simplifiant la gestion des dépendances.
- Portabilité : Les containers encapsulent toutes les dépendances nécessaires, permettant leur exécution sur n'importe quel système supportant Docker.
- Efficacité : Les containers partagent le même noyau d'exploitation, ce qui les rend plus légers et plus efficaces en termes de ressources par rapport aux machines virtuelles.
Machines virtuelles vs Containers
La virtualisation et la containerisation sont deux technologies permettant de créer des environnements isolés pour exécuter des applications. Elles diffèrent dans leur utilisation des ressources système.
Virtualisation
La virtualisation consiste à exécuter plusieurs systèmes d'exploitation complets sur une seule machine physique à l'aide d'un hyperviseur. Chaque instance virtuelle est appelée machine virtuelle (VM).
Principes
- Hyperviseur : Un logiciel (comme VMware, VirtualBox, ou Hyper-V) permettant de créer et gérer les machines virtuelles. Il se situe entre le matériel physique et les systèmes d'exploitation virtuels.
- Système d'exploitation complet : Chaque VM contient un système d'exploitation complet (ex. : Windows, Linux), ce qui augmente la consommation de ressources.
- Isolation : Chaque VM est complètement isolée des autres, y compris son système d'exploitation.
Avantages
- Isolation forte : Chaque machine possède son propre système d’exploitation, assurant une isolation complète.
- Compatibilité : Permet d’exécuter différents systèmes d’exploitation sur la même machine (par exemple, une VM Linux sur un hôte Windows).
Inconvénients
- Consommation de ressources : Chaque VM exécute un OS complet, ce qui consomme beaucoup de mémoire et de CPU, rendant les VMs plus lourdes et plus lentes à démarrer.
Containerisation (avec Docker)
La containerisation, avec des outils comme Docker, isole les applications dans des conteneurs légers. Contrairement aux VM, les conteneurs partagent le noyau du système d'exploitation de la machine hôte.
Principes
- Sans hyperviseur : Docker n’utilise pas d’hyperviseur, mais repose sur les fonctionnalités du noyau du système d’exploitation (comme les espaces de noms et les cgroups sur Linux) pour isoler les conteneurs.
- Partage du noyau OS : Tous les conteneurs partagent le noyau du système d’exploitation hôte, permettant d’exécuter plusieurs conteneurs sur une seule instance d’OS.
- Image légère : Les conteneurs n'incluent que l'application et ses dépendances, sans le système d’exploitation complet.
Avantages
- Léger et rapide : Les conteneurs sont plus légers et démarrent plus vite que les VM.
- Portabilité : Les conteneurs Docker sont très portables, facilitant le déploiement dans différents environnements (développement, test, production).
- Gestion des dépendances : Docker inclut tout ce dont une application a besoin (dépendances, bibliothèques, etc.), assurant une cohérence entre les environnements.
Inconvénients
- Partage du noyau : Les conteneurs partagent le noyau du système d’exploitation hôte, limitant la capacité à exécuter des OS différents sans une couche d’abstraction (ex. : WSL pour exécuter Linux sur Windows).
- Isolation moindre : L’isolation des conteneurs est moins forte que celle des VMs, car ils partagent le même noyau.
Résumé comparatif
| Aspect | Virtualisation (VM) | Containerisation (Docker) |
|---|---|---|
| Isolement | Système d'exploitation complet | Partage le noyau de l’hôte |
| Taille | Lourde (chaque VM a son OS) | Légère (pas d'OS dans chaque conteneur) |
| Performance | Plus lente, consomme plus de ressources | Rapide et léger |
| Portabilité | Moins portable (dépend de l’hyperviseur) | Très portable |
| Démarrage | Lent | Démarrage quasi instantané |
| Utilisation | Environnements très isolés, multi-OS | Applications isolées, souvent mono-OS |
En conclusion, la virtualisation est idéale pour exécuter plusieurs systèmes d’exploitation avec une forte isolation, mais elle est gourmande en ressources. La containerisation, par contre, est plus légère, rapide et adaptée pour le déploiement portable d’applications avec une isolation suffisante pour la majorité des cas d’usage.
Les Control Groups
Docker repose sur l'utilisation des Control Groups (cgroups) qui sont une fonctionnalité du noyau Linux qui permet de regrouper et de gérer les ressources (comme le CPU, la mémoire, la bande passante réseau, etc.) utilisées par un ensemble de processus. Ils offrent un moyen d'organiser et de contrôler les ressources système, en allouant des limites, des priorités et des politiques d'isolation pour améliorer la gestion des performances et la sécurité dans un environnement multi-utilisateur ou multi-processus, comme les serveurs, les conteneurs (Docker, Kubernetes), etc.
Limiter l'utilisation des ressources
Docker permet de limiter et comptabiliser l'utilisation des ressources (CPU, mémoire, etc.) pour chaque conteneur à l'aide des options comme --cpus, --memoryet --memory-swap.
- CPU
# Lancer un conteneur en limitant l'utilisation CPU à 1,5 coeur
docker run -d --name limited_cpu_container --cpus="1.5" nginxL'option --cpus="1.5" limite ce conteneur à 1,5 unité de CPU, ce qui correspond à un cœur et demi du cpu.
- Memory
# Lancer un conteneur avec une limite mémoire de 256Mb
docker run -d --name limited_memory_container --memory="256m" nginxL'option --memory="256m" limite la mémoire disponible pour le conteneur à 256 Mb. Si le conteneur essaie d'utiliser plus, il sera soit limité soit tué par le système.
- I/O
Pour limiter l'accès aux disques ou aux périphériques de stockage, Docker utilise les options --device-read-bps, --device-write-bps, et --blkio-weight.
# Lancer un conteneur avec des limites sur les I/O et un poids de priorité pour le bloc d'I/O
docker run -d --name limited_io_container \
--device-read-bps /dev/sda:10mb \
--device-write-bps /dev/sda:5mb \
--blkio-weight=500 \
nginx--device-read-bps /dev/sda:10mb limite la vitesse de lecture sur le périphérique /dev/sda à 10 Mb/s.
--device-write-bps /dev/sda:5mb limite la vitesse d'écriture sur le périphérique /dev/sda à 5 Mb/s.
--blkio-weight=500 définit une priorité de 500 pour les opérations d'I/O bloc. Cette valeur se situe au milieu de la plage possible (10 à 1000). Un conteneur avec un poids plus élevé aura une priorité plus importante pour les accès I/O par rapport à ceux avec un poids plus faible lorsque plusieurs conteneurs se partagent les ressources I/O.
- Network
Pour limiter l'utilisation du réseau d'un conteneur, vous pouvez utiliser tc (traffic control) ou gérer la bande passante via des plugins spécifiques. Docker ne fournit pas directement une option de limitation de bande passante, mais cela peut être réalisé via un contrôle réseau ou des outils comme netem.
Voici un exemple avec Docker en utilisant --network pour simuler un environnement réseau lent ou avec une latence élevée.
# Lancer un conteneur avec une latence simulée sur le réseau
docker run -d --name limited_network_container \
--network bridge \
nginx
# Ajouter une latence réseau via `tc` (traffic control) sur le conteneur
docker exec -it limited_network_container \
tc qdisc add dev eth0 root netem delay 100msLa commande tc qdisc add dev eth0 root netem delay 100ms ajoute une latence de 100ms sur l'interface réseau eth0 du conteneur, simulant un réseau plus lent. Cette méthode est utile pour tester des applications dans des environnements avec des connexions lentes ou instables.
Il est à noté que tc n'est pas installé par défaut sur toutes les distributions Linux. Il faudra donc, suivant l'image de base utilisée, installer iproute2.
Prioriser les processus
Pour contrôler la priorité des processus dans un conteneur Docker, vous pouvez utiliser l'option --cpu-shares pour allouer une part de CPU. Cela n'affecte pas directement la vitesse d'exécution des processus, mais aide à prioriser l'utilisation des ressources CPU si plusieurs conteneurs en demandent.
# Exemple pour lancer un conteneur en assignant des priorités CPU
docker run -d --name low_priority_container --cpu-shares 512 nginx
docker run -d --name high_priority_container --cpu-shares 1024 nginxLe conteneur low_priority_container a une priorité CPU plus faible (512), tandis que high_priority_container a une priorité plus élevée (1024). Si les deux conteneurs exécutent des processus en même temps, celui ayant 1024 utilisera deux fois plus de temps CPU que celui ayant 512.
Comptabiliser les ressources
Vous pouvez inspecter l'utilisation des ressources d'un conteneur à tout moment avec la commande docker stats.
# Afficher les statistiques d'utilisation des ressources d'un conteneur
docker stats limited_cpu_containerLes namespaces
Les namespaces sont des fonctionnalités du noyau Linux qui isolent différentes ressources système entre les processus. Docker utilise les namespaces pour isoler les containers les uns des autres et du système hôte. Cela garantit que chaque container dispose de son propre environnement isolé.
- PID
Ce namespace isole les identifiants de processus (Process IDs). Cela permet aux processus à l'intérieur d'un container d'avoir des PID commençant à 1, indépendamment des PID sur le système hôte.
Pour illustrer, l'exemple suivant permet de :
Lancer un container avec son propre PID namespace
docker run -d --name pid_demo alpine sleep infinityLe container nommé pid_demo est lancé en arrière plan à partir d'une image alpine et exécute la commande sleep infinity
Depuis la machine hôte, vérifiez l'isolation des PID :
docker top pid_demoLes processus du container sont affichés avec des PID correspondant à la vue du système hôte.
Affichez les processus lancés par le container
docker exec -it pid_demo topOn constate que les PID sont numérotés à partir de 1 ; indépendamment des PID du système hôte.
- Network
Le Network Namespace isole la pile réseau, y compris les interfaces réseau, les adresses IP, les ports et les tables de routage.
Pour illustrer, voici quelques exemples :
Lancer deux containers sur le réseau bridge par défaut
docker run -d --name net_demo1 alpine sleep infinity
docker run -d --name net_demo2 alpine sleep infinityAfin de vérifier l'isolation réseau, vérifiez l'ip du premier container :
docker inspect -f '{{ .NetworkSettings.IPAddress }}' net_demo1Depuis le second, essayez de pinger le premier
docker exec -it net_demo2 ping -c 4 IP_de_net_demo1Les deux containers peuvent communiquer car ils sont sur le même réseau bridge.
Isoler le Réseau en Utilisant des Réseaux Personnalisés
Créez un réseau
docker network create isolated_netLancez un container sur ce réseau
docker run -d --name net_demo3 --network=isolated_net alpine sleep infinityEssayez de pinger depuis le container le premier container
docker exec -it net_demo3 ping -c 4 IP_de_net_demo1Le container est donc isolé sur son propre network qui eux sont sur le network bridge par défaut
- Mount
Le Mount Namespace isole les points de montage et le système de fichiers. Chaque container a sa propre vue du système de fichiers.
Voici un exemple d'utilisation permettant de :
Lancer un container avec un volume monté
docker run -d --name mount_demo -v $PWD:/data alpine sleep infinityLe répertoire courant $PWD est monté sur le répertoire /data du container.
Vérifiez l'isolation du système de fichier en listant le contenu du répertoire /data
docker exec -it mount_demo ls /dataOn constate que le container n'a de vision que sur le contenu du répertoire courant mais le reste du système de fichiers de la machine hôte est isolé.
- UTS
Le UTS Namespace (UNIX Timesharing System) permet à un container d'avoir son propre nom d'hôte et nom de domaine.
L'exemple suivant illustration l'utilisation d'un nom d'hôte personnalisé
docker run -it --name uts_demo --hostname container_host alpine ashOn constate que le nom d'hôte est bien celui spécifié
hostnameL'exemple qui suit montre le partage de l'UTS avec le système hôte
docker run it --name uts_host_demo --uts=host alpine ashL'hostname utilisé est alors celui de la machine hôte
hostname- IPC
Le namespace IPC isole les ressources de communication inter-processus, telles que les sémaphores, les queues, et la mémoire partagée.
On peut par exemple lancer un container avec son propre IPC
docker run -d --name ipc_demo alpine sleep infinityVérifiez alors l'isolation IPC en créant une mémoire partagée sur le container
docker exec -it ipc_demo sh -c 'echo "hello" > /dev/shm/testfile'Sur le système hôte, le fichier n'existe pas, confirmant l'isolation
ls /dev/shm/testfile- User
Le User Namespace permet de mapper les ID utilisateur et groupe du container à des ID différents sur le système hôte, améliorant ainsi la sécurité. Modifiez le daemon Docker pour activer le remapping des utilisateurs en ajoutant les lignes suivantes dans /etc/docker/daemon.json :
{
"userns-remap": "default"
}Redémarrez le service Docker
sudo systemctl restart dockerLancez alors un containert avec le namespace user activé
docker run -it --name userns_demo alpine ashVérifiez l'id de l'utilisateur à l'intérieur du container
iduid=0(root) s'affiche, indiquant que vous êtes root dans le container. Vérifiez alors sur la machine hôte les processus du container
ps aux | grep userns_demoLes processus s'exécutent sous un UID différent, généralement non privilégié, améliorant ainsi la sécurité