Une introduction conviviale aux conteneurs, aux machines virtuelles et à Docker

Si vous êtes un programmeur ou un technicien, il y a de fortes chances que vous ayez au moins entendu parler de Docker: un outil utile pour emballer, expédier et exécuter des applications dans des «conteneurs». Ce serait difficile de ne pas le faire, avec toute l'attention qu'il reçoit ces jours-ci - de la part des développeurs et des administrateurs système. Même les grands chiens comme Google, VMware et Amazon créent des services pour le soutenir.

Que vous ayez ou non un cas d'utilisation immédiat en tête pour Docker, je pense toujours qu'il est important de comprendre certains des concepts fondamentaux autour de ce qu'est un «conteneur» et comment il se compare à une machine virtuelle (VM). Bien qu'Internet regorge d'excellents guides d'utilisation de Docker, je n'ai pas trouvé beaucoup de guides conceptuels adaptés aux débutants, en particulier sur la composition d'un conteneur. Donc, j'espère que cet article résoudra ce problème :)

Commençons par comprendre ce que sont même les VM et les conteneurs.

Que sont les «conteneurs» et les «VM»?

Les conteneurs et les machines virtuelles ont des objectifs similaires: isoler une application et ses dépendances dans une unité autonome qui peut s'exécuter n'importe où.

De plus, les conteneurs et les machines virtuelles suppriment le besoin de matériel physique, permettant une utilisation plus efficace des ressources informatiques, à la fois en termes de consommation d'énergie et de rentabilité.

La principale différence entre les conteneurs et les machines virtuelles réside dans leur approche architecturale. Regardons de plus près.

Machines virtuelles

Une VM est essentiellement une émulation d'un vrai ordinateur qui exécute des programmes comme un vrai ordinateur. Les machines virtuelles s'exécutent sur une machine physique à l'aide d'un «hyperviseur» . Un hyperviseur, à son tour, s'exécute sur une machine hôte ou sur du «bare-metal» .

Décompressons le jargon:

Un hyperviseur est un logiciel, un micrologiciel ou un matériel sur lequel les VM s'exécutent. Les hyperviseurs eux-mêmes fonctionnent sur des ordinateurs physiques, appelés «machine hôte» . La machine hôte fournit aux machines virtuelles des ressources, notamment de la RAM et du processeur. Ces ressources sont réparties entre les VM et peuvent être distribuées comme bon vous semble. Ainsi, si une VM exécute une application plus gourmande en ressources, vous pouvez allouer plus de ressources à celle-ci qu'aux autres VM exécutées sur la même machine hôte.

La machine virtuelle qui s'exécute sur la machine hôte (encore une fois, à l'aide d'un hyperviseur) est souvent appelée «machine invitée». Cette machine invitée contient à la fois l'application et tout ce dont elle a besoin pour exécuter cette application (par exemple, les binaires système et les bibliothèques). Il contient également une pile matérielle virtualisée complète, y compris des cartes réseau virtualisées, un stockage et un processeur - ce qui signifie qu'il dispose également de son propre système d'exploitation invité à part entière. De l'intérieur, la machine invitée se comporte comme sa propre unité avec ses propres ressources dédiées. De l'extérieur, nous savons qu'il s'agit d'une VM - partageant les ressources fournies par la machine hôte.

Comme mentionné ci-dessus, une machine invitée peut s'exécuter sur un hyperviseur hébergé ou un hyperviseur nu . Il existe des différences importantes entre eux.

Tout d'abord, un hyperviseur de virtualisation hébergé s'exécute sur le système d'exploitation de la machine hôte. Par exemple, un ordinateur exécutant OSX peut avoir une VM (par exemple VirtualBox ou VMware Workstation 8) installée sur ce système d'exploitation. La VM n'a pas d'accès direct au matériel, elle doit donc passer par le système d'exploitation hôte (dans notre cas, l'OSX du Mac).

L'avantage d'un hyperviseur hébergé est que le matériel sous-jacent est moins important. Le système d'exploitation de l'hôte est responsable des pilotes matériels au lieu de l'hyperviseur lui-même, et est donc considéré comme ayant plus de «compatibilité matérielle». D'autre part, cette couche supplémentaire entre le matériel et l'hyperviseur crée plus de surcharge de ressources, ce qui réduit les performances de la machine virtuelle.

