Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Commencé le 10 août à Slurm Cours vidéo Docker, dans lequel nous l'analysons complètement - des abstractions de base aux paramètres de réseau.

Dans cet article nous parlerons de l'histoire de Docker et de ses principales abstractions : Image, Cli, Dockerfile. La conférence est destinée aux débutants, il est donc peu probable qu'elle intéresse les utilisateurs expérimentés. Il n'y aura pas de sang, d'appendice ou d'immersion profonde. Les bases.

Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Qu'est-ce que Docker

Regardons la définition de Docker sur Wikipédia.

Docker est un logiciel permettant d'automatiser le déploiement et la gestion d'applications dans des environnements conteneurisés.

Rien n’est clair dans cette définition. On ne sait particulièrement pas ce que signifie « dans des environnements prenant en charge la conteneurisation ». Pour le savoir, remontons le temps. Commençons par l’ère que j’appelle conventionnellement « l’ère monolithique ».

Ère monolithique

L’ère monolithique correspond au début des années 2000, lorsque toutes les applications étaient monolithiques, avec de nombreuses dépendances. Le développement a pris beaucoup de temps. En même temps, il n'y avait pas beaucoup de serveurs, nous les connaissions tous par leur nom et les surveillions. Il y a une comparaison tellement amusante :

Les animaux de compagnie sont des animaux domestiques. À l’ère monolithique, nous traitions nos serveurs comme des animaux de compagnie, soignés et chéris, chassant les grains de poussière. Et pour une meilleure gestion des ressources, nous avons eu recours à la virtualisation : nous avons pris un serveur et l'avons découpé en plusieurs machines virtuelles, assurant ainsi l'isolation de l'environnement.

Systèmes de virtualisation basés sur un hyperviseur

Tout le monde a probablement entendu parler des systèmes de virtualisation : VMware, VirtualBox, Hyper-V, Qemu KVM, etc. Ils assurent l'isolation des applications et la gestion des ressources, mais ils présentent également des inconvénients. Pour faire de la virtualisation, vous avez besoin d'un hyperviseur. Et l'hyperviseur représente une surcharge de ressources. Et la machine virtuelle elle-même est généralement un colosse - une image lourde contenant un système d'exploitation, Nginx, Apache et éventuellement MySQL. L'image est volumineuse et la machine virtuelle n'est pas pratique à utiliser. Par conséquent, travailler avec des machines virtuelles peut être lent. Pour résoudre ce problème, des systèmes de virtualisation ont été créés au niveau du noyau.

Systèmes de virtualisation au niveau du noyau

La virtualisation au niveau du noyau est prise en charge par les systèmes OpenVZ, Systemd-nspawn et LXC. Un exemple frappant d'une telle virtualisation est LXC (Linux Containers).

LXC est un système de virtualisation au niveau du système d'exploitation permettant d'exécuter plusieurs instances isolées du système d'exploitation Linux sur un seul nœud. LXC n'utilise pas de machines virtuelles, mais crée un environnement virtuel avec son propre espace de processus et sa propre pile réseau.

Essentiellement, LXC crée des conteneurs. Quelle est la différence entre les machines virtuelles et les conteneurs ?

Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Le conteneur n'est pas adapté pour isoler les processus : des vulnérabilités se trouvent dans les systèmes de virtualisation au niveau du noyau qui leur permettent de s'échapper du conteneur vers l'hôte. Par conséquent, si vous devez isoler quelque chose, il est préférable d’utiliser une machine virtuelle.

Les différences entre la virtualisation et la conteneurisation sont visibles dans le diagramme.
Il existe des hyperviseurs matériels, des hyperviseurs au-dessus du système d'exploitation et des conteneurs.

Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Les hyperviseurs matériels sont intéressants si vous voulez vraiment isoler quelque chose. Car il est possible d'isoler au niveau des pages mémoire et des processeurs.

Il existe des hyperviseurs en tant que programme, et il existe des conteneurs, et nous en reparlerons plus loin. Les systèmes de conteneurisation n'ont pas d'hyperviseur, mais il existe un Container Engine qui crée et gère les conteneurs. Cette chose est plus légère, donc en raison du travail avec le noyau, il y a moins de frais généraux, voire aucun.

Qu'est-ce qui est utilisé pour la conteneurisation au niveau du noyau

Les principales technologies qui permettent de créer un conteneur isolé des autres processus sont les espaces de noms et les groupes de contrôle.

Espaces de noms : PID, Réseau, Montage et Utilisateur. Il y en a d’autres, mais pour faciliter la compréhension, nous nous concentrerons sur ceux-ci.

L'espace de noms PID limite les processus. Lorsque, par exemple, nous créons un espace de noms PID et y plaçons un processus, il devient avec le PID 1. Habituellement, dans les systèmes, le PID 1 est systemd ou init. Par conséquent, lorsque nous plaçons un processus dans un nouvel espace de noms, il reçoit également le PID 1.

Networking Namespace vous permet de limiter/isoler le réseau et de placer vos propres interfaces à l’intérieur. Mount est une limitation du système de fichiers. Utilisateur : restriction sur les utilisateurs.

Groupes de contrôle : Mémoire, CPU, IOPS, Réseau – environ 12 paramètres au total. Sinon, ils sont également appelés groupes C (« groupes C »).

Les groupes de contrôle gèrent les ressources d'un conteneur. Grâce aux groupes de contrôle, nous pouvons dire que le conteneur ne doit pas consommer plus d'une certaine quantité de ressources.

Pour que la conteneurisation fonctionne pleinement, des technologies supplémentaires sont utilisées : capacités, copie sur écriture et autres.

Les capacités, c'est quand nous disons à un processus ce qu'il peut et ne peut pas faire. Au niveau du noyau, ce sont simplement des bitmaps avec de nombreux paramètres. Par exemple, l'utilisateur root dispose de tous les privilèges et peut tout faire. Le serveur de temps peut modifier l’heure du système : il a des capacités sur la Time Capsule, et c’est tout. Grâce aux privilèges, vous pouvez configurer de manière flexible des restrictions pour les processus et ainsi vous protéger.

Le système Copy-on-write nous permet de travailler avec des images Docker et de les utiliser plus efficacement.

Docker a actuellement des problèmes de compatibilité avec Cgroups v2, cet article se concentre donc spécifiquement sur Cgroups v1.

Mais revenons à l'histoire.

Lorsque les systèmes de virtualisation sont apparus au niveau du noyau, ils ont commencé à être activement utilisés. La surcharge sur l'hyperviseur a disparu, mais quelques problèmes subsistent :

  • de grandes images : ils poussent un système d'exploitation, des bibliothèques, un tas de logiciels différents dans le même OpenVZ, et au final l'image s'avère quand même assez grande ;
  • Il n’existe pas de norme normale en matière d’emballage et de livraison, le problème des dépendances demeure donc. Il existe des situations où deux morceaux de code utilisent la même bibliothèque, mais avec des versions différentes. Il peut y avoir un conflit entre eux.

Pour résoudre tous ces problèmes, la prochaine ère est arrivée.

L’ère des conteneurs

Lorsque l’ère des conteneurs est arrivée, la philosophie de travail avec eux a changé :

  • Un processus - un conteneur.
  • Nous livrons toutes les dépendances dont le processus a besoin à son conteneur. Cela nécessite de découper les monolithes en microservices.
  • Plus l'image est petite, mieux c'est : il y a moins de vulnérabilités possibles, elle se déploie plus rapidement, etc.
  • Les instances deviennent éphémères.

Vous vous souvenez de ce que j'ai dit à propos des animaux de compagnie et du bétail ? Auparavant, les instances étaient comme des animaux domestiques, mais maintenant elles sont devenues comme du bétail. Auparavant, il existait un monolithe : une seule application. Il s’agit désormais de 100 microservices, 100 conteneurs. Certains conteneurs peuvent avoir 2 à 3 répliques. Il devient moins important pour nous de contrôler chaque conteneur. Ce qui est plus important pour nous, c'est la disponibilité du service lui-même : ce que fait cet ensemble de conteneurs. Cela change les approches de surveillance.

En 2014-2015, Docker a prospéré - la technologie dont nous parlerons maintenant.

Docker a changé la philosophie et standardisé le packaging des applications. Grâce à Docker, nous pouvons empaqueter une application, l'envoyer vers un référentiel, la télécharger à partir de là et la déployer.

Nous mettons tout ce dont nous avons besoin dans le conteneur Docker, le problème de dépendance est donc résolu. Docker garantit la reproductibilité. Je pense que beaucoup de gens ont été confrontés à l'irreproductibilité : tout fonctionne pour vous, vous le poussez en production, et là, ça ne fonctionne plus. Avec Docker, ce problème disparaît. Si votre conteneur Docker démarre et fait ce qu'il doit faire, alors avec un degré de probabilité élevé, il démarrera en production et y fera de même.

Digression sur les frais généraux

Il y a toujours des conflits sur les frais généraux. Certains pensent que Docker ne supporte pas de charge supplémentaire, puisqu'il utilise le noyau Linux et tous ses processus nécessaires à la conteneurisation. Par exemple, « si vous dites que Docker est en surcharge, alors le noyau Linux est en surcharge. »

D'un autre côté, si vous allez plus loin, il y a effectivement plusieurs choses dans Docker qui, avec un peu d'étirement, peuvent être considérées comme étant des frais généraux.

Le premier est l’espace de noms PID. Lorsque nous plaçons un processus dans un espace de noms, le PID 1 lui est attribué. En même temps, ce processus a un autre PID, situé sur l'espace de noms de l'hôte, en dehors du conteneur. Par exemple, nous avons lancé Nginx dans un conteneur, il est devenu PID 1 (processus maître). Et sur l’hôte, il a le PID 12623. Et il est difficile de dire à quel point cela représente une surcharge.

La deuxième chose concerne les groupes C. Prenons les groupes C par mémoire, c'est-à-dire la possibilité de limiter la mémoire d'un conteneur. Lorsqu'il est activé, les compteurs et la comptabilité mémoire sont activés : le noyau doit comprendre combien de pages ont été allouées et combien sont encore libres pour ce conteneur. Il s'agit peut-être d'une surcharge, mais je n'ai vu aucune étude précise sur la façon dont cela affecte les performances. Et je n'ai moi-même pas remarqué que l'application exécutée dans Docker a soudainement subi une forte perte de performances.

Et encore une remarque sur les performances. Certains paramètres du noyau sont transmis de l'hôte au conteneur. En particulier, certains paramètres réseau. Par conséquent, si vous souhaitez exécuter quelque chose de haute performance dans Docker, par exemple quelque chose qui utilisera activement le réseau, vous devez au moins ajuster ces paramètres. Certains nf_conntrack, par exemple.

À propos du concept Docker

Docker se compose de plusieurs composants :

  1. Docker Daemon est le même moteur de conteneur ; lance des conteneurs.
  2. Docker CII est un utilitaire de gestion Docker.
  3. Dockerfile - instructions sur la façon de créer une image.
  4. Image — l'image à partir de laquelle le conteneur est déployé.
  5. Récipient.
  6. Le registre Docker est un référentiel d'images.

Schématiquement, cela ressemble à ceci :

Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Le démon Docker s'exécute sur Docker_host et lance des conteneurs. Il existe un client qui envoie des commandes : créer l'image, télécharger l'image, lancer le conteneur. Le démon Docker accède au registre et les exécute. Le client Docker peut accéder à la fois localement (à un socket Unix) et via TCP depuis un hôte distant.

Passons en revue chaque composant.

Démon Docker - c'est la partie serveur, elle fonctionne sur la machine hôte : télécharge les images et lance des conteneurs à partir d'elles, crée un réseau entre les conteneurs, collecte les logs. Quand nous disons « créer une image », le démon le fait aussi.

Interface de ligne de commande Docker — Partie client Docker, utilitaire console pour travailler avec le démon. Je le répète, cela peut fonctionner non seulement localement, mais aussi sur le réseau.

Commandes de base :

docker ps - affiche les conteneurs actuellement en cours d'exécution sur l'hôte Docker.
images docker - affiche les images téléchargées localement.
docker search <> - recherche une image dans le registre.
docker pull <> - télécharge une image du registre sur la machine.
construction de docker < > - récupérer l'image.
docker run <> - lance le conteneur.
docker rm <> - supprime le conteneur.
journaux du docker <> - journaux du conteneur
docker start/stop/restart <> - travailler avec le conteneur

Si vous maîtrisez ces commandes et êtes sûr de les utiliser, considérez que vous maîtrisez Docker à 70 % au niveau utilisateur.

Dockerfile - des instructions pour créer une image. Presque chaque commande d’instruction est une nouvelle couche. Regardons un exemple.

Qu'est-ce que Docker : une brève excursion dans l'histoire et les abstractions de base

Voici à quoi ressemble le Dockerfile : les commandes à gauche, les arguments à droite. Chaque commande présente ici (et généralement écrite dans le Dockerfile) crée un nouveau calque dans Image.

Même en regardant du côté gauche, vous pouvez comprendre à peu près ce qui se passe. Nous disons : « créez un dossier pour nous » - c'est une couche. « Faire fonctionner le dossier » est une autre couche, et ainsi de suite. Le gâteau en couches rend la vie plus facile. Si je crée un autre Dockerfile et modifie quelque chose dans la dernière ligne - j'exécute autre chose que "python" "main.py" ou j'installe des dépendances à partir d'un autre fichier - alors les couches précédentes seront réutilisées comme cache.

Image(s) - il s'agit d'un emballage en conteneur ; les conteneurs sont lancés à partir de l'image. Si nous regardons Docker du point de vue d'un gestionnaire de packages (comme si nous travaillions avec des packages deb ou rpm), alors image est essentiellement un package rpm. Grâce à yum install, nous pouvons installer l'application, la supprimer, la trouver dans le référentiel et la télécharger. C'est à peu près la même chose ici : les conteneurs sont lancés à partir de l'image, ils sont stockés dans le registre Docker (semblable à miam, dans un référentiel), et chaque image a un hachage SHA-256, un nom et une balise.

L'image est construite selon les instructions du Dockerfile. Chaque instruction du Dockerfile crée un nouveau calque. Les calques peuvent être réutilisés.

Registre Docker est un référentiel d'images Docker. Semblable au système d'exploitation, Docker dispose d'un registre public standard - Dockerhub. Mais vous pouvez créer votre propre référentiel, votre propre registre Docker.

Contenant - ce qui est lancé depuis l'image. Nous construisons une image selon les instructions du Dockerfile, puis nous la lançons à partir de cette image. Ce conteneur est isolé des autres conteneurs et doit contenir tout le nécessaire au fonctionnement de l'application. Dans ce cas, un conteneur – un processus. Il arrive que vous deviez effectuer deux processus, mais cela est quelque peu contraire à l'idéologie Docker.

L'exigence « un conteneur, un processus » est liée à l'espace de noms PID. Lorsqu'un processus avec le PID 1 démarre dans l'espace de noms, s'il meurt soudainement, le conteneur entier meurt également. Si deux processus y sont en cours d'exécution : l'un est vivant et l'autre est mort, alors le conteneur continuera à vivre. Mais c'est une question de Bonnes Pratiques, nous en parlerons dans d'autres supports.

Pour étudier plus en détail les fonctionnalités et le programme complet du cours, veuillez suivre le lien : «Cours vidéo Docker».

Auteur : Marcel Ibraev, administrateur certifié Kubernetes, ingénieur en exercice à Southbridge, conférencier et développeur de cours Slurm.

Source: habr.com

Ajouter un commentaire