Docker : la technologie de conteneur révolutionnaire

Docker constitue une technologie dédiée à la virtualisation basées sur des conteneurs d’applications logiciel. L’approche grand public basée sur les conteneurs de Docker a changé le visage du développement d’applications ces dernières années. Elle a affecté chacune des différentes zones du développement, y compris la manière dont les applications et les composants sont développés et dont les services logiciel sont distribués, et a déplacé ces derniers du développement à la production. Avec Docker, tous ces processus sont exécutés d’une manière différente de celle dont ils l’étaient auparavant.

Mais les processus de développement ne sont pas les seuls à avoir changé : c’est aussi le cas de l’architecture logicielle. Elle a glissé de solutions générales monolithiques vers des clusters de « microservices » associés légers. Ceci a, en retour, rendu dans leur ensemble les systèmes en résultant plus complexes. Ces dernières années, des logiciels tels que Kubernetes se sont imposés comme des leaders dans la gestion d’applications multi-conteneurs.

Le développement de la virtualisation basée sur les conteneurs est loin d’être achevé, ce dernier restant dès lors un champ d’expérimentation intéressant. Dans cet article, nous vous expliquons comment Docker fonctionne en tant que technologie sous-jacente. Par ailleurs, nous examinerons les raisons qui ont conduit au développement de Docker.

Note

Le nom « Docker » recouvre plusieurs sens. Il est utilisé en tant que synonyme du logiciel lui-même, pour désigner le projet open source sur lequel il est basé, et une compagnie américaine qui gère divers produits et services à titre commercial.

Docker : historique rapide

Le logiciel publié à l’origine sous le nom « Docker » fut développé sur la base de la technologie Linux Container (LXC). LXC fut ensuite remplacée par le libcontainer de Docker. De nouveaux composants logiciel ont été ajoutés alors que Docker a continué à croître et à devenir le standard pour la virtualisation basée sur les conteneurs. ContainerD a notamment émergé du développement de Docker en tant que moteur d’exécution de conteneur avec l’implémentation standard runC. Aujourd’hui, ces projets sont gérés par la Cloud Native Computing Foundation (CNCF) et l’Open Container Initiative (OCI).

Outre l’équipe consacrée à Docker, des entreprises leader dans le monde de la tech telles que Cisco, Google, Huawei, IBM, Microsoft, ou encore Red Hat sont impliquées dans le développement de Docker et de technologies connexes. Parmi les évolutions les plus récentes, on notera le fait que Windows est désormais également utilisé en tant qu’environnement natif pour les conteneurs Docker, en plus du noyau Linux. Ci-dessous, voici quelques-uns des jalons majeurs dans l’historique de l’évolution de Docker :

Années Étapes-clés dans l’évolution de Docker
2007 La technologie cgroup est intégrée au noyau Linux
2008 Sortie de LXC ; il s’appuie sur les espaces de nom de cgroups et Linux, comme Docker le fera par la suite
2013 Sortie de Docker comme logiciel open source
2014 Docker est disponible sur Amazon EC2
2015 Sortie de Kubernetes
2016 Docker est disponible sous Windows 10 via Hyper-V
2019 Docker est disponible sous Windows Home via WSL2
Conseil

À la fin de cet article, nous entrerons plus en détails sur ce qui a motivé le développement de Docker et de technologies de virtualisation similaires.

Qu’est-ce que Docker ?

La fonctionnalité centrale de Docker est la virtualisation d’applications basée sur les conteneurs. Cela contraste avec la virtualisation via des machines virtuelles (VM). Avec Docker, le code de l’application, qui comprend toutes les dépendances, est empaqueté à l’intérieur d’une « image ». Le logiciel Docker exécute l’application empaquetée dans un conteneur Docker. Les images peuvent être déplacées d’un système à l’autre et être exécutées sur tout système compatible avec Docker.

Citation

"Containers are a standardized unit of software that allows developers to isolate their app from its environment […]“ – Citation d’un développeur de Docker source: https://www.Docker.com/why-Docker

« Les conteneurs constituent des unités standardisées de software qui permettent à des développeurs d’isoler leur appli de son environnement […] » – (Traduction : IONOS)

Comme c’est le cas avec le déploiement d’une machine virtuelle (VM), une des premières préoccupations des conteneurs Docker est d’isoler l’application en cours d’exécution. À la différence des VM, néanmoins, un système d’exploitation entier n’est pas virtualisé. À la place, Docker alloue certaines ressources issues du système d’exploitation et du matériel à chaque conteneur : on peut créer et diriger en parallèle autant de conteneurs qu’on veut à partir d’une image Docker. C’est ainsi que les services Cloud extensibles sont mis en place.

Bien que nous parlions de Docker comme d’une simple partie d’un logiciel, il s’agit en réalité de multiples composants de logiciels qui communiquent via l’API Docker Engine. Par ailleurs, une poignée d’objets Docker spéciaux sont utilisés tels que les images susmentionnées et les conteneurs. Des workflows spécifiques à Docker sont constitués des composants logiciels et des objets Docker. Jetons un œil à la manière dont ils interagissent en détails.

Logiciel Docker

La base du logiciel Docker est le Docker-Engine. Ce dernier est principalement utilisé pour gérer et contrôler les conteneurs et leurs images sous-jacentes. Des outils spécifiques sont utilisés pour d’autres fonctionnalités. Ceux-ci sont principalement requis pour gérer des applications constituées de groupes de conteneurs.

Docker Engine

Docker Engine est exécuté sur un système local ou un serveur et est constitué de deux composants :

  1. Le Docker Daemon (Dockerd) : ce dernier est constamment exécuté en arrière-plan et à l’affût de requêtes de l’API Docker Engine. Dockerd répond aux commandes ad hoc pour gérer les conteneurs Docker et d’autres objets Docker.
  2. Le Docker Client (Docker) : il s’agit d’un programme de ligne de commande. Le Docker Client est utilisé pour contrôler le Docker Engine et fournit des commandes pour créer et développer des conteneurs Docker, ainsi que pour créer, obtenir et versionner des images Docker.

L’API Docker Engine

L’API Docker Engine est une API REST. Elle communique avec le Docker Daemon. Des « kits de développement logiciel » (SKD pour Software Development Kit) officiels pour Go et Python sont disponibles pour intégrer l’API Docker Engine dans des projets de logiciels. Des bibliothèques similaires existent également pour plus d’une douzaine d’autres langages de programmation. Vous pouvez accéder à l’API avec la ligne de commande à l’aide de la commande Docker. Par ailleurs, vous pouvez accéder à l’API directement à l’aide de cURL ou d’outils similaires.

Outils Docker

Lorsque vous avez recours à des machines virtuelle, vous utilisez souvent des systèmes qui consistent en plusieurs composants logiciels. Par contraste, la virtualisation en conteneurs à l’aide de Docker favorise les clusters de microservices librement associés. Ces derniers sont adaptés pour les solutions Cloud distribuées qui offrent un haut degré de modularité et une grande disponibilité. Quoi qu’il en soit, ces types de systèmes deviennent rapidement très complexes. Pour gérer des applications conteneurisées de manière efficace, vous avez recours à des outils logiciels spécifiques appelés « orchestrateurs ».

Docker Swarm et Docker Compose sont deux outils Docker officiels qui sont disponibles pour orchestrer les clusters de conteneurs. La commande « Docker swarm » peut être utilisée pour combiner plusieurs Docker Engines en un seul engin virtuel. On peut ensuite faire fonctionner les engins individuels à travers de multiples systèmes et infrastructures. La commande « Docker Compose » est utilisée pour créer des applications multi-conteneurs, que l’on appelle également des « stacks ».

L’orchestrateur Kubernetes, développé à l’origine par Google, est plus ergonomique que Swarm et Compose. Il s’est imposé comme le standard dans son domaine et est très largement utilisé au sein du secteur. Les entreprises d’hébergement et les fournisseurs de solutions « Software as a Service » (SaaS) et « Platform as a Service » (PaaS) utilisent de plus en plus Kubernetes à titre d’infrastructure sous-jacente.

Les objets Docker

Les workflows dans l’écosystème Docker constituent un résultat de la manière dont les objets Docker interagissent les uns avec les autres. Ils sont gérés en communiquant avec l’API Docker Engine. Jetons un œil à chaque type d’objet en détails.

Image Docker

Une image Docker est un modèle en lecture seule pour créer un ou plusieurs conteneurs identiques. Les images Docker constituent de facto les racines du système ; elles sont utilisées pour mettre en paquets et livrer les applications.

Divers dépôts sont utilisés pour partager les images Docker. Il existe aussi bien des dépôts publics que privés. Lorsque nous écrivons ces lignes, plus de cinq millions d’images sont disponibles en téléchargement sur le célèbre « Docker Hub ». Les commandes Docker « Docker pull » et « Docker push » sont utilisées pour télécharger une image depuis un dépôt et la partager à cet endroit.

Les images Docker sont construites par couches. Chaque couche représente un changement spécifique apporté à l’image. Ceci a pour résultat un versionnage continu des images, lequel permet un retour à un état antérieur. Une image existante peut être utilisée à titre de base pour créer une nouvelle image.

Dockerfile

Un Dockerfile est un fichier texte qui décrit la structure d’une image Docker. Un Dockerfile est similaire à un script de traitement par lots : le fichier contient des commandes qui décrivent une image. Lorsque vous exécutez un Dockerfile, les commandes sont traitées l’une après l’autre. Chaque commande crée une nouvelle couche dans l’image Docker. Vous pouvez donc également voir un Dockerfile comme une sorte de recette utilisée comme base pour créer une image.

Conteneur Docker

Passons maintenant au concept central de l’univers Docker : les conteneurs Docker. Tandis que, d’un côté, une image Docker constitue un modèle inerte, de l’autre, un conteneur Docker représente une instance active et en cours d’exécution d’une image. Une image Docker existe localement sous la forme d’une copie unique et ne prend qu’une faible quantité d’espace de stockage. Par contraste, de multiples conteneurs Docker peuvent être créés depuis la même image et être exécutés en parallèle.

Chaque conteneur Docker consomme une certaine part des ressources du système lors de son exécution, comme le processeur, la RAM, les interfaces réseau, etc. Un conteneur Docker peut être créé, lancé, interrompu, et détruit. Vous pouvez également sauvegarder l’état d’un conteneur en cours d’exécution en tant que nouvelle image.

Volume Docker

Comme nous l’avons vu, vous créez un conteneur Docker exécutable à partir d’une image non-exécutable. Mais qu’en est-il des données qui sont utilisées à l’intérieur du conteneur et ont besoin d’être maintenues au-delà de sa durée de vie ? C’est dans de tels cas qu’on aura recours aux volumes Docker. Un volume Docker existe en-dehors d’un conteneur spécifique. Dès lors, plusieurs conteneurs peuvent partager un même volume. Les données contenues dans le volume sont stockées sur le système de fichiers de l’hôte. Cela signifie qu’un volume Docker est comparable à un dossier partagé sur une machine virtuelle.

Comment Docker fonctionne-t-il ?

Le principe de fonctionnement de base de Docker est similaire à celui de LXC, la technologie de virtualisation développée précédemment : tous deux s’appuient sur le noyau Linux et exécutent une virtualisation basée sur des conteneurs. Docker comme LXC combinent deux buts contradictoires :

  1. Des conteneurs en cours d’exécution partagent le même noyau Linux, ce qui les rend plus léger que des machines virtuelles.
  2. Des conteneurs en cours d’exécution sont isolés les uns des autres et ont uniquement accès à une quantité limitée de ressources système.

Docker et LXC ont recours aux « espaces de nom du noyau » et aux « groupes de contrôle » pour atteindre ces buts. Jetons un œil à la manière dont cela fonctionne en détails.

Le noyau Linux

Le noyau Linux constitue le composant central du système d’exploitation open source GNU/Linux. Le noyau gère le hardware et contrôle les processus. Lorsque l’on exécute Docker en-dehors de Linux, un hyperviseur ou une machine virtuelle est nécessaire pour apporter la fonctionnalité du noyau Linux. Sur macOS, xhyve, un dérivé de l’hyperviseur BSD hypervisor bhyve, est employé. Sur Windows 10, Docker utilise l’hyperviseur Hyper-V.

Les espaces de nom du noyau

Les espaces de nom constituent une des caractéristiques du noyau Linux. Ils compartimentent les ressources du noyau et s’assurent ainsi que les processus restent séparés les uns des autres. Un processus d’espace de nom peut uniquement voir les ressources de noyau du même espace de nom. Voici un aperçu de l’espace de nom utilisé dans Docker :

Espace de nom Description Explication
UTS Identification Système Assigne des conteneurs à leurs propres hôtes et noms de domaine
PID ID de processus Chaque conteneur utilise son propre espace de nom pour les ID de processus ; les PID issus d’autres conteneurs ne sont pas visibles ; par conséquent, deux processus dans des conteneurs différents peuvent avoir recours au même PID sans conflit.
IPC Communication inter-processus Les espaces de nom IPC isolent les processus dans un seul conteneur de sorte qu’ils ne puissent communiquer avec les processus dans d’autres conteneurs.
NET Ressources réseau Assigne des ressources réseau séparées telles que des adresses IP ou des tableau de routage à un conteneur
MNT Points de montage du système fichier Restreint le système fichier de l’hôte à une section étroitement définie du point de vue du conteneur

Groupes de contrôle

Les groupes de contrôle, dont l’abréviation courante est cgroups, sont utilisés pour organiser les processus Linux de manière hiérarchique. Un processus (ou groupe de processus) se voit allouer un nombre limité de ressources système. Ceci comprend la RAM, les cœurs du processeur, le stockage de masse et les appareils réseaux (virtuels). Tandis que les espaces de nom isolent les processus les uns des autres, les groupes de contrôle limitent l’accès aux ressources système. Ceci garantit le fait que le système, dans son ensemble, reste en état de marche lorsqu’il fait fonctionner de multiples conteneurs.

Quels sont les avantages de Docker ?

Jetons un œil à l’histoire du développement informatique en vue de comprendre les bénéfices apportés par Docker. Comment les logiciels étaient-ils développés, livrés et exécutés auparavant et comment le sont-ils aujourd’hui ? Quels aspects du processus ont connu des changements radicaux ? Le logiciel est le pendant du matériel, l’ordinateur physique. Sans le logiciel, l’ordinateur constitue juste un bout de plastique et de métal. Alors que le matériel est fixe et inamovible, le logiciel peut être réinventé et personnalisé. C’est de l’interaction de ces deux niveaux que résulte cet extraordinaire monde digital.

Le logiciel sur une machine physique

Traditionnellement, le logiciel est apparu pour être exécuté sur une machine physique. Mais on se heurte rapidement à un mur lorsque nous suivons cette voie. Un logiciel peut uniquement être exécuté sur certaines machines, par exemple, car il a besoin d’un certain type de processeur.

Par ailleurs, les logiciels les plus complexes ne sont en général pas exécutés de manière complètement autonome, mais sont intégrés au sein d’un écosystème logiciel. Ces derniers comprennent un système d’exploitation, des bibliothèques, et des dépendances. La bonne version de tous ces composants doit être disponible pour leur permettre d’interagir correctement. Une configuration entre également en jeu, laquelle décrit de quelle manière les composants individuels sont liés les uns aux autres.

Si vous souhaitez exécuter plusieurs applications sur une seule et même machine en parallèle, des conflits de versions émergeront sans tarder. Il se peut qu’une application nécessite une version d’un composant qui se trouve être incompatible avec une autre application. Dans le pire des cas, chaque application devrait être exécutée sur sa propre machine physique. Ce qui est vrai est le fait que les machines physiques sont chères et ne peuvent pas s’adapter facilement à la demande. Dès lors, si les besoins en ressources d’une application croissent, il pourra être nécessaire de migrer cette dernière vers une nouvelle machine physique.

Le fait qu’un logiciel en cours de développement soit utilisé dans différents environnements soulève un autre problème. Un développeur écrit du code sur le système local et l’y exécute à titre de test. L’application passe par différents stades de test avant de passer en phase de production, y compris un test d’environnement pour la certification de qualité ou un environnement de simulation pour être testé par l’équipe produit.

Les différents environnements existent souvent sur différentes machines physiques. Il existe presque toujours des différences dans les systèmes d’exploitation, les bibliothèques, et les versions de configuration. Comment peut-on toutes les réconcilier ? Car, si tous les environnements sont différents les uns des autres, les tests n’ont plus aucun sens. Par ailleurs, un système doit être remplacé en cas d’échec. Comment peut-on garantir la compatibilité ? Il n’est pas aisé de gérer ces problèmes sur des machines physiques.

Les machines virtuelles : un pas dans la bonne direction

La popularité des machines virtuelles (VM) découle des problèmes décrits avec l’utilisation des machines physiques. L’idée de base est d’intégrer une couche entre le matériel et le système d’exploitation ou les systèmes d’exploitation hôte et invité. Une VM découple l’environnement des applications du matériel sous-jacent. La combinaison spécifique d’un système d’exploitation, d’une application, de bibliothèques, et d’une configuration peut être reproduite depuis une image. Outre le fait d’isoler complètement une application, ceci permet aux développeurs de grouper plusieurs applications dans une « appliance ».

Les images VM peuvent être déplacées entre les machines physiques, et de multiples système d’exploitation peuvent être exécutés en parallèle. Ceci garantit la scalabilité de l’application. Quoi qu’il en soit, la virtualisation du système d’exploitation mobilise des ressources importantes et est excessive pour de simples cas d’usage.

Les avantages de la virtualisation basée sur les conteneurs avec Docker

Les images utilisées dans la virtualisation basée sur les conteneurs n’ont pas besoin d’un système d’exploitation. La virtualisation basée sur les conteneurs est plus légère et apporte à peu de choses près une isolation aussi élevée que les VM. Une image conteneur combine le code d’une application avec toutes les dépendances requises et la configuration. Les images sont transférables d’un système à l’autre, et les conteneurs développées à partir de ces dernières peuvent être reproduits. Les conteneurs peuvent être utilisés dans divers environnements, tels que le développement, la production, le test et la simulation. Le contrôle de version des images et couches fournit également son lot de modularité.

Résumons donc les avantages-clés de la virtualisation basée sur Docker des applications, par opposition à l’utilisation d’une VM. Un conteneur Docker :

  • Ne contient pas son propre système d’exploitation et matériel simulé
  • Partage un même noyau de systèmes d’exploitation avec d’autres conteneurs hébergés sur le même système
  • Est léger et compact en termes d’utilisation des ressources comparé à une application basée sur une VM
  • Se lance plus vite qu’une machine virtuelle
  • peut être utilisé en parallèle dans plusieurs instances de la même image
  • Peut être utilisé en même temps que d’autres services basés sur les conteneurs via l’orchestration
  • Est parfaitement adapté au développement en local