Un environnement d'hyperviseur bare metal s'attaque au problème de performances en installant et en exécutant le matériel de la machine hôte. Comme il s'interface directement avec le matériel sous-jacent, il n'a pas besoin d'un système d'exploitation hôte pour fonctionner. Dans ce cas, la première chose installée sur le serveur d'une machine hôte en tant que système d'exploitation sera l'hyperviseur. Contrairement à l'hyperviseur hébergé, un hyperviseur sans système d'exploitation possède ses propres pilotes de périphérique et interagit avec chaque composant directement pour toutes les tâches d'E / S, de traitement ou spécifiques au système d'exploitation. Cela se traduit par de meilleures performances, évolutivité et stabilité. Le compromis ici est que la compatibilité matérielle est limitée car l'hyperviseur ne peut avoir qu'un nombre limité de pilotes de périphériques intégrés.

Après tout ce discours sur les hyperviseurs, vous vous demandez peut-être pourquoi nous avons besoin de cette couche «hyperviseur» supplémentaire entre la VM et la machine hôte.

Eh bien, étant donné que la VM possède son propre système d'exploitation virtuel, l'hyperviseur joue un rôle essentiel en fournissant aux VM une plate-forme pour gérer et exécuter ce système d'exploitation invité. Il permet aux ordinateurs hôtes de partager leurs ressources entre les machines virtuelles qui s'exécutent en tant qu'invités sur eux.

Comme vous pouvez le voir dans le diagramme, les VM regroupent le matériel virtuel, un noyau (c'est-à-dire un système d'exploitation) et un espace utilisateur pour chaque nouvelle VM.

Récipient

Contrairement à une VM qui fournit la virtualisation matérielle, un conteneur fournit une virtualisation au niveau du système d'exploitation en faisant abstraction de «l'espace utilisateur». Vous verrez ce que je veux dire lorsque nous déballons le terme conteneur .

À toutes fins utiles, les conteneurs ressemblent à une machine virtuelle. Par exemple, ils ont un espace privé pour le traitement, peuvent exécuter des commandes en tant que root, avoir une interface de réseau privé et une adresse IP, autoriser des itinéraires personnalisés et des règles iptables, peuvent monter des systèmes de fichiers, etc.

La grande différence entre les conteneurs et les machines virtuelles est que les conteneurs * partagent * le noyau du système hôte avec d'autres conteneurs.

Ce diagramme vous montre que les conteneurs ne regroupent que l'espace utilisateur, et non le noyau ou le matériel virtuel comme le fait une VM. Chaque conteneur dispose de son propre espace utilisateur isolé pour permettre à plusieurs conteneurs de s'exécuter sur une seule machine hôte. Nous pouvons voir que toute l'architecture au niveau du système d'exploitation est partagée entre les conteneurs. Les seules parties créées à partir de zéro sont les bacs et les bibliothèques. C'est ce qui rend les conteneurs si légers.

D'où vient Docker?

Docker est un projet open-source basé sur des conteneurs Linux. Il utilise les fonctionnalités du noyau Linux telles que les espaces de noms et les groupes de contrôle pour créer des conteneurs au-dessus d'un système d'exploitation.

Les conteneurs sont loin d'être neufs; Google utilise sa propre technologie de conteneur depuis des années. Les autres technologies de conteneur Linux incluent les zones Solaris, les prisons BSD et LXC, qui existent depuis de nombreuses années.

Alors pourquoi Docker gagne-t-il soudainement de la vapeur?

1. Facilité d'utilisation: Docker a permis à quiconque - développeurs, administrateurs système, architectes et autres - de tirer parti des conteneurs afin de créer et de tester rapidement des applications portables. Il permet à n'importe qui de conditionner une application sur son ordinateur portable, qui à son tour peut s'exécuter sans modification sur n'importe quel cloud public, cloud privé ou même bare metal. Le mantra est: "Construisez une fois, exécutez n'importe où."

2. Vitesse: les conteneurs Docker sont très légers et rapides. Étant donné que les conteneurs ne sont que des environnements sandbox exécutés sur le noyau, ils utilisent moins de ressources. Vous pouvez créer et exécuter un conteneur Docker en quelques secondes, par rapport aux machines virtuelles, ce qui peut prendre plus de temps car elles doivent démarrer un système d'exploitation virtuel complet à chaque fois.

3. Docker Hub: les utilisateurs de Docker bénéficient également de l'écosystème de plus en plus riche de Docker Hub, que vous pouvez considérer comme un «magasin d'applications pour les images Docker». Docker Hub contient des dizaines de milliers d'images publiques créées par la communauté qui sont facilement disponibles. Il est incroyablement facile de rechercher des images qui répondent à vos besoins, prêtes à être déroulées et utilisées avec peu ou pas de modifications.

4. Modularité et évolutivité: Docker facilite la répartition des fonctionnalités de votre application dans des conteneurs individuels. Par exemple, votre base de données Postgres peut s'exécuter dans un conteneur et votre serveur Redis dans un autre tandis que votre application Node.js est dans un autre. Avec Docker, il est devenu plus facile de lier ces conteneurs entre eux pour créer votre application, ce qui facilite la mise à l'échelle ou la mise à jour des composants indépendamment à l'avenir.

Enfin et surtout, qui n'aime pas la baleine Docker? ;)

Concepts fondamentaux de Docker

Maintenant que nous avons une vue d'ensemble en place, passons en revue les parties fondamentales de Docker pièce par pièce:

Moteur Docker

Le moteur Docker est la couche sur laquelle s'exécute Docker. Il s'agit d'un environnement d'exécution et d'outils légers qui gère les conteneurs, les images, les builds, etc. Il fonctionne nativement sur les systèmes Linux et est composé de:

1. Un démon Docker qui s'exécute sur l'ordinateur hôte.

2. Un client Docker qui communique ensuite avec le démon Docker pour exécuter des commandes.

3. Une API REST pour interagir avec le démon Docker à distance.

Client Docker

Le client Docker est ce avec quoi vous, en tant qu'utilisateur final de Docker, communiquez. Considérez-le comme l'interface utilisateur de Docker. Par exemple, lorsque vous faites…

vous communiquez avec le client Docker, qui communique ensuite vos instructions au démon Docker.

Démon Docker

Le démon Docker est ce qui exécute réellement les commandes envoyées au client Docker - comme la construction, l'exécution et la distribution de vos conteneurs. Le démon Docker s'exécute sur la machine hôte, mais en tant qu'utilisateur, vous ne communiquez jamais directement avec le démon. Le client Docker peut également s'exécuter sur la machine hôte, mais ce n'est pas obligatoire. Il peut s'exécuter sur une machine différente et communiquer avec le démon Docker qui s'exécute sur la machine hôte.

Dockerfile

Un Dockerfile est l'endroit où vous écrivez les instructions pour créer une image Docker. Ces instructions peuvent être:

  • RUN apt-get y install some-package : pour installer un package logiciel
  • EXPOSE 8000: pour exposer un port
  • ENV ANT_HOME / usr / local / apache-ant pour passer une variable d'environnement

et ainsi de suite. Une fois que vous avez configuré votre Dockerfile, vous pouvez utiliser la commande docker build pour créer une image à partir de celui-ci. Voici un exemple de Dockerfile:

Image Docker

Les images sont des modèles en lecture seule que vous créez à partir d'un ensemble d'instructions écrites dans votre Dockerfile. Les images définissent à la fois à quoi vous voulez que votre application packagée et ses dépendances ressemblent * et * quels processus exécuter lors de son lancement.

L'image Docker est créée à l'aide d'un Dockerfile. Chaque instruction du Dockerfile ajoute un nouveau «calque» à l'image, avec des calques représentant une partie du système de fichiers d'images qui ajoute ou remplace le calque en dessous. Les couches sont la clé de la structure légère mais puissante de Docker. Docker utilise un système de fichiers Union pour y parvenir:

Systèmes de fichiers Union

Docker utilise Union File Systems pour créer une image. Vous pouvez considérer un système de fichiers Union comme un système de fichiers empilable, ce qui signifie que les fichiers et répertoires de systèmes de fichiers séparés (appelés branches) peuvent être superposés de manière transparente pour former un système de fichiers unique.

Le contenu des répertoires qui ont le même chemin dans les branches superposées est vu comme un seul répertoire fusionné, ce qui évite d'avoir à créer des copies séparées de chaque couche. Au lieu de cela, ils peuvent tous recevoir des pointeurs vers la même ressource; lorsque certaines couches doivent être modifiées, il crée une copie et modifie une copie locale, laissant l'original inchangé. C'est ainsi que les systèmes de fichiers peuvent * apparaître * inscriptibles sans autoriser réellement les écritures. (En d'autres termes, un système de «copie sur écriture».)

Les systèmes en couches offrent deux avantages principaux:

1. Sans duplication: les couches évitent de dupliquer un ensemble complet de fichiers chaque fois que vous utilisez une image pour créer et exécuter un nouveau conteneur, ce qui rend l'instanciation des conteneurs Docker très rapide et bon marché.

2. Ségrégation des calques: effectuer une modification est beaucoup plus rapide - lorsque vous modifiez une image, Docker propage uniquement les mises à jour sur le calque qui a été modifié.

Volumes

Les volumes sont la partie «données» d'un conteneur, initialisée lors de la création d'un conteneur. Les volumes vous permettent de conserver et de partager les données d'un conteneur. Les volumes de données sont séparés du système de fichiers Union par défaut et existent en tant que répertoires et fichiers normaux sur le système de fichiers hôte. Ainsi, même si vous détruisez, mettez à jour ou reconstruisez votre conteneur, les volumes de données resteront intacts. Lorsque vous souhaitez mettre à jour un volume, vous y apportez des modifications directement. (En prime, les volumes de données peuvent être partagés et réutilisés entre plusieurs conteneurs, ce qui est assez soigné.)

Conteneurs Docker

Un conteneur Docker, comme indiqué ci-dessus, enveloppe le logiciel d'une application dans une boîte invisible avec tout ce dont l'application a besoin pour s'exécuter. Cela inclut le système d'exploitation, le code d'application, le runtime, les outils système, les bibliothèques système, etc. Les conteneurs Docker sont construits à partir d'images Docker. Étant donné que les images sont en lecture seule, Docker ajoute un système de fichiers en lecture-écriture sur le système de fichiers en lecture seule de l'image pour créer un conteneur.

De plus, lors de la création du conteneur, Docker crée une interface réseau afin que le conteneur puisse parler à l'hôte local, attache une adresse IP disponible au conteneur et exécute le processus que vous avez spécifié pour exécuter votre application lors de la définition de l'image.

Une fois que vous avez créé un conteneur avec succès, vous pouvez l'exécuter dans n'importe quel environnement sans avoir à apporter de modifications.

Double-cliquez sur "conteneurs"

Phew! Cela fait beaucoup de pièces mobiles. Une chose qui m'a toujours rendu curieux était de savoir comment un conteneur est réellement implémenté, d'autant plus qu'il n'y a pas de frontière d'infrastructure abstraite autour d'un conteneur. Après beaucoup de lecture, tout cela a du sens, alors voici ma tentative pour vous l'expliquer! :)

Le terme «conteneur» n'est en réalité qu'un concept abstrait pour décrire comment plusieurs fonctionnalités différentes fonctionnent ensemble pour visualiser un «conteneur». Passons en revue très rapidement:

1) Espaces de noms

Les espaces de noms fournissent aux conteneurs leur propre vue du système Linux sous-jacent, limitant ce que le conteneur peut voir et accéder. Lorsque vous exécutez un conteneur, Docker crée des espaces de noms que le conteneur spécifique utilisera.

Il existe plusieurs types d'espaces de noms dans un noyau que Docker utilise, par exemple:

une. NET: Fournit à un conteneur sa propre vue de la pile réseau du système (par exemple, ses propres périphériques réseau, adresses IP, tables de routage IP, répertoire / proc / net, numéros de port, etc.).

b. PID: PID signifie Process ID. Si vous avez déjà exécuté ps aux dans la ligne de commande pour vérifier quels processus sont en cours d'exécution sur votre système, vous aurez vu une colonne nommée «PID». L'espace de noms PID donne aux conteneurs leur propre vue d'ensemble des processus qu'ils peuvent afficher et avec lesquels ils peuvent interagir, y compris un init indépendant (PID 1), qui est «l'ancêtre de tous les processus».

c. MNT: donne à un conteneur sa propre vue des «montages» sur le système. Ainsi, les processus dans différents espaces de noms de montage ont des vues différentes de la hiérarchie du système de fichiers.

ré. UTS: UTS signifie UNIX Timesharing System. Il permet à un processus d'identifier les identifiants du système (ie nom d'hôte, nom de domaine, etc.). UTS permet aux conteneurs d'avoir leur propre nom d'hôte et nom de domaine NIS indépendants des autres conteneurs et du système hôte.

e. IPC: IPC signifie InterProcess Communication. L'espace de noms IPC est chargé d'isoler les ressources IPC entre les processus exécutés à l'intérieur de chaque conteneur.

F. USER: cet espace de noms est utilisé pour isoler les utilisateurs dans chaque conteneur. Il fonctionne en permettant aux conteneurs d'avoir une vue différente des plages uid (ID utilisateur) et gid (ID de groupe), par rapport au système hôte. En conséquence, l'uid et le gid d'un processus peuvent être différents à l'intérieur et à l'extérieur d'un espace de noms utilisateur, ce qui permet également à un processus d'avoir un utilisateur non privilégié à l'extérieur d'un conteneur sans sacrifier les privilèges root à l'intérieur d'un conteneur.

Docker utilise ces espaces de noms ensemble afin d'isoler et de commencer la création d'un conteneur. La fonction suivante est appelée groupes de contrôle.

2) Groupes de contrôle

Les groupes de contrôle (également appelés cgroups) sont une fonctionnalité du noyau Linux qui isole, hiérarchise et rend compte de l'utilisation des ressources (CPU, mémoire, E / S disque, réseau, etc.) d'un ensemble de processus. En ce sens, un groupe de contrôle garantit que les conteneurs Docker n'utilisent que les ressources dont ils ont besoin - et, si nécessaire, définissent les limites des ressources qu'un conteneur * peut * utiliser. Les groupes de contrôle garantissent également qu'un seul conteneur n'épuise pas l'une de ces ressources et ne met pas tout le système hors service.

Enfin, l'union des systèmes de fichiers est une autre fonctionnalité utilisée par Docker:

3) Système de fichiers Union isolé:

Décrit ci-dessus dans la section Images Docker :)

C'est vraiment tout ce qu'il y a à un conteneur Docker (bien sûr, le diable est dans les détails de l'implémentation - comme comment gérer les interactions entre les différents composants).

L'avenir de Docker: Docker et les VM coexisteront

Bien que Docker gagne certainement beaucoup de vitesse, je ne pense pas que cela deviendra une véritable menace pour les VM. Les conteneurs continueront de gagner du terrain, mais il existe de nombreux cas d'utilisation dans lesquels les machines virtuelles sont toujours mieux adaptées.

Par exemple, si vous devez exécuter plusieurs applications sur plusieurs serveurs, il est probablement logique d'utiliser des machines virtuelles. D'un autre côté, si vous devez exécuter plusieurs * copies * d'une seule application, Docker offre des avantages convaincants.

De plus, si les conteneurs vous permettent de diviser votre application en parties discrètes plus fonctionnelles pour créer une séparation des problèmes, cela signifie également qu'il y a un nombre croissant de pièces à gérer, ce qui peut devenir fastidieux.

La sécurité est également un sujet de préoccupation avec les conteneurs Docker - puisque les conteneurs partagent le même noyau, la barrière entre les conteneurs est plus mince. Alors qu'une VM complète ne peut émettre des hypercalls que vers l'hyperviseur hôte, un conteneur Docker peut effectuer des appels système vers le noyau hôte, ce qui crée une plus grande surface d'attaque. Lorsque la sécurité est particulièrement importante, les développeurs sont susceptibles de choisir des machines virtuelles, qui sont isolées par du matériel abstrait, ce qui rend beaucoup plus difficile les interférences les unes avec les autres.

Bien sûr, des problèmes tels que la sécurité et la gestion sont certains d'évoluer à mesure que les conteneurs sont davantage exposés en production et sont davantage surveillés par les utilisateurs. Pour l'instant, le débat sur les conteneurs et les machines virtuelles est vraiment mieux pour les développeurs qui les vivent et les respirent tous les jours!

Conclusion

J'espère que vous êtes maintenant équipé des connaissances dont vous avez besoin pour en savoir plus sur Docker et peut-être même l'utiliser dans un projet un jour.

Comme toujours, écrivez-moi dans les commentaires si j'ai fait des erreurs ou si je peux être utile de toute façon! :)