Comprendre Docker et ses conteneurs en profondeur
Docker en bref
Docker est un outil open-source écrit en langage Go et créé en 2012 par trois ingénieurs français. Il résulte de développements internes de l’entreprise dotCloud co-fondés par deux français d’Epitech.
Il s’agit aujourd’hui d’une entreprise américaine qui a été scindée en deux : une partie open-source et une partie entreprise qui a été revendue à Mirantis.
Nous allons dans ce post nous intéresser à Docker Community Edition, la partie entreprise détenue par Mirantis étant réservée aux très grosses entreprises.
Une bonne première définition de Docker est un outil qui peut empaqueter une application et ses dépendances dans un conteneur isolé, qui pourra être exécuté sur n'importe quel environnement.
Appréhender Docker n’est pas une chose aisée. Aussi il est normal d’être perdu au début et il faut avoir bien avancé pour comprendre l’utilité et la puissance de cet outil !
Les principaux avantages à utiliser Docker
Nous allons voir les principaux avantages de Docker.
1 - Utilisation plus efficiente des ressources système
Docker permet une utilisation plus efficiente des ressources d’un système tout en permettant les mêmes avantages qu’une machine virtuelle : c’est-à-dire principalement l’isolation et la reproductibilité.
Les applications conteneurisées utilisent beaucoup moins de ressources que des machines virtuelles car elles utilisent le même noyau Linux.
Elles démarrent et s'arrêtent en quelques millisecondes, alors qu’il faut quelques secondes ou minutes pour une machine virtuelle.
2 - Reproductibilité
Un conteneur Docker est garanti d’être identique quel que soit le système.
Il garantit que la bonne version de chaque dépendance soit installée. Ainsi, chaque membre d’une équipe est certain d’avoir le ou les applications qui fonctionnent identiquement sur des environnements différents (pas les mêmes OS, pas les même configurations locales, pas les mêmes dépendances globales etc).
Avec Docker, plus jamais vous entendrez le : “Moi, cela fonctionne sur ma machine”.
Cela permet aux développeurs de se soucier uniquement du code et pas de l’environnement où celui-ci sera finalement exécuté.
3 - Isolation
Les dépendances ou les configurations d’un conteneur n’affecteront aucune dépendance ou configuration des autres conteneurs et de la machine hôte.
Vous pouvez ainsi avoir 5 versions de Node.js ou de MongoDB différentes localement sans aucune peine ! C’est un énorme plus lorsque l’on travaille sur plusieurs projets avec des versions de dépendances qui ne sont pas les mêmes. En effet, elles présentent souvent des incompatibilités ou des breaking changes se qui se révèlent être un véritable casse-tête !
4 - Mises à jour et tests
Pour ceux qui ont à gérer plusieurs serveurs, vous savez combien il est difficile de maintenir et de mettre à jour correctement des environnements complexes.
Il faut bien sûr mettre à jour l’OS sur chaque serveur, puis les environnements serveurs et les bases de données : le tout dans le bon ordre et sans oublier aucun serveur. Les risques sont élevés (downtimes, pertes de données etc).
Avec Docker, la mise à jour d’un environnement dans le cloud, même sur de nombreux serveurs est très simple. Vous gagnerez un temps précieux.
Même chose si vous devez gérer plusieurs environnements : par exemple le classique development / tests, staging et production. Il faut être sûr que tous les environnements aient les mêmes dépendances, les mêmes versions d’OS etc pour que les tests soient fiables. Avec Docker toute cette gestion d’environnements est grandement simplifiée !
5 - Bonnes pratiques grâce aux images open-source
Docker est si populaire que tous les mainteneurs de librairies ou de logiciels divers (environnements serveurs, serveurs Web, bases de données etc) maintiennent des images Docker.
Ces images Docker officielles, disponibles sur une plateforme que nous étudierons, Docker Hub, permettent de lancer des conteneurs fiables et sécurisés très rapidement et sans avoir à les configurer soit même.
Les grands principes des conteneurs Docker
Docker est une plateforme pour les développeurs et les devops qui permet de build, de lancer et de partager des applications en utilisant des conteneurs.
Les conteneurs permettent d’isoler les applications pour assurer une parfaite réplicabilité des environnement.
Autrement dit, ils permettent de s’assurer que vous exécuter une ou plusieurs applications sur le même environnement quel que soit la machine physique et l’OS utilisé.
Les principes des conteneurs Docker sont les suivants :
- ils sont flexibles : n’importe quelle application, même les plus complexes, peuvent être conteneurisées (on dit aussi “dockerisées”).
- ils sont légers : le fait qu’ils partagent les ressources et le noyau Linux du système d’exploitation de la machine hôte font qu’ils sont beaucoup plus légers que des machines virtuelles.
- ils sont portables : ils sont utilisables sur n’importe quel environnement (tout OS, aussi bien pour le développement local que sur des centaines de serveurs dans le cloud).
- ils ont un couplage faible : cela signifie que les conteneurs sont autonomes car ils sont isolés. Il est facile de les supprimer, les ajouter, les modifier (mise à jour par exemple) sans perturber les autres conteneurs tournant sur une machine.
- ils sont scalables : des outils permettent de déployer des centaines voir des milliers de conteneurs en quelques minutes voir secondes sur un très grande nombre de serveurs. Il est même possible d’automatiser le déploiement de nouveaux conteneurs (auto-scaling).
- ils sont sécurisés : les contraintes et l’isolation par défaut des conteneurs apporte une grande sécurité des environnements déployés.
Approfondissements
Nous allons maintenant approfondir certains concepts pour que vous ayez une meilleure compréhension de Docker.
Qu’est-ce qu’une machine virtuelle ?
Une machine virtuelle (Virtual Machine ou VM) est une machine créée par un logiciel et qui n’a pas donc d’existence physique.
Elle permet de simuler des ressources matérielles -disque dur, RAM, CPU etc- et logicielles afin qu’un ou plusieurs autres systèmes d’exploitation puissent être utilisé(s) sur la même machine physique.
L’avantage d’une machine virtuelle est la possibilité de lancer plusieurs environnements isolés, avec des systèmes d’exploitation pouvant être différents, en même temps, sur la même machine.
C'est particulièrement utile pour des serveurs, souvent très puissants, qui peuvent ainsi lancer des dizaines voir des centaines d’environnements isolés sur les mêmes machines physiques -c’est l’une des technologie du Cloud-.
C’est aussi utile pour pouvoir utiliser différents systèmes d’exploitation sans avoir à redémarrer : c’est parfait pour le développement par exemple. Vous pouvez avoir une machine virtuelle avec tous les logiciels que vous souhaitez sur GNU/Linux et la lancer depuis Windows en deux clics par exemple.
L’inconvénient est bien sûr une diminution des performances, car il faut diviser l’allocation des ressources physiques entre machine hôte et machine virtuelle, et il y a à exécuter la couche système hôte, l’hyperviseur et la couche système invité comme nous allons le voir.
Différences avec Docker
Il faut bien comprendre que Docker utilise le noyau Linux et l’environnement GNU de votre système.
Si vous êtes sur n’importe quelle distribution de GNU/Linux, il pourra utiliser directement cet environnement.
Si vous êtes sur MacOS ou Windows, il utilisera l’hyperviseur installé sur le système (de type 2) et lancera une machine virtuelle GNU/Linux qu’il utilisera.
Dans tous les cas, Docker utilise un seul système d’exploitation GNU/Linux.
Sur ce système, il va lancer un conteneur qui est un processus -c’est-à-dire un programme en cours d’exécution-, avec des fonctionnalités d’encapsulation lui permettant d’être isolé de l’hôte et des autres conteneurs.
Chaque conteneur a son propre système de fichiers isolé qui est fourni par une image Docker. Ce système de fichiers contient le code, les binaires, les fichier exécutables et toutes les dépendances requises pour faire fonctionner une application.
Nous reviendrons sur tous ces aspects en détails, mais cela permet d’avoir une idée du fonctionnement par rapport à une machine virtuelle.
Autrement dit, Docker partage le système d’exploitation entre tous les conteneurs qu’il lance, qui sont des simples processus légers. Alors qu’une machine virtuelle est un système d’exploitation entier nécessitant plusieurs gigaoctets de mémoire vive, beaucoup d’espace disque et une utilisation importante du CPU.
Technologies utilisées par Docker
Docker est un environnement beaucoup plus facile d’accès et déjà paramétré de technologies existantes. En fait, il s’agit d’une surcouche et d’une API permettant de contrôler des fonctionnalités bas niveau du noyau Linux.
Docker est écrit en langage Go et utilise toutes les fonctionnalités Linux que nous allons voir.
Les namespaces
Docker utilise les namespaces Linux qui permettent de créer des espaces isolés sur un système d’exploitation Linux.
C’est cette fonctionnalité de Linux qui confère aux conteneurs leur isolation. Chaque aspect d’un conteneur est exécuté dans un namespace qui lui est propre et auquel seul ce dernier peut accéder.
Docker utilise ainsi des namespaces pour :
- l’isolation des processus du conteneur (PID namespace)
- l’isolation des interfaces réseaux du conteneur (NET namespace)
- l’isolation des ressources de communications inter-processus (IPC namespace)
- l’isolation du système de fichiers (MNnamespace)
- l’isolation des identifiants du noyau et des versions (UTS namespace) qui permet notamment d’avoir un hostname différent pour les conteneurs.
Les Cgroups
Docker utilise également les control groups (cgroups).
Cette technologie Linux permet de limiter l’accès aux ressources à des processus. Grâce à cela, Docker partage les ressources système disponibles entre tous les conteneurs (RAM, CPU, accès réseaux et lecture / écriture disques etc).
Il est également possible, grâce à cette technologie, de limiter les ressources allouées à chaque conteneur comme nous le verrons plus tard.
UnionFS
UnionFS -Union File System- est un type de système de fichiers pour Linux.
Suivant la distribution Linux, Docker utilise l’une des implémentations disponibles : AUFS, btrfs, vfs ou DeviceMapper.
Ces systèmes de fichiers fonctionnent en créant des couches -layers-. Ils sont au cœur des images Docker et leur permettent d’être légères et très rapides.
L'écosystème Docker
Nous allons maintenant voir un premier aperçu des éléments utilisés par Docker.
Le Docker Engine
Le Docker Engine ou moteur Docker est une application sur un modèle client / serveur.
Il est composé de trois parties principales.
1 - Le serveur dockerd
Le serveur dockerd qui est un service, également appelé démon, qui est exécuté en permanence. C’est lui qui va créer et gérer tous les objets Docker : par exemples les images, les conteneurs, les réseaux et les volumes.
2 - L’API REST
L’API REST spécifie les interfaces que les programmes peuvent utiliser pour communiquer avec le service dockerd.
3 - Le client (CLI)
Le client un CLI pour - command line interface - permet d’exécuter des commandes Docker. Le CLI traduit les commandes entrées par l’utilisateur en requêtes pour l’API REST.
Le schéma officiel suivant montre les relations entre le client, le démon et la bibliothèque d’images - Docker Hub ou un registry privé - :
Premier aperçu des objets Docker
Les images
Une image Docker est un schéma en lecture seule qui contient les instructions pour créer un conteneur Docker.
Le plus souvent, une image est elle-même basée sur une autre image avec des configurations spécifiques.
Par exemple, il est possible de prendre une image Node.js et d’ajouter les fichiers nécessaires au fonctionnement de votre application.
Pour créer une image, il faut utiliser un fichier spécifique appelé Dockerfile qui a une syntaxe particulière permettant de définir les étapes nécessaires à la création de l’image.
Chaque instruction dans un Dockerfile permet de créer une couche dans l’image.
Lorsque vous modifiez le Dockerfile et que vous rebuildez l’image, seules les couches modifiées sont rebuild. C’est l’une des raisons pourquoi Docker est beaucoup plus rapide que les machines virtuelles.
Les conteneurs
Un conteneur est une instance d’une image en cours d’exécution qui peut prendre des options de configuration passées lors du lancement.
Vous pouvez donc avoir de nombreux conteneurs à partir de la même image.
C’est la même logique avec un programme que vous pouvez lancer plusieurs fois en même temps créant ainsi plusieurs processus.
Pour créer, démarrer, arrêter, déplacer ou supprimer un conteneur il faut utiliser le CLI dont nous avons parlé.
Nous verrons qu’il est possible de connecter un conteneur à un ou plusieurs réseaux, qu’il est également possible de lui attacher un ou plusieurs volumes de stockage.
Comme nous l’avons vu, par défaut, un conteneur est isolé des autres conteneurs et de la machine hôte. Il est bien sûr possible de paramétrer cette isolation en modifiant les réseaux auxquels le conteneur est connecté, le stockage etc.
Les volumes
Les volumes permettent aux conteneurs de stocker des données.
Ils sont initialisés lors de la création d’un conteneur.
Ils permettent de persister et de partager des données d’un conteneur.
Ces volumes sont stockés en dehors du système UnionFS que nous avons vu. Ils permettent en effet de conserver des données même si un conteneur est supprimé, mis à jour ou rechargé.
Scaler avec Docker
Pour l’instant nous avons vu les objets permettant de lancer une ou plusieurs instances d’une image sous la forme de conteneurs.
Mais au fur et à mesure que votre application grandit, vous voudrez lancer plusieurs conteneurs sur le même hôte ou plusieurs conteneurs sur plusieurs hôtes.
Des outils extrêmement puissants permettent de gérer pour vous la problématique du scaling sur plusieurs hôtes.
Exécuter une application multi-conteneurs avec Docker compose
Docker Compose est l’outil Docker permettant de définir et de lancer des applications multi-conteneurs.
Il suffit d'utiliser un fichier de configuration spécifique pour définir les services de votre application.
Avec Docker Compose vous pourrez ainsi lancer tous les services de votre application en une commande.
Par exemple, un service pour votre base de données, un service pour l’authentification et un troisième pour votre application serveur.
Utiliser un orchestrateur pour du multi-hôte avec Docker swarm ou Kubernetes
Docker Swarm est un orchestrateur qui permet de gérer facilement plusieurs conteneurs, souvent nombreux, sur de multiples serveurs.
Nous verrons que ce sont les mêmes fichiers de configuration que Docker Compose mais pas les mêmes commandes.
Kubernetes est également un orchestrateur mais développé par Google et non par Docker.
C’est un outil extrêmement puissant permettant de faire énormément de choses (auto-scaling notamment). Il permet de faire beaucoup plus de choses que Docker Swarm mais est bien plus long à maîtriser. Vous n’en aurez besoin que dans des cas spécifiques (énormes charges -applications vidéos très connues par exemple-, machine-learning etc).