IMA4 2018/2019 P30 : Différence entre versions
(→Objectifs) |
(→Documents Rendus) |
||
(286 révisions intermédiaires par le même utilisateur non affichées) | |||
Ligne 19 : | Ligne 19 : | ||
# La taille : En effet un conteneur prend quelques dizaines de mégaoctets de taille, contrairement à une VM qui peut prendre plusieurs Gigabytes à cause de son système d'exploitation.Ce qui rend l'utilisation des conteneurs très populaire sur les serveurs qui peuvent en contenir plusieurs. Et dans le cloud où l'on paie ce qu'on consomme. | # La taille : En effet un conteneur prend quelques dizaines de mégaoctets de taille, contrairement à une VM qui peut prendre plusieurs Gigabytes à cause de son système d'exploitation.Ce qui rend l'utilisation des conteneurs très populaire sur les serveurs qui peuvent en contenir plusieurs. Et dans le cloud où l'on paie ce qu'on consomme. | ||
− | # Le temps : Une application conteneurisée peut être démarrée instantanément et quand il est nécessaire peut disparaître libérant des ressources sur son hôte tandis que les machines virtuelles peuvent prendre plusieurs minutes pour démarrer leurs systèmes d'exploitation et commencer à exécuter les applications qu' | + | # Le temps : Une application conteneurisée peut être démarrée instantanément et quand il est nécessaire peut disparaître libérant des ressources sur son hôte tandis que les machines virtuelles peuvent prendre plusieurs minutes pour démarrer leurs systèmes d'exploitation et commencer à exécuter les applications qu'elles hébergent. |
− | + | #La modularité : Plutôt que d'exécuter une application complexe entière dans un seul conteneur, l'application peut être divisée en modules (tels que la base de données, l'interface de l'application, etc.) Les applications créées de cette manière sont plus faciles à gérer car chaque module est relativement simple et des modifications peuvent être apportées aux modules sans avoir à reconstruire l'application entière. Étant donné que les conteneurs sont si légers, les modules individuels (ou micro services) ne peuvent être instanciés que lorsqu'ils sont nécessaires et sont disponibles presque immédiatement. | |
− | |||
Avec une telle architecture, on suit la philosophie UNIX du “KISS” | Avec une telle architecture, on suit la philosophie UNIX du “KISS” | ||
* Les applications n’ont pas de dépendance système. | * Les applications n’ont pas de dépendance système. | ||
* Les mises à jours sont déployables facilement. | * Les mises à jours sont déployables facilement. | ||
− | * La consommation de ressources est | + | * La consommation de ressources est optimisée. |
Les containers sont donc proches des machines virtuelles, mais présentent des avantages importants. | Les containers sont donc proches des machines virtuelles, mais présentent des avantages importants. | ||
Ligne 42 : | Ligne 41 : | ||
==Objectifs== | ==Objectifs== | ||
− | + | L'objectif de ce projet est de réaliser un écosystème minimal permettant de gérer des images et des instances de conteneurs. | |
− | + | *Les commandes seront réalisées en shell et les informations stockées dans des fichiers textes. Une configuration réseau devra être mise en place. | |
− | + | *Il est demandé de réaliser un système de gestion de conteneurs comme Docker mais en plus simple. | |
− | + | *Il est interdit de lancer un démon ou de perturber le réseau de l'hôte avec des règles de filtrage de paquets. | |
− | + | *Un contrôle des instances se fera par "crontab" et les instances seront connectées via des commutateurs virtuels Linux classiques. | |
− | + | *La gestion sophistiquée des images à base de système de fichiers à couche n'est pas permise. | |
− | + | *Avant de lancer un conteneur, le système de fichiers (un fichier lui-même) sera copié et le conteneur sera démarré sur la copie. | |
− | |||
− | * | ||
− | * | ||
− | * | ||
− | |||
− | |||
=Analyse du projet= | =Analyse du projet= | ||
Ligne 63 : | Ligne 56 : | ||
==Analyse du premier concurrent== | ==Analyse du premier concurrent== | ||
− | Docker Enterprise Edition est sans doute la solution de gestion de conteneurs commerciale la plus connue. Il fournit une plate-forme intégrée, testée et certifiée pour les applications exécutées sur les systèmes d'exploitation Linux ou Windows et les fournisseurs de cloud. L’écosystème de Docker est riche et complet, cela va de la gestion de conteneurs, à l’orchestration (Docker Swarm ou son concurrent Google Kubernetes) tout en passant par la gestion du réseau, de l’intégration du cloud avec Docker machine, des | + | [[Fichier:Docker_logo.jpg|200px|thumb|right|Docker]] |
+ | Docker Enterprise Edition est sans doute la solution de gestion de conteneurs commerciale la plus connue. Il fournit une plate-forme intégrée, testée et certifiée pour les applications exécutées sur les systèmes d'exploitation Linux ou Windows et les fournisseurs de cloud. L’écosystème de Docker est riche et complet, cela va de la gestion de conteneurs, à l’orchestration (Docker Swarm ou son concurrent Google Kubernetes) tout en passant par la gestion du réseau, de l’intégration du cloud avec Docker machine, des applications multi conteneurs avec Docker Compose. | ||
Aujourd’hui, selon les créateurs de Docker, plus de 3,5 millions d’applications ont été containérisées en utilisant cette technologie, et plus de 37 milliards d’applications containérisées ont été téléchargées. | Aujourd’hui, selon les créateurs de Docker, plus de 3,5 millions d’applications ont été containérisées en utilisant cette technologie, et plus de 37 milliards d’applications containérisées ont été téléchargées. | ||
Ligne 70 : | Ligne 64 : | ||
==Analyse du second concurrent== | ==Analyse du second concurrent== | ||
+ | [[Fichier:rocket.jpg|200px|thumb|left|Rocket]] | ||
+ | Rocket (rkt) est un outil édité par CoreOS et est le concurrent de Docker. | ||
+ | C'est un moteur de conteneur d'application développé pour les environnements de production modernes basés sur le cloud. Il présente une approche pod-native, un environnement d’exécution enfichable et une surface bien définie qui le rend idéal pour une intégration avec d’autres systèmes. | ||
+ | |||
+ | L'unité d'exécution principale de rkt est le "pod": un ensemble d'une ou plusieurs applications s'exécutant dans un contexte partagé (les pods de rkt sont synonymes du concept du système d'orchestration Kubernetes). Rkt permet aux utilisateurs d'appliquer différentes configurations (telles que les paramètres d'isolation) au niveau du pod et au niveau plus granulaire par application. L’architecture de rkt signifie que chaque pod s’exécute directement dans le modèle de processus Unix classique (c’est-à-dire qu’il n’y a pas de démon central), dans un environnement isolé et autonome. rkt implémente un format de conteneur standard moderne et ouvert, la spécification App Container (appc), mais peut également exécuter d'autres images de conteneur, telles que celles créées avec Docker. | ||
− | + | Depuis son introduction par CoreOS en décembre 2014, le projet rkt a considérablement mûri et est largement utilisé. Il est disponible pour la plupart des principales distributions Linux et chaque version de rkt construit des packages rpm / deb autonomes que les utilisateurs peuvent installer. Ces packages sont également disponibles dans le référentiel Kubernetes pour permettre de tester l'intégration rkt + Kubernetes. rkt joue également un rôle central dans la manière dont Google Container Image et CoreOS Container Linux exécutent Kubernetes. | |
− | + | Les éditeurs se concentrent sur la sécurité (le plus gros point faible de Docker), la compatibilité et une intégration aux standards. | |
− | + | Le but étant de fournir les mêmes fonctionnalités que docker et être complémentaire. | |
+ | |||
+ | ==Docker comparé à Rkt== | ||
+ | *Sécurité de l'image du conteneur : | ||
+ | L’un des avantages de Docker est qu’il existe un registre public à partir duquel tout le monde peut télécharger des images optimisées du serveur d’applications. Donc, si vous voulez un serveur Nginx optimisé pour une application Web Magento, vous en obtiendrez un du registre Docker. | ||
+ | Cependant, il y a un danger caché à cela. Un attaquant peut remplacer une image de serveur par une autre infectée par un logiciel malveillant. | ||
+ | Avant la version 1.8, Docker n’avait aucun moyen de vérifier l’authenticité d’une image de serveur. Mais dans la version 1.8, une nouvelle fonctionnalité appelée Docker Content Trust a été introduite pour signer et vérifier automatiquement la signature d'un éditeur. | ||
+ | Dans rkt, la vérification de la signature est effectuée par défaut. Ainsi, dès qu'une image de serveur est téléchargée, elle est vérifiée avec la signature de l'éditeur pour voir si elle est falsifiée. | ||
+ | *Prévention des attaques d'élévation de privilèges «root» | ||
+ | Docker s'exécute avec les privilèges de super-utilisateur «root» et crée de nouveaux conteneurs en tant que sous-processus. Le problème avec cela est qu'une vulnérabilité dans un conteneur ou un confinement médiocre peut donner à un attaquant un accès de niveau racine au serveur entier. | ||
+ | Rkt a proposé une meilleure solution: les nouveaux conteneurs ne sont jamais créés à partir d'un processus root. De cette manière, même si une rupture de conteneur se produit, l'attaquant ne peut pas obtenir les privilèges root. | ||
+ | *Flexibilité dans la publication ou le partage d'images | ||
+ | Lors du développement d'applications, il peut être nécessaire de partager des images de conteneur avec vos partenaires technologiques. Si vous souhaitez partager des conteneurs Docker, vous devez configurer un registre privé spécial sur vos serveurs ou l'héberger dans un compte payant Docker pour le partager avec vos partenaires. | ||
+ | Pour rkt, vous n’avez besoin que de votre serveur Web. Rkt utilise le protocole HTTPS pour télécharger des images et utilise une méta-description sur le serveur Web pour pointer vers l'emplacement. C’est donc un serveur de moins à maintenir et plus facile à accéder pour les partenaires. | ||
+ | *Taille de la base de code | ||
+ | Docker ajoute toutes ses fonctionnalités à un seul fichier programme monolithique, ce qui signifie que le nombre de lignes de code ne cesse d’augmenter à chaque nouvelle version. Et voici un problème de sécurité. Une seule vulnérabilité dans l'une des milliers de lignes de code peut compromettre l'intégralité du programme et donner essentiellement un accès root à un attaquant. | ||
+ | Au contraire, rkt utilise une architecture modulaire dans laquelle les fonctionnalités sont développées sous forme de fichiers binaires indépendants par différents fournisseurs. Le code de base est maintenu aussi petit que possible afin de réduire la surface d'attaque. Cela garantit que même si un sous-composant tel que etcd est compromis, les dommages seraient limités aux données auxquelles ce programme peut accéder. | ||
+ | *Portabilité à d'autres systèmes de conteneurs | ||
+ | De nouveaux systèmes de conteneurs sortent tout le temps. Avec Docker, la migration vers une nouvelle technologie de conteneur peut poser problème, car il utilise un format d’image propriétaire. | ||
+ | En revanche, rkt utilise un format de conteneur open source appelé «appc». Ainsi, toute image de serveur créée à l'aide de rkt peut être facilement portée vers un autre système de conteneur à condition qu'elle suive le format ouvert «appc». | ||
+ | En adhérant à un standard ouvert, rkt n’impose pas de verrouillage du fournisseur. Cela aide les propriétaires de système à migrer sans peine vers un autre système de conteneur mieux adapté à leurs besoins. | ||
+ | [https://bobcares.com/blog/docker-vs-rkt-rocket/] | ||
==Scénario d'usage du produit ou du concept envisagé== | ==Scénario d'usage du produit ou du concept envisagé== | ||
Ligne 97 : | Ligne 117 : | ||
Pour répondre à cette question, il faut comprendre comment Docker fonctionne : Docker est un logiciel modulaire qui comprend plusieurs couches. L'une de ses couches est le docker deamon (dockerd), le deamon est codé afin d'exposer une API REST. Ainsi, il est bind sur des sockets au sein de la machine. | Pour répondre à cette question, il faut comprendre comment Docker fonctionne : Docker est un logiciel modulaire qui comprend plusieurs couches. L'une de ses couches est le docker deamon (dockerd), le deamon est codé afin d'exposer une API REST. Ainsi, il est bind sur des sockets au sein de la machine. | ||
− | L'autre inconvénient de Docker c'est qu'il va manipuler les Iptables | + | L'autre inconvénient de Docker c'est qu'il va manipuler les Iptables [https://docs.docker.com/network/iptables/] afin de pouvoir gérer le réseau des conteneurs, cela peut venir perturber les configurations réseaux déjà présentes sur la machine. Mon système n'utilisera que les commutateurs logiciels (bridges linux) standards. Ainsi, il n'y aura aucune altération du routage des paquets au sein de la machine. |
− | De plus je n'utiliserai pas de gestion sophistiquée des images à base de système de fichiers à couche. Avant de lancer un conteneur le système de fichiers (un fichier lui-même) est copié, et le conteneur est démarré sur cette copie. | + | De plus, je n'utiliserai pas de gestion sophistiquée des images à base de système de fichiers à couche. Avant de lancer un conteneur, le système de fichiers (un fichier lui-même) est copié, et le conteneur est démarré sur cette copie. |
=Préparation du projet= | =Préparation du projet= | ||
==Cahier des charges== | ==Cahier des charges== | ||
+ | |||
+ | Ce projet peut se découper en cinq grandes parties : | ||
+ | |||
+ | <ul> | ||
+ | <li>Commencer par faire un état de l'art afin de me forger des connaissances solides sur les conteneurs et leur orchestration.</li> | ||
+ | <li>Créer, configurer et supprimer des conteneurs "à la main".</li> | ||
+ | <li>Créer un programme shell (qu'on lance dans le contexte d'un conteneur) qui permet d'automatiser la gestion des conteneurs, pour :</li> | ||
+ | <ol> | ||
+ | <li>Lancer une instance.</li> | ||
+ | <li>Lister les instances en cours d'exécution (identifiant, nom de l'image d'origine, temps d'exécution) tout en mettant à jour le fichier des instances (s'il le faut).</li> | ||
+ | <li>Arrêter une instance, détruire ses ressources réseau tout en conservant son image disque dans un état stable ainsi que ses caractéristiques.</li> | ||
+ | <li>Suppression d'une instance avec la destruction de son image disque et ses caractéristiques: </li> | ||
+ | <ul> | ||
+ | <li>Stocker les informations sur les conteneurs qui tournent dans un fichier texte, paramétrer finement la configuration réseau.</li> | ||
+ | <li>Mettre en place une application de test : une architecture de ferme de serveurs Web privée avec un accès par mandataire inverse. Les serveurs Web et le mandataire inverse seront des conteneurs.</li> | ||
+ | </ul> | ||
+ | </ol> | ||
+ | </ul> | ||
+ | |||
+ | L'objectif est de créer un programme qui se présentera comme suit (on le nommera baleine) : | ||
+ | |||
+ | *Crée un nouveau conteneur (lui ajouter des contraintes avec les cgroups, ajouter le bridge auquel l'ajouter ainsi que son adresse ip) : | ||
+ | baleine container create | ||
+ | *Retourne la liste des conteneurs qui tournent sur le système, avec leurs adresses ip, leurs limitations cgroups, etc | ||
+ | baleine container list | ||
+ | *Supprimer un conteneur : | ||
+ | baleine container remove | ||
+ | *Stopper le conteneur : | ||
+ | baleine container stop | ||
+ | *Redémarrer un conteneur: | ||
+ | baleine container restart | ||
+ | *Crée un nouveau pont (bridge) linux afin d'interconnecter les containers | ||
+ | baleine bridge create | ||
+ | *Renvoie la liste des ponts linux ainsi que le nombre de containers connectés dessus | ||
+ | baleine bridge list | ||
+ | *Crée une nouvelle image avec la taille indiquée | ||
+ | baleine image create | ||
+ | *Liste les images présentes sur le système | ||
+ | baleine image list | ||
+ | *importe une image depuis une image Docker | ||
+ | baleine image import | ||
+ | *exporte une image vers une image Docker | ||
+ | baleine image export | ||
+ | Afin de lister les conteneurs, les images, et les bridges, il sera nécessaire de les stocker dans un fichier. | ||
+ | Pour les conteneurs, ce fichier sera /var/lib/baleine/manifeste/containers/nom_conteneur.manifest, il se présentera sous la forme : | ||
+ | nom_container: | ||
+ | nom_image: | ||
+ | pid: | ||
+ | nom_bridge: | ||
+ | starting_time: | ||
+ | Pour les images, ce fichier sera /var/lib/baleine/manifeste/images/nom_image.manifest, il se présentera sous la forme : | ||
+ | nom_image: | ||
+ | taille: | ||
+ | chemin: | ||
+ | Pour les bridges, ce fichier sera /var/lib/baleine/manifeste/bridges/nom_bridge.manifest, il se présentera sous la forme : | ||
+ | nom_bridge: | ||
+ | |||
==Choix techniques : matériel et logiciel== | ==Choix techniques : matériel et logiciel== | ||
− | Ce projet est purement informatique, il ne nécessite aucun matériel. Par contre au moment où je ferai l'infrastructure avec les conteneurs, j'aurai besoin d'un serveur. | + | Ce projet est purement informatique, il ne nécessite aucun matériel. Par contre, au moment où je ferai l'infrastructure avec les conteneurs, j'aurai besoin d'un serveur ainsi qu'une machine virtuelle. |
− | |||
==Liste des tâches à effectuer== | ==Liste des tâches à effectuer== | ||
Ligne 124 : | Ligne 200 : | ||
==Calendrier prévisionnel== | ==Calendrier prévisionnel== | ||
− | Le projet étant à rendre au mois de Mai, prenant en compte une marge d' | + | Le projet étant à rendre au mois de Mai, prenant en compte une marge d'erreur et d'amélioration éventuelle, le calendrier prévisionnel optimal que je me suis fixée se trouve sur le diagramme de Gantt suivant : |
[[Fichier:Gantt_prévisionnel.png]] | [[Fichier:Gantt_prévisionnel.png]] | ||
=Réalisation du Projet= | =Réalisation du Projet= | ||
==Feuille d'heures== | ==Feuille d'heures== | ||
+ | |||
+ | Cette feuille d'heures a été faite approximativement. En effet, ayant apprécié le sujet, j'y ai consacré un temps sans vraiment compter, et j'aurai voulu aller plus loin pour répondre à toutes les consignes du cahier des charges. | ||
{| class="wikitable" | {| class="wikitable" | ||
!Tâche !! Prélude !! Heures S1 !! Heures S2 !! Heures S3 !! Heures S4 !! Heures S5 !! Heures S6 !! Heures S7 !! Heures S8 !! Heures S9 !! Heures S10 !! Total | !Tâche !! Prélude !! Heures S1 !! Heures S2 !! Heures S3 !! Heures S4 !! Heures S5 !! Heures S6 !! Heures S7 !! Heures S8 !! Heures S9 !! Heures S10 !! Total | ||
|- | |- | ||
− | | Analyse du projet | + | | Analyse du projet || 10 || || || || || || || || || || ||10 |
− | | | + | |- |
− | | | + | |État de l'art || 2|| 8 || || || ||2 ||3 || || || || || 15 |
− | | | + | |- |
− | | | + | |Création-configuration des conteneurs à la main || || ||6 || 5|| 4|| || || || || || || 15 |
− | | | + | |- |
− | | | + | |Configuration des interfaces et création du bridge|| || |||| || || 6|| || || || || || 6 |
− | | | + | |- |
− | | | + | |Script Shell|| || || || || || ||8 ||4 ||3 ||2 ||4|| 21 |
− | | | + | |- |
− | | | + | |Gestion du stockage des informations des conteneurs || || || || || || || || ||6 || || || 6 |
− | | | + | |- |
− | | | + | |Paramétrage de la configuration réseau|| || || || || || | || || || || 8|| || 8 |
+ | |- | ||
+ | |Mise en application de l'application des tests|| || || || || || | || || || || ||4 || 4 | ||
+ | |- | ||
+ | |Rédaction du rapport || || || || || || | || || || || ||4|| 4 | ||
+ | |- | ||
+ | |Rédaction du Wiki|| 1 ||1 ||1 || 1|| 1|| 1|| 1|| 1|| 1||1 ||1 || 11 | ||
+ | |- | ||
+ | |'''Total'''|| 13 ||9 ||7 ||6 ||5 || 9||12 ||5 ||10 || 11|| 13|| 100 | ||
|} | |} | ||
− | |||
==Prologue== | ==Prologue== | ||
==Semaine 1== | ==Semaine 1== | ||
+ | La première semaine a été dédiée à l’étude de l'état de l'art de la conteneurisation. Et ce, afin de me familiariser et d'en apprendre un maximum sur le sujet, comprendre les subtilités de l’utilité des conteneurs ainsi que leurs évolutions dans le temps. | ||
+ | Le sujet était passionnant et très intéressant, les sources étaient multiples et j’ai dû filtrer, traduire, composer mon propre état de l’art en récoltant de chaque source ce que je trouvais intéressant à mentionner. Cette quête de l’information m’a beaucoup apportée et initiée à la recherche, cependant elle m’a pris un peu plus d’une séance, mais c’était une partie très agréable à faire. | ||
+ | |||
==Semaine 2== | ==Semaine 2== | ||
+ | Durant cette semaine, il m'a été recommandé de réaliser des TP dédiés aux GIS sur la virtualisation et me familiariser avec les commandes permettant de créer des conteneurs à la main, ce que j'ai réussi à faire. Une fois arrivée à la partie où il faut limiter / restreindre les cpu, la mémoire, le nombre d'entrées/sorties ou encore l'interdiction d'accès à des périphériques, les commandes fonctionnaient mais il fallait tester si elles fonctionnaient bien et qu'elles délimitaient bien. Il a fallu trouver des idées pour tester la mémoire par exemple : allouer de la mémoire en boucle infinie et renvoyer le nombre de fois où ça a été fait afin de vérifier que ça respecte bien la taille donnée dans le cgroup. Cela ne fonctionnait point, et la compréhension des cgroups m'a prise plus de temps que prévu , ce qui m'a retardé... | ||
+ | |||
+ | ==Semaine 3== | ||
+ | Suite au blocage de la semaine d’avant, je n’ai pas réussi à avancer, j’ai dû réaliser quelques essais mais en vain, j’ai essayé de limiter le nombre de CPUs à utiliser par le conteneur , l’écriture sur le fichier cpuset.cpus en mentionnant le PID du processus du conteneur dans le fichier tasks du pseudo fichier model1 se faisait correctement. Le problème était la vérification, en essayant de lancer des processus différents qui obligeraient l'utilisation d’un autre CPU, le conteneur prenait d’autre CPUs même si j’avais fait en sorte qu’il en ait pas le droit. | ||
+ | De même, j’ai essayé de limiter les entrées sorties mais en vain. Seule la restriction mémoire a fonctionné, description des détails dans la section “création des conteneurs à la main” ci-dessous. | ||
+ | J’ai fini par abandonner les restrictions par les cgroups (et faire ça en parallèle) et entamer d’autres tâches qui me paraissaient prioritaires et ce, en attendant l’aide de mon encadrant. | ||
+ | |||
+ | ==Semaine 4== | ||
+ | Lors de cette semaine, j'ai installé Docker, suivi le Get started pour manier les commandes, découvert son fonctionnement pour finalement mieux comprendre ce qui est demandé de faire dans le projet. J'ai lu de nombreux passages du livre Docker Deep Dive [https://leanpub.com/dockerdeepdive] de Nigel Poulton qui est une référence dans le monde de Docker. C'est un livre qui décrit l'histoire de Docker à travers le temps mais aussi qui rentre dans le détail technique de ses fonctionnalités. Ce document m'a été bénéfique afin de comprendre les tenants et aboutissants de Docker. Cela m'a aidé à imaginer comment mon script allait fonctionner. | ||
+ | Ainsi, pour mon script, je vais m’inspirer de la syntaxe de docker afin de créer des conteneurs, d'exécuter une commande à l’intérieur de ceux-ci, de créer des bridges réseaux pour les conteneurs, etc. | ||
+ | |||
+ | ==Semaine 5== | ||
+ | Durant cette semaine, j’ai commencé à réfléchir au cahier des charges précis et clair du projet et j’ai écrit une première ébauche de ce que devra être le format de stockage des fichiers, pour les conteneurs, pour les images et pour les bridges réseau. | ||
+ | |||
+ | J’ai donc établi l’architecture logicielle de mon application. Les informations sur les conteneurs, les images et les interfaces réseaux se trouveront dans des fichiers au format CSV qui seront manipulés par mon application. | ||
+ | |||
+ | En parallèle, il m’a fallu comprendre comment fonctionnaient les veth linux qui permettent de connecter deux espaces de nom réseau différents, et comment les utiliser dans le cadre de mon projet. Il faudra donc faire en sorte de créer une paire de veth par conteneur au moins, et l’une des extrémités de la paire veth sera à mettre dans l’espace de nom du conteneur. L’autre extrémité sera à connecter au bridge Linux côté hôte. | ||
+ | |||
+ | A la fin de cette séance j’ai réussi à mettre en place plusieurs conteneurs qui communiquent grâce à leurs interfaces virtuelles via un bridge mis dans l’espace de nom hôte, puis j’ai commencé à réfléchir à la façon d’écrire mon script qui fera cela de façon automatique. J’ai tenté d’envisager les éventuelles problématiques que je rencontrerai. | ||
+ | |||
+ | Par exemple, une de mes difficultés est la suivante : lorsque je lance le unshare, il faut faire attention à ce que je garde la main sur le terminal qui lance le unshare, car c’est sur ce terminal que tournera le script. Ainsi, après avoir recherché sur le net, j’ai appris l'existence de l’utilitaire “screen” qui est un multiplexeur de terminal. | ||
+ | |||
+ | ==Semaine 6== | ||
+ | Lors de cette semaine j'ai essayé de résoudre le problème du "disown" (pour permettre à mon unshare de continuer à tourner lorsque le shell qui le lance s'arrête) mais en vain. À la fin de chaque exécution, le unshare n’apparaît plus dans la liste des processus. Je me suis aussi documentée sur comment Docker parvenait à franchir cette étape grâce au runc afin de m'en inspirer. | ||
+ | J'ai préféré donc me mettre à écrire le script me permettant de créer l'interface réseau ainsi que sa configuration lors du lancement du conteneur. Je me suis rendue compte que j'avais mal considéré la structure et le squelette de mes scripts en ne prenant pas en compte les formats d'images de lancement des conteneurs ! Une documentation technique s'est imposée. | ||
+ | |||
+ | ==Semaine 7== | ||
+ | Durant cette semaine, j’ai été boostée et j'ai bien avancée car pleins de problèmes se sont débloqués ce qui m’a énormément motivée ! | ||
+ | |||
+ | En effet, le souci des cgroup (mémoire) a été réglé, il était difficile de comprendre pourquoi ça ne fonctionnait pas alors que la méthode décrite sur tous les forums avait l’air simple et fonctionnelle. Il se trouve qu’en limitant la mémoire grâce à “memory.limit_in_bytes” et sans changer memory.swappiness, en arrivant à la limite fixée, le SWAP (défini en annexe) rentrait en action et procurait de l’espace à notre mémoire. Ainsi, nous n’avions pas une limite mémoire fonctionnelle. La solution est de le fixer à 0. | ||
+ | Ainsi, notre mémoire était complètement limitée ! | ||
+ | L’idée maintenant est de découvrir l’utilité de tous les pseudo fichiers des cgroups concernant les CPU et parvenir à limiter de la même manière les CPU. | ||
+ | |||
+ | Suite au travail de la semaine précédente, une bonne partie de l’état de l’art sur les images a été écrite, ce qui m’a permis d’avoir une compréhension plus détaillée et précise à ce sujet. Il en a résulté d’une bonne avancée côté script. En effet, on peut désormais en lançant le script créer une image, en choisissant sa taille, stocker son nom, sa taille et son chemin dans un fichier texte portant son nom. | ||
+ | |||
+ | Il faudra entreprendre le script permettant de lister les images. Pour ce dernier, il faudra faire une sorte de manifeste (défini en annexe) de chaque image, qui décrira cette dernière. A chaque création d’une image, il conviendra d’alimenter ce manifeste. Un format de manifeste d’image est déjà décrit par l’Open Container Initiative (norme que suit Docker). Cette spécification est disponible à l’adresse : [https://github.com/opencontainers/image-spec/blob/master/spec.md] | ||
+ | |||
+ | ==Semaine 8== | ||
+ | |||
+ | Durant cette semaine là, je me suis occupée principalement de l’organisation des manifestes. Le souci c’est que je ne parvenais pas à créer plusieurs images portant le même nom. J’ai essayé donc d’attribuer à chaque image une petite extension contenant un chiffre aléatoire ce qui la distinguerait d’une autre image portant le même nom. Mais cela rendrait plus difficile la manipulation de son manifeste et la récupération de ses informations. | ||
+ | |||
+ | ==Semaine 9== | ||
+ | Pendant cette semaine, je me suis concentrée sur la configuration réseau du conteneur mais j’ai rencontré un certain nombre de soucis. En effet, je ne parvenais pas à exécuter une commande dans le namespace du conteneur. J’utilisais la commande ip netns exec, cependant cette commande attend un nom de namespace et non un identifiant de namespace. Autrement dit je ne pouvais pas utiliser ip netns exec avec l’argument /proc/PID/ns/net, il me fallait en amont créer un nouveau namespace. | ||
+ | |||
+ | ==Semaine 10== | ||
+ | |||
+ | Étant donné qu’il m’a été interdit de lancer un démon ou de perturber le réseau de l'hôte avec des règles de filtrage de paquets, j’utilise de simples bridges linux. | ||
+ | En cette dernière séance officielle de projet, j’ai réussi à : | ||
+ | *créer un bridge depuis le script et créer son manifeste | ||
+ | *le supprimer | ||
+ | *lister les bridges présents | ||
+ | *Mettre up/down un bridge | ||
+ | *lister et supprimer les images | ||
+ | |||
+ | Mais il reste des choses à faire : | ||
+ | |||
+ | *Faire en sorte qu’en une seule commande on arrive à créer une image, créer à partir de la copie de cette dernière le conteneur. | ||
+ | *créer la ferme web pour le test avec mandataire inverse | ||
+ | *Utiliser "cron" pour contrôler les instances | ||
+ | *Importer / exporter une image depuis/vers Docker | ||
+ | *Écrire un Help | ||
+ | |||
+ | == Semaines vacances + semaines supplémentaires == | ||
+ | |||
+ | |||
+ | Il était évident qu’il fallait rajouter des heures supplémentaires pour mener à bien ce projet, je n’ai pas compté les heures ni le temps consacré à réaliser ce projet. | ||
+ | |||
+ | Je suis revenue sur le problème de la configuration réseau que j’avais rencontré deux semaines plus tôt. J’ai découvert que sur Linux, il était impossible d'avoir des interfaces avec des noms de plus de 16 caractères. Et puisque le seul moyen de nommer l’interface de “tel “ conteneur était de reprendre la variable “NOM_CONTAINER” et lui appliquer par exemple la commande : eth0@vif1 | ||
+ | Je me suis donc orientée vers la commande nsenter qui permet de faire ce que je souhaite, c’est à dire d'exécuter une commande à l’intérieur du namespace d’un process. Nsenter me permettra ainsi entre autres d’ajouter une adresse ip à une interface au sein d’un conteneur. | ||
+ | |||
+ | Autre souci auquel je me suis heurtée : le nom des interfaces virtuelles à ajouter au container : le noyau Linux ne permet pas d’avoir des noms d’interface de plus de 16 octets. Ainsi, je ne pouvais pas donner à mes paires veth le nom du conteneur, au risque de faire planter le programme. | ||
+ | |||
+ | J’ai ainsi trouvé une autre solution, de manière à avoir des interfaces réseaux qui soient uniques à un conteneur et qu’on puisse les identifier à l’aide du nom du conteneur. Ma solution consiste à prendre le nom du container, à le hasher en SHA1, puis à prendre les 3 premiers digits significatifs. | ||
+ | |||
+ | Par exemple prenons un container nommé “moncontainer”, le sha1 donne 412c637eebd95f6a5bb63292421b3107b07f076e. Maintenant supposons que l’on souhaite avoir deux interfaces réseau sur ce conteneur. Nous allons récupérer les trois premiers chiffres du hash : 412 puis on rajoute un 4 ème chiffre correspondant à l’indice de l’interface. La première interface du conteneur aura ainsi l’identifiant 4121 et la seconde l’identifiant 4122. | ||
+ | |||
+ | Ainsi, on crée deux vif, la première sera la suivante : vif4121 +--------------> eth0 | ||
+ | , et la seconde sera : vif4121 +----------------> eth1. | ||
+ | |||
+ | [https://stackoverflow.com/questions/24932172/what-length-can-a-network-interface-name-have] | ||
+ | |||
+ | De cette manière on s’assure qu’il est peu probable que deux conteneurs tombent sur le même identifiant de vif. | ||
+ | |||
+ | Une fois que la structure globale du projet était fonctionnelle, je me suis penchée sur les détails du cahier des charges : | ||
+ | |||
+ | *Une mise en place d’un programme permettant de stopper simplement le conteneur. | ||
+ | *Un autre script permettant de démarrer un conteneur déjà existant et “stoppé” . | ||
+ | *Un script pour exécuter des programmes directement sur le conteneur. | ||
+ | |||
+ | *Une meilleure gestion des “list-images/conteneurs/bridges” . En effet, j’ai rajouté des options afin de faciliter l’extraction d’informations des manifestes. J’ai rajouté des “:” avant chaque information. Ainsi, des “cut -d ‘:’ ” serait faisable pour extraire et gérer d’une meilleure façon les manifestes. | ||
+ | |||
+ | *Après cela, je voulais vérifier que je pouvais lancer Apache2 sur mon conteneur | ||
+ | |||
+ | *Un nettoyage “clean-up” du code et ajout de commentaires pour que le code soit lisible et compréhensible par n’importe quel lecteur. | ||
+ | |||
+ | =État de l'art= | ||
+ | |||
+ | == Définition == | ||
+ | |||
+ | |||
+ | Un conteneur Linux est un processus ou un ensemble de processus isolés du reste du système. Tous les fichiers nécessaires à leur exécution sont fournis par une image distincte, ce qui signifie que les conteneurs Linux sont portables et fonctionnent de la même manière dans les environnements de développement, de test et de production. Ainsi, ils sont bien plus rapides que les pipelines de développement qui s'appuient sur la réplication d'environnements de test traditionnels. | ||
+ | |||
+ | == L’utilité de la conteneurisation == | ||
+ | |||
+ | L'incidence de la virtualisation sur l'informatique moderne est profonde. Elle permet aux entreprises d'améliorer considérablement la rentabilité et la flexibilité des ressources informatiques. | ||
+ | Mais la virtualisation a un coût, notamment au niveau de l'hyperviseur et des systèmes d'exploitation invités, qui requièrent chacun de la mémoire et d’éventuelles licences coûteuses. Il en résulte une augmentation de la taille de chaque machine virtuelle, ce qui limite le nombre de VM qu'un serveur peut héberger. L'objectif de la conteneurisation vise à virtualiser les applications sans trop alourdir le système. | ||
+ | L'idée n'est pas nouvelle : depuis plusieurs années déjà, des systèmes d'exploitation tels que OpenVZ, FreeBSD, Solaris Containers et Linux-VServer prennent cette fonctionnalité en charge. Mais c'est la récente introduction de plateformes ouvertes, telle que Docker, qui a remis sous les feux de la rampe la conteneurisation et son potentiel en matière d'applications distribuées évolutives. | ||
+ | |||
+ | == Évolution == | ||
+ | |||
+ | |||
+ | La plupart des applications tournent sur des serveurs. Dans le passé, nous ne pouvions exécuter qu’une seule application par serveur. Le monde des systèmes ouverts de Windows et Linux n’avait pas les technologies pour exécuter en toute sécurité plusieurs applications sur un même serveur. | ||
+ | Bien sûr, chaque fois qu’une entreprise avait besoin d’une nouvelle application, le service informatique allait acheter un nouveau serveur. Et la plupart du temps personne ne connaissait parfaitement les exigences de performance de la nouvelle application ! Cela signifie que le service informatique devait deviner souvent à la volée le modèle et la taille des serveurs à acheter. | ||
+ | |||
+ | En conséquence, le service informatique a fait la seule chose à faire: acheter de gros serveurs rapides avec beaucoup de résilience. Après tout, la dernière chose que quelqu'un souhaitait, y compris l'entreprise, était des serveurs surchargés. Les serveurs surchargés peuvent entraîner une perte de clients et de revenus. Alors, l’IT généralement achetait des serveurs plus gros que ce qui était réellement nécessaire. Cela a entraîné un énorme nombre de serveurs fonctionnant entre 5 et 10% de leur capacité potentielle. Un gaspillage tragique du capital et des ressources de l'entreprise! | ||
+ | |||
+ | '''Hello VMware !''' | ||
+ | |||
+ | Pour tout cela, VMware, Inc. a fait un cadeau au monde: la machine virtuelle (VM). | ||
+ | Et presque du jour au lendemain, le monde s'est transformé en un endroit bien meilleur! Nous avons enfin acquis une technologie qui nous permettrait de gérer en toute sécurité plusieurs applications sur un seul serveur tout en étant isolées les unes des autres. | ||
+ | |||
+ | C'était une révolution, il n’était plus nécessaire de se procurer un tout nouveau | ||
+ | serveur surdimensionné à chaque fois que l'entreprise demandait une nouvelle application. | ||
+ | |||
+ | Tout à coup, tout est devenu bien plus dynamique, lancer une machine virtuelle pour tester une application ou la lancer en production est rapide, contrairement à l'achat de nouveaux serveurs. De plus, la supervision et le management de machines virtuelles est aisé, on peut facilement dupliquer, supprimer, relancer une machine virtuelle. Ce nouveau dynamisme au sein de l'informatique a été un réel tournant à 180 ° ! | ||
+ | |||
+ | '''VMwarts''' | ||
+ | |||
+ | Même si les machines virtuelles sont géniales, elles ne sont pas parfaites! | ||
+ | Le fait que chaque machine virtuelle nécessite son propre système d'exploitation dédié est un défaut majeur. Chaque OS consomme du CPU, de la RAM et du stockage qui pourraient autrement être utilisés pour alimenter davantage les applications. Chaque système d'exploitation a besoin de correctifs et de surveillance. Et dans certains cas, | ||
+ | chaque système d'exploitation nécessite une licence. | ||
+ | Le modèle de machine virtuelle présente également d'autres défis. Les machines virtuelles sont lentes à démarrer et à la portabilité pas terrible - migrer et déplacer des charges de travail de machine virtuelle entre hyperviseurs et les plates-formes cloud est plus difficile qu'il ne le faut. | ||
+ | |||
+ | '''Bonjour les conteneurs!''' | ||
+ | |||
+ | Pendant longtemps, les grands acteurs du Web tels que Google utilisaient des conteneurs. C'est une technologie pour remédier à ces faiblesses du modèle de VM. | ||
+ | Dans le modèle de conteneur, le conteneur est à peu près analogue à la VM. | ||
+ | La différence est que chaque conteneur n’a pas besoin d’un système d’exploitation complet. | ||
+ | En fait, tous les conteneurs d’un même hôte partagent un même système d’exploitation. Cela libère énormément de quantités de ressources système telles que le processeur, la RAM et le stockage. Il réduit également les coûts de licence potentiels et réduit les frais généraux liés aux correctifs de système d’exploitation et autres entretiens. | ||
+ | Les conteneurs sont également rapides à démarrer et ultra-portables. Déplacement de charges de travail de conteneur depuis votre ordinateur portable vers le cloud, puis vers des ordinateurs virtuels. | ||
+ | |||
+ | '''1979: Unix V7''' | ||
+ | [[Fichier:UNIX.png|200px|thumb|right|Unix]] | ||
+ | Au cours de l’histoire unix du développement des conteneurs d’Unix V7 en 1979, l’appel système chroot a été introduit, modifiant le répertoire racine d’un processus et de ses fils dans un nouvel emplacement du système de fichiers. Cette avancée a marqué le début de l'isolation des processus: la séparation de l'accès aux fichiers pour chaque processus. Chroot a été ajouté à BSD en 1982. | ||
+ | Un programme exécuté dans un tel environnement ne peut pas accéder aux fichiers et aux commandes situés en dehors de cette arborescence de répertoires environnementaux. Cet environnement modifié est appelé une ” prison” chroot. | ||
+ | |||
+ | '''2000: JBS FreeBSD''' | ||
+ | [[Fichier:freebsd-logo.jpg|100px|thumb|right|FreeBSD]] | ||
+ | Près de deux décennies plus tard, en 2000, un petit fournisseur d’hébergement en environnement partagé est arrivé dans l’historique upBSD des conteneurs avec les FreeBSD jails afin de séparer clairement ses services de ceux de ses clients pour des raisons de sécurité et de facilité d’administration. | ||
+ | Les jails FreeBSD permettent aux administrateurs de partitionner un système informatique FreeBSD en plusieurs systèmes indépendants plus petits, appelés jails, avec la possibilité d'attribuer une adresse IP à chaque système et une configuration. | ||
+ | |||
+ | '''2001: Linux VServer''' | ||
+ | [[Fichier:vserver_linux.png|100px|thumb|right|Linux VServer]] | ||
+ | Comme les jails FreeBSD, Linux VServer est un mécanisme jail qui permet de partitionner les ressources des conteneurs (systèmes de fichiers, adresses réseau, mémoire) sur un système informatique. Introduite en 2001, cette virtualisation de système d’exploitation est mise en œuvre en appliquant un correctif au noyau Linux. Des correctifs expérimentaux sont toujours disponibles, mais le dernier correctif stable a été publié en 2006. | ||
+ | |||
+ | '''2004: conteneurs Solaris''' | ||
+ | |||
+ | En 2004, la première version bêta publique de Solaris Containers, qui associe des contrôles des ressources système et une séparation des limites fournie par les zones, permettait de tirer parti de fonctionnalités telles que les instantanés et le clonage à partir de ZFS. | ||
+ | |||
+ | '''2005: Open VZ (Open Virtuzzo)''' | ||
+ | [[Fichier:OpenVZ-logo.png|200px|thumb|right|Open Virtuzzo]] | ||
+ | Il s'agit d'une technologie de virtualisation au niveau du système d'exploitation pour l'historique de conteneurs de Linux openVZ, qui utilise un noyau Linux corrigé pour la virtualisation, l'isolation, la gestion des ressources et les points de contrôle. Le code n'a pas été publié dans le noyau officiel de Linux. | ||
+ | |||
+ | '''2006: conteneurs de processus''' | ||
+ | |||
+ | Process Containers (lancé par Google en 2006) a été conçu pour limiter, comptabiliser et isoler l'utilisation des ressources (CPU, mémoire, E / S de disque, réseau) d'un ensemble de processus. Il a été renommé «Control Groups (cgroups)» un an plus tard et a finalement été fusionné avec le noyau Linux 2.6.24. | ||
+ | |||
+ | '''2008: LXC''' | ||
+ | [[Fichier:lxc.png|200px|thumb|right|Lxc]] | ||
+ | |||
+ | LXC (LinuX Containers) a été la première et la plus complète implémentation du gestionnaire de conteneurs Linux. Il a été implémenté en 2008 à l'aide des “namespace ” cgroups de Linux et fonctionne sur un seul noyau Linux sans nécessiter de correctifs. | ||
+ | |||
+ | Le noyau de Linux 2.6.24 intègre une prise en charge fondamentale de la conteneurisation pour assurer une virtualisation au niveau du système d'exploitation et permettre à un même hôte d'exécuter plusieurs instances Linux isolées, baptisées « conteneurs Linux », ou LXC (LinuX Containers). | ||
+ | LXC repose sur la notion de groupes de contrôle Linux, les cgroups. Ici, chaque groupe de contrôle offre aux applications une isolation totale des ressources (notamment processeur, mémoire et accès E/S), et ce sans recourir à des machines virtuelles à part entière. | ||
+ | Les conteneurs Linux proposent également une isolation complète de leurs espaces de noms. Les fonctions telles que les systèmes de fichiers, les ID réseau et les ID utilisateur, ainsi que tout autre élément généralement associé aux systèmes d'exploitation, peuvent donc être considérés comme « uniques » du point de vue de chaque conteneur. | ||
+ | |||
+ | '''2011: Warden''' | ||
+ | |||
+ | CloudFoundry a commencé Warden en 2011, en utilisant LXC dans le | ||
+ | début et plus tard en le remplaçant par owncloud. Warden peut isoler des environnements sur n’importe quel système d’exploitation, s’exécutant en tant que démon et fournissant une API pour la gestion des conteneurs. Il a développé un modèle client-serveur pour gérer une collection de conteneurs sur plusieurs hôtes, et Warden inclut un service permettant de gérer les groupes de contrôle, les name space et le cycle de vie des processus. | ||
+ | |||
+ | '''2013: LMCTFY''' | ||
+ | |||
+ | LMCTFY a démarré en 2013 en tant que version à source ouverte de la pile de conteneurs de Google, fournissant des conteneurs d'applications Linux. Les applications peuvent être configurées en «conteneur», en créant et en gérant leurs propres sous-conteneurs. Le déploiement actif dans LMCTFY a été arrêté en 2015 après que Google ait commencé à contribuer à libcontainer, qui fait désormais partie des concepts LMCTFY de base, qui fait maintenant partie de la Open Container Foundation. | ||
+ | |||
+ | '''2013: Docker''' | ||
+ | [[Fichier:docker_logo.jpg|200px|thumb|right|Docker]] | ||
+ | |||
+ | Lorsque Docker est apparu en 2013, la popularité des conteneurs a explosé. Ce n’est pas une coïncidence: l’histoire croissante des conteneurs de docker a toujours été liée à l’utilisation de docker et des conteneurs. | ||
+ | |||
+ | Docker a explosé sur scène en 2013, et cela a causé de l'excitation dans les cercles informatiques. La technologie de conteneur d'application fournie par Docker promet de changer la façon dont les opérations informatiques sont réalisées de la même manière que la technologie de virtualisation quelques années auparavant. | ||
+ | |||
+ | Autres exemples, Docker propose des outils de génération automatisée de build. Ces outils aident les développeurs à passer plus facilement d'un code source à des applications conteneurisées, ou à travailler avec des outils de "configuration as a code", tels que Chef, Ansible, Puppet et autres, afin d'automatiser ou de rationaliser le processus de build. | ||
+ | |||
+ | La gestion des versions permet aux développeurs de suivre l'évolution des versions des conteneurs, de comprendre les différences, voire de revenir à des versions antérieures le cas échéant. Et sachant que tout conteneur peut servir d'image de base à un autre, il est d'autant plus facile de réutiliser des composants aisément partageables via un registre public (ou privé). | ||
+ | |||
+ | Tout comme Warden, Docker a également utilisé LXC à ses débuts et a par la suite remplacé ce gestionnaire de conteneurs par sa propre bibliothèque, libcontainer (plus tard apellée containerD). Mais il ne fait aucun doute que Docker s’est séparé du pack en offrant un écosystème complet pour la gestion des conteneurs. | ||
+ | |||
+ | |||
+ | '''2017: Les outils de conteneur deviennent matures''' | ||
+ | |||
+ | Des centaines d'outils ont été développés pour faciliter la gestion des conteneurs. Alors que ces types d'outils existent depuis des années, 2017 est l'année où beaucoup d'entre eux ont gagné leurs galons. Il suffit de regarder Kubernetes; Depuis son adoption dans la Cloud Native Computing Foundation (CNCF) en 2016, VMWare, Azure, AWS et même Docker ont annoncé leur soutien, en plus de leurs infrastructures. | ||
+ | |||
+ | Alors que le marché continue de croître, certains outils ont permis de définir certaines fonctions de la communauté des conteneurs. Ceph et REX-Ray établissent des normes pour le stockage de conteneurs, tandis que Flannel connecte des millions de conteneurs dans des centres de données. Et dans CI / CD, Jenkins change complètement la façon dont nous construisons et déployons des applications. | ||
+ | ''' | ||
+ | Adoption de rkt et Containerd par la CNCF''' | ||
+ | |||
+ | L’écosystème de conteneurs est unique dans la mesure où il repose sur un effort et un engagement de la communauté vis-à-vis de projets open source. Le don de Docker du projet Containerd à la CNCF en 2017 est emblématique de ce concept, ainsi que l'adoption de CNCF du conteneur KRT (prononcé « fusée ») l'exécution dans le même temps. Cela a conduit à une plus grande collaboration entre les projets, à plus de choix pour les utilisateurs et à une communauté centrée sur l'amélioration de l'écosystème des conteneurs. | ||
+ | |||
+ | '''Kubernetes grandit''' | ||
+ | |||
+ | En 2017, le projet open-source a montré de grandes avancées pour devenir un k8s logo.pngtechnology plus mature. Kubernetes prend en charge des classes d'applications de plus en plus complexes, permettant ainsi la transition de l'entreprise vers le cloud hybride et les microservices. A DockerCon à Copenhague, Docker a annoncé qu'ils soutiendront le conteneur Kubernetes orchestrateur et Azure et AWS est tombé en ligne avec AKS (Azure Services Kubernetes) et EKS, un service Kubernetes pour rivaliser avec ECS propriétaire. Il s’agissait également du premier projet adopté par le CNCF et commandait une liste croissante de fournisseurs de services d’intégration de systèmes tiers. Kubernetes semble avoir un avenir prometteur en tant que plate-forme d'orchestration de facto. | ||
+ | |||
+ | '''Docker : un LXC augmenté''' | ||
+ | |||
+ | Les plateformes de conteneurisation d'applications, telles que Docker, ne remplacent pas les conteneurs Linux. L'idée consiste à utiliser LXC comme base, puis à ajouter des capacités de niveau supérieur. | ||
+ | Par exemple, une plate-forme comme Docker autorise la portabilité entre machines (qui exécutent aussi Docker) et permet ainsi à une application et à ses composants d'exister en tant qu'objet mobile unique. LXC seul permet la mobilité, mais la build est liée à la configuration du système. Donc la déplacer sur une autre machine peut introduire des différences susceptibles d'empêcher le conteneur de l'application de s'exécuter à l'identique (voire de s'exécuter tout court). | ||
+ | |||
+ | Mais le recours à LXC était un problème depuis le début. | ||
+ | Tout d’abord, LXC est spécifique à Linux. C’était un problème pour un projet qui avait aspirations à être multi-plateforme. | ||
+ | Deuxièmement, être dépendant d'un outil externe pour quelque chose d'aussi essentiel au projet était un risque énorme qui pourrait entraver le développement. | ||
+ | |||
+ | En conséquence, Docker. Inc. a développé son propre outil appelé libcontainer en tant que | ||
+ | remplacement pour LXC. Le but de libcontainer était d'être une plate-forme agnostique outil permettant à Docker d’avoir accès à la plate-forme de base des conteneurs -blocs qui existent dans le système d'exploitation. | ||
+ | |||
+ | Libcontainer a remplacé LXC en tant que pilote d'exécution par défaut dans Docker 0.9. | ||
+ | |||
+ | |||
+ | |||
+ | Aujourd’hui, Dave Bartoletti, analyste chez Forrester, pense que seulement 10% des entreprises utilisent actuellement des conteneurs en production, mais près d’un tiers sont en phase de test. Docker a généré 762 millions de dollars de revenus en 2016. Les conteneurs ont transformé le monde de l’informatique car ils utilisent des systèmes d’exploitation partagés. Cette technologie permet à un data center ou à un fournisseur Cloud d’économiser des dizaines de millions de dollars par an, mais tout dépend bien sûr des risques qu’il serait prêt à prendre. | ||
+ | Cependant, quelques préoccupations existent concernant l’assurance qu’auront les développeurs d’innover librement en utilisant des conteneurs. Les développeurs devraient pouvoir choisir les outils et les frameworks qu’ils souhaitent utiliser sans avoir à demander systématiquement la permission. L’utilisation d’un conteneur pourrait en effet étouffer la créativité d’un développeur… | ||
+ | Le choix incombe, alors à l’entreprise d'investir ou non dans les conteneurs. Avec les avantages et les inconvénients qui se contrebalancent, tout peut se résumer au goût du risque. | ||
+ | |||
+ | == Différence avec la virtualisation == | ||
+ | [[Fichier:machinevirtuelle.png|200px|thumb|right|VM]] | ||
+ | [[Fichier:conteneur.png|200px|thumb|left|Conteneur]] | ||
+ | Bien sûr, la conteneurisation a de nombreux avantages par rapport à la virtualisation mais on ne peut pas affirmer que cette technologie est 100% parfaite, elle a aussi son lot d’inconvénients. | ||
+ | Ces deux schémas ci dessus, d’un environnement de virtualisation et d’un environnement de conteneurisation permettent de bien identifier les différences entre ces deux technologies. | ||
+ | |||
+ | Tout d’abord, les conteneurs partagent un seul et unique système d’exploitation, de ce fait, l’échange de données entre les conteneurs est plus simple et plus rapides que pour les VM. De plus comme chaque conteneur de ne contient pas de système d’exploitation propre à lui, les conteneurs sont donc réduits et prennent moins de place et moins de ressource Serveur (environ 10 fois plus petit qu’un VM). Le temps de création et de suppression d’un conteneur est par la même occasion réduit. | ||
+ | Les conteneurs facilitent l’évolution technique, par exemple si dans un environnement de VM, l’on souhaite faire évoluer les OS de plusieurs VM, il faut le faire manuellement sur chaque Machines. Ce problème n’est pas présent pour la conteneurisation car toute l’infrastructure repose sur un seul système d’exploitation. | ||
+ | Mais la conteneurisation peut avoir quelques inconvénients, comme tous les conteneurs ne reposent que sur un seul système d’exploitation, la diversification des systèmes d’exploitation n’est pas possible avec la conteneurisation, « ou est plus compliquée à mettre en place ». Les conteneurs sont isolés pour assurer la sécurité et empêcher les malwares de se transmettre entre les conteneurs, mais il est évident que les machines virtuelles seront toujours plus isolées que les conteneurs. Même si les conteneurs ont un grand nombre d’avantages, leur apparition ne sonne pas la fin de la virtualisation. Aujourd’hui, les machines virtuelles sont intégrées dans de nombreuses entreprises comportant des réseaux de grande taille. Pour utiliser entièrement la technologie de conteneurisation, ces entreprises devraient remanier tout leur système informatique ce qui est impensable. Mais de nouvelles entreprises ont vu le potentiel de la conteneurisation et on donc créer leur système informatique en fonction. | ||
+ | |||
+ | == Les images == | ||
+ | [[Fichier:images_docker.png]] | ||
+ | |||
+ | La figure présente une vue de haut niveau de la relation entre les images et | ||
+ | conteneurs. Nous utilisons les commandes : "docker container run" et "docker service create" pour démarrer un ou plusieurs conteneurs à partir d'une seule image. | ||
+ | Cependant, une fois qu’on a démarré un conteneur à partir d'une image, les deux constructions deviennent dépendantes les unes des autres et on ne peut pas supprimer l'image avant que le dernier conteneur l'utilisant ait été arrêté et détruit. Tenter de supprimer une image sans s'arrêter et la destruction de tous les conteneurs l’utilisant entraînera l’erreur suivante : | ||
+ | |||
+ | $ docker image rm <image-name> | ||
+ | Error response from daemon: conflict: unable to remove repository reference \ | ||
+ | "<image-name>" (must force) - container <container-id> is using its referenc\ | ||
+ | ed image <image-id> | ||
+ | |||
+ | |||
+ | '''Les images sont généralement petites''' | ||
+ | |||
+ | Le but d'un conteneur est d'exécuter une application ou un service. Cela signifie | ||
+ | que l'image à partir de laquelle le conteneur est créé doit contenir tous les systèmes d'exploitation et tous les fichiers requis pour exécuter l'application / service. | ||
+ | Cependant, les conteneurs sont connus pour être rapide et léger. Cela veut dire que les images à partir desquelles ils sont construits sont généralement petites et dépouillées de toutes les parties non essentielles. | ||
+ | Par exemple, les images Docker ne sont pas livrées avec 6 shells différents. | ||
+ | Elles sont généralement livrées avec un seul shell minimaliste, ou aucun shell du tout. Elles ne contiennent pas non plus de kernel - tous les conteneurs s'exécutant sur le Docker hôte partagent un accès à au kernel de l’hôte du kernel. Pour ces raisons, on dit parfois que les images ne contiennent pas suffisamment de système d'exploitation (généralement elles contiennent que des fichiers et des objets du système de fichiers liés au système d'exploitation). | ||
+ | |||
+ | L’image officielle d’Alpine Linux Docker fait environ 4 Mo de taille et représente un bon exemple de la petite taille des images Docker. Cependant, un exemple plus typique pourrait être quelque chose comme l’image officielle Ubuntu Docker qui fait actuellement environ 120 Mo. Ceux-ci sont clairement dépouillés de la plupart des parties non essentielles ! | ||
+ | Les images Windows ont tendance à être plus volumineuses que les images Linux, à cause de la façon dont le système d'exploitation Windows fonctionne. Par exemple, la dernière version de .NET | ||
+ | image (microsoft / dotnet:latest) est supérieure à 2 Go lorsqu’elle est extrait sans compression. | ||
+ | L'image de Windows Server 2016 Nano Server dépasse légèrement 1 Go lorsqu'elle est tirée et non compressé. | ||
+ | |||
+ | Un hôte Docker correctement installé n'a aucune image dans son référentiel local. | ||
+ | Le référentiel d'images local sur un hôte Docker basé sur Linux est généralement situé à l'emplacement suivant: | ||
+ | / var / lib / docker / <storage-driver>. | ||
+ | On peut vérifier si notre hôte Docker contient des images dans son référentiel local avec la commande suivante: | ||
+ | |||
+ | $ docker image ls | ||
+ | REPOSITORY TAG IMAGE ID CREATED SIZE | ||
+ | |||
+ | |||
+ | |||
+ | '''Nom de l'image''' | ||
+ | |||
+ | Dans le cadre de chaque commande, nous devions spécifier quelle image extraire. Pour ce faire, nous avons besoin d’un peu de contexte sur la manière de stocker les images. | ||
+ | Registres d'images : | ||
+ | Les images Docker sont stockées en ligne dans des registres d'images. Le registre le plus courant est Docker Hub [https://hub.docker.com]. Il existe d'autres registres, y compris des registres tiers . Cependant, le client Docker est avisé et utilise par défaut Docker Hub. | ||
+ | |||
+ | Les registres d'images contiennent plusieurs référentiels d'images. À leur tour, les référentiels d’images peuvent contenir plusieurs images. Cela pourrait être un peu déroutant, alors la figure montre une image d'un registre d'images contenant 3 référentiels, et chaque référentiel contient une ou plusieurs images. | ||
+ | |||
+ | [[Fichier:registry_docker.png]] | ||
+ | |||
+ | '''Dépôts officiels et non officiels''' | ||
+ | |||
+ | Docker Hub a également le concept de référentiels officiels et non officiels des dépôts. | ||
+ | |||
+ | Comme le nom l'indique, les référentiels officiels contiennent des images qui ont été vérifiées par Docker, Inc. Cela signifie qu'ils doivent contenir un code à jour et de haute qualité, sécurisé, bien documenté et conforme aux meilleures pratiques. | ||
+ | Les référentiels non officiels sont tout le contraire. | ||
+ | |||
+ | '''Nom et marquage des images''' | ||
+ | |||
+ | Le format pour docker : image pull | ||
+ | quand on travaille avec une image d'un dépôt officiel, c'est: | ||
+ | |||
+ | docker image pull <repository>: <tag> | ||
+ | |||
+ | Dans les exemples Linux précédents, nous avons téléchargé des images Alpine et Ubuntu. | ||
+ | avec les deux commandes suivantes: | ||
+ | |||
+ | docker image pull alpine:latest and docker image pull ubuntu:latest | ||
+ | |||
+ | Les commandes suivantes montrent comment téléchargé différentes images d’un référentiel officiel: | ||
+ | |||
+ | $ docker image pull mongo:3.3.11 | ||
+ | //This will pull the image tagged as `3.3.11` | ||
+ | //from the official `mongo` repository. | ||
+ | $ docker image pull redis:latest | ||
+ | //This will pull the image tagged as `latest` | ||
+ | //from the official `redis` repository. | ||
+ | $ docker image pull alpine | ||
+ | //This will pull the image tagged as `latest` | ||
+ | //from the official `alpine` repository. | ||
+ | |||
+ | Quelques points à noter sur les commandes ci-dessus : | ||
+ | Tout d’abord, si on ne spécifie pas de balise d’image après le nom du référentiel, Docker suppose que nous nous référons à la dernière image. | ||
+ | |||
+ | Deuxièmement, la dernière balise n’a aucun pouvoir magique! Juste parce qu'une image est étiquetée comme dernière ne garantit pas que c’est l’image est la plus récente dans un dépôt! | ||
+ | |||
+ | ''' Images et couches ''' | ||
+ | |||
+ | Une image Docker est juste un groupe de couches en lecture seule faiblement connectés. | ||
+ | Docker s’occupe d’empiler ces couches et de les représenter comme un seul objet unifié. | ||
+ | Il y a plusieurs façons de voir et d'inspecter les couches qui composent une image: | ||
+ | |||
+ | |||
+ | [[Fichier:images_layer_docker.png]] | ||
+ | |||
+ | Toutes les images Docker commencent par une couche de base, et au fur et à mesure que des modifications sont apportées, le contenu est ajouté, les nouvelles couches sont ajoutées en haut. | ||
+ | |||
+ | '''Les hachages d'image (digests)''' | ||
+ | |||
+ | |||
+ | L’image n’est en réalité qu’un objet de configuration répertoriant les couches et ainsi que certaines métadonnées (telle que son nom par exemple). | ||
+ | Les couches constituant une image sont totalement indépendantes et n’ont aucune notion de faire partie d'une image collective. | ||
+ | |||
+ | Chaque image est identifiée par un identifiant de cryptage qui est un hachage de l'objet config. Chaque couche est identifiée par un identifiant de cryptage qui est un hachage du contenu qu’elle contient. | ||
+ | |||
+ | Cela signifie que changer le contenu de l'image, ou de l'une de ses couches, | ||
+ | provoquera le changement des cryptages associés. En conséquence, les images et les couches sont immuables. | ||
+ | |||
+ | Lorsqu’on push et pull les images, nous compressons leurs couches pour économiser la bande passante. | ||
+ | Mais la compression d’une couche change son contenu ! Cela signifie que le contenu du hachage ne correspond plus après l'opération de push et pull ! Ceci est évidemment un problème. | ||
+ | Par exemple, lorsqu’on place une couche d’image sur Docker Hub, Docker Hub | ||
+ | tente de vérifier que l'image est arrivée sans être altérée en route. | ||
+ | Pour ce faire, il exécute un hachage sur la couche et vérifie s'il correspond au | ||
+ | hash qui a été envoyé avec la couche. Parce que la couche a été compressée (modifiée) la vérification du hachage échouera. | ||
+ | |||
+ | Pour résoudre ce problème, chaque couche reçoit également un élément appelé hachage de distribution. C’est un hachage de la version compressée de la couche. Quand une couche est push et pull du registre, son hash de distribution est inclus, et c'est ce qui est utilisé pour vérifier que la couche est arrivée sans être altérée. | ||
+ | Ce modèle de stockage à contenu adressable améliore considérablement la sécurité en nous offrant un moyen d’imprimer et de superposer des données après des opérations de push et pull. Cela évite aussi les collisions d'identifiants pouvant survenir si les identifiants d'image et de couche étaient générés de manière aléatoire. | ||
+ | |||
+ | |||
+ | '''Images multi-architecturales''' | ||
+ | |||
+ | Docker prend désormais en charge les images multi-plateformes et multi-architectures. | ||
+ | Cela signifie qu’un référentiel d’images et une balise doivent avoir une image pour Linux sur x64 et Linux sur PowerPC, etc. | ||
+ | Pour ce faire, l’API de registre prend en charge un fat manifest ainsi qu’une image | ||
+ | manifest. Les fat manifests listent les architectures supportées par une image particulière, tandis que l'image manifest, liste les couches qui composent une image particulière. | ||
+ | |||
+ | Supposons qu’on utilise Docker sous Linux x64. Quand on pull une image depuis | ||
+ | Docker hub, notre client Docker adresse les demandes d’API pertinentes à Docker API tournant sur Docker Hub. | ||
+ | Si un fat manifest existe pour cette image, il sera analysé pour voir s’il existe une entrée pour Linux sur x64. Si elle existe, l'image manifest pour cette image est récupérée et analysée pour les couches réelles qui composent l'image. Les couches sont identifiées par leurs identifiants de cryptage et sont extraites du Registre “blob”. | ||
+ | |||
+ | '''Supprimer des images''' | ||
+ | |||
+ | Lorsqu’on n’a plus besoin d'une image, on peut la supprimer de notre hôte Docker | ||
+ | avec la commande : | ||
+ | docker image rm. rm | ||
+ | Supprimer les images “pull” dans les étapes précédentes avec la commande: | ||
+ | docker image rm | ||
+ | |||
+ | Si l'image qu’on essaye de supprimer est utilisée par un conteneur en cours d'exécution, on ne pourra pas la supprimer. Il faudra Arrêter et supprimer tous les conteneurs avant d'essayer la suppression à nouveau. | ||
+ | Un raccourci pratique pour nettoyer un système et supprimer toutes les images sur un Docker hôte est d’exécuter la commande docker image rm et lui transmettre une liste de tous les ID d'image sur le système en appelant l'image de menu fixe ls avec l'indicateur -q. | ||
+ | |||
+ | docker image rm $(docker image ls -q) -f | ||
+ | |||
+ | =Création des conteneurs "à la main" = | ||
+ | '''Création des fichiers disque des conteneurs''' | ||
+ | |||
+ | En un premier temps, je crée un fichier de 10 Gio = 10 240 * 1 Mio et je spécifie que tous les octets sont égaux à 0. Et ce, grâce à la commande : | ||
+ | |||
+ | dd if=/dev/zero of=/home/pifou/hmalti bs=1024k count=10240 | ||
+ | |||
+ | '''Création du système de fichiers''' | ||
+ | |||
+ | Ici, j’associe un système de fichiers du type “ext4” à mon fichier. (Je le formate à ce “format”) : | ||
+ | |||
+ | mkfs.ext4 /home/pifou/hmalti/containter_hmalti | ||
+ | |||
+ | '''Montage du système de fichiers''' | ||
+ | J’ai créé le répertoire qui contiendra les rootfs montés : | ||
+ | |||
+ | mount -t ext4 -o loop /home/pifou/hmalti/hmalti /mnt/container_hmalti | ||
+ | ''' | ||
+ | Déploiement de l'arborescence Debian''' | ||
+ | |||
+ | Il faut exporter le proxy de l'école dans la variable d’environnement correspondante pour pouvoir télécharger les paquetages. | ||
+ | |||
+ | export http_proxy=http://proxy.polytech-lille.fr:3128 | ||
+ | |||
+ | Ensuite j’utilise l'outil debootstrap dans /mnt/container_hmalti pour créer une arborescence Debian de base. | ||
+ | |||
+ | debootstrap --include=apache2,vim,nano stable /mnt/container_hmalti | ||
+ | |||
+ | Puis il faut renseigner le fichier fstab du container_hmalti : | ||
+ | |||
+ | echo "proc /proc proc defaults 0 0" >> /mnt/container_hmalti/etc/fstab | ||
+ | |||
+ | '''Pour démonter le fichier :''' | ||
+ | |||
+ | umount /mnt/container_hmalti | ||
+ | |||
+ | '''Pour le remonter :''' | ||
+ | |||
+ | mount -t ext4 -o loop /home/pifou/hmalti/hmalti /mnt/container_hmalti | ||
+ | |||
+ | Afin de vérifier que le fichier est bien créé | ||
+ | |||
+ | ls /mnt/container_hmalti | ||
+ | |||
+ | '''Isolation du conteneur et création :''' | ||
+ | |||
+ | unshare -p -f -m -n -u chroot /mnt/container_hmalti /bin/sh -c "mount /proc ; /bin/bash" | ||
+ | |||
+ | Et à ce moment là, on peut vérifier que notre conteneur est bien isolé on peut lister les processus qui tournent dessus : | ||
+ | [[Fichier:unshare.png]] | ||
+ | |||
+ | |||
+ | ps aux (on ne trouve que 3 processus, ce qui est logique ) | ||
+ | |||
+ | De même pour ce qui est de l’adresse réseau de notre conteneur : | ||
+ | |||
+ | ip l (Dans le conteneur nous ne voyons pas les interfaces réseau de l'hôte car nous ne sommes pas dans le même espace de nom) | ||
+ | |||
+ | ls (on retrouve le contenu du conteneur et non celui de l’hôte) | ||
+ | |||
+ | |||
+ | |||
+ | == Imposer des limitations aux conteneurs == | ||
+ | |||
+ | Ici, le pid que l’on met est celui du processus du conteneur dans le namespace de l’hôte ( par exemple : si on fait un ps aux sur le conteneur et on voit : | ||
+ | |||
+ | [[Fichier:container_ps_aux.png]] | ||
+ | |||
+ | Ça veut dire que le numéro du PID du processus fils d’unshare est : 3 dans le namespace du conteneur, il ne sera pas le même vis à vis du hôte, pour le voir on fait un petit ps aux et on voit : | ||
+ | |||
+ | [[Fichier:host_ps_aux.png]] | ||
+ | |||
+ | Le PID du processus fils du unshare porte le numéro : 6410 | ||
+ | Ce sera donc ce dernier PID que l’on mettra dans “tasks” | ||
+ | |||
+ | |||
+ | '''Restriction sur l’utilisation mémoire :''' | ||
+ | |||
+ | Le bout de code ci-dessous permet d'allouer 10K de mémoire toutes les secondes en boucle | ||
+ | |||
+ | mkdir /sys/fs/cgroup/memory/model1 | ||
+ | echo 2048000 > /sys/fs/cgroup/memory/model1/memory.limit_in_bytes | ||
+ | echo 2048000 > /sys/fs/cgroup/memory/model1/memory.memsw.limit_in_bytes | ||
+ | echo 0 > /sys/fs/cgroup/memory/model1/memory.swappiness | ||
+ | echo $pid > /sys/fs/cgroup/memory/test/tasks | ||
+ | |||
+ | - une boucle pour monitorer la mémoire : | ||
+ | |||
+ | while true ; do | ||
+ | cat memory.stat | egrep 'total_cache |total_rss |total_swap |total_.*_anon' | ||
+ | cat memory.usage_in_bytes | ||
+ | echo '---' | ||
+ | sleep 1 | ||
+ | done | ||
+ | |||
+ | On s'aperçoit comme indiqué dans les documentations que memory.usage_in_bytes est approximatif, la vraie valeur étant : total_cache+total_rss+total_swap | ||
+ | - le programme /tmp/alloc s'arrête sur 1945600 octets alloués | ||
+ | - la boucle de monitoring s'arrête sur : | ||
+ | total_cache 24576 | ||
+ | total_rss 1970176 | ||
+ | total_swap 0 | ||
+ | total_inactive_anon 0 | ||
+ | total_active_anon 1970176 | ||
+ | 2048000 | ||
+ | --- | ||
+ | - le noyau indique : | ||
+ | |||
+ | [90844.999641] Task in /test killed as a result of limit of /test | ||
+ | [90844.999647] memory: usage 2000kB, limit 2000kB, failcnt 0 | ||
+ | [90844.999649] memory+swap: usage 2000kB, limit 2000kB, failcnt 2445774 | ||
+ | [90844.999650] kmem: usage 36kB, limit 9007199254740988kB, failcnt 0 | ||
+ | [90844.999651] Memory cgroup stats for /test: cache:0KB rss:1964KB | ||
+ | rss_huge:0KB mapped_file:0KB dirty:0KB writeback:0KB swap:0KB | ||
+ | inactive_anon:1024KB active_anon:940KB inactive_file:0KB | ||
+ | active_file:0KB unevictable:0KB | ||
+ | |||
+ | |||
+ | [[Fichier:Test memory.png]] | ||
+ | |||
+ | = Explication de la structure du code = | ||
+ | |||
+ | |||
+ | Le plus difficile pour moi fut de trouver la bonne structure et l’architecture qui répondait correctement au cahier des charges. | ||
+ | L’erreur que j’ai commise est de ne pas avoir fait un schéma papier et ne pas avoir pris le temps de structurer et généraliser mon script pour qu’il s’adapte à tout utilisateur. Le plus important est d’avoir compris l’utilité et l’importance de cette technique dans un projet et d’en tirer une leçon. | ||
+ | |||
+ | Il y a 20 scripts, j’ai choisis de les diviser comme ce qui suit : | ||
+ | |||
+ | '''help''' : indique toutes les fonctionnalités, les commandes et flags associés | ||
+ | baleine help | ||
+ | '''create image :''' La commande pour simplement créer une image est : | ||
+ | < image create -i “nom_image” -s “taille_image” -r “chemin_image” -p “proxy” > | ||
+ | car on pourrait vouloir créer une image mais ne pas vouloir lancer un conteneur de dessus, d’où la séparation de la création du conteneur et de l’image.. | ||
+ | Il faut noter que dans ce script, il crée l’image dans le PATH_BALEINE : /var/lib/baleine/images. | ||
+ | |||
+ | Cependant, la copie de l’image sur laquelle le conteneur est lancé se fait dans le PATH : /var/lib/baleine/containers/nom_container | ||
+ | Il crée un manifest contenant le nom de l’image, sa taille, son chemin. | ||
+ | Ainsi, on établit un ordre logique dans la structure. Et l’utilisation sera généralisée à tout utilisateur qui se sert de l’application. | ||
+ | Ce script crée aussi un mannifest* (voir la définition en annexe) de l’image, contenant : son nom, sa taille et son chemin. | ||
+ | create bridge : On crée un bridge grâce à la commande < bridge create -b “nom_bridge” -a “ADDR_IPV4” > | ||
+ | |||
+ | '''create container :''' ce script permet de lancer une instance et crée des interfaces réseau virtuelles relié au commutateur/bridge cité plus tôt. Et ce avec la commande : | ||
+ | < container create -i “nom_image” -c “nom_container” -b “nom_bridge” -a “ADDR_IPV4” -r “repertoire” -p ”programme”> | ||
+ | Mais avant ceci, il copie l’image dans : /var/lib/baleine/containers/nom_container | ||
+ | Puis il la monte dans : /mnt/baleine/nom_container | ||
+ | De même, il crée un manifeste contenant le nom du conteneur, l’image depuis laquelle il a été monté, le nom de son bridge, son PID ainsi que son "starting_time" ( pour savoir depuis quand le conteneur tourne ). | ||
+ | |||
+ | '''remove image :''' Ce script permet de détruire complètement une image et supprimer son manifest avec la commande : | ||
+ | < image remove “NAME_IMAGE_TO_REMOVE”> | ||
+ | |||
+ | '''remove container :''' ce script éradique l’instance et détruit son image disque et de ses caractéristiques, avec : | ||
+ | < container remove “NAME_CONTAINER_TO_REMOVE”> | ||
+ | '''remove bridge:''' Ici, on supprime simplement un bridge spécifique ( en entrant le nom de ce dernier en paramètre ). | ||
+ | < bridge remove “NAME_BRIDGE_TO_REMOVE”> | ||
+ | '''stop container :''' arrête une instance, et détruit naturellement ses ressources réseau. Mais son image disque et ses caractéristiques restent conservées dans un état stable pour qu’il puisse faire se lancer à nouveau depuis. | ||
+ | < container stop -c “NAME_CONTAINER_TO_STOP”> | ||
+ | '''list images: '''liste toutes les images qui ont été créées ( nom, taile , chemin ) , ceci se fait à partir du manifest. | ||
+ | < image list > | ||
+ | '''list containers :''' de même, ici on liste tous les conteneurs qui tournent (nom du conteneur , le nom de l’image qui l’a lancé, PID , temps d’exécution ) | ||
+ | < container list > | ||
+ | '''list bridges:''' idem, on liste tous les bridges | ||
+ | < bridge list > | ||
+ | '''up/down bridge : ''' ici nous pouvons mettre en up ou down un bridge | ||
+ | < bridge -b “NAME_BRIDGE” up > | ||
+ | < bridge -b “NAME_BRIDGE” down > | ||
+ | |||
+ | == Options ajoutées == | ||
+ | |||
+ | *Pour faciliter l’utilisation, un help équivalent à un manuel a été mis en place, que l’on peut visualiser en exécutant la commande suivante : baleine.sh help | ||
+ | *Une option a été mise en place concernant les interfaces réseau lors de la création du conteneur. En effet, si l’on souhaite créer plusieurs interfaces sur un même conteneur avec plusieurs bridges, on arrive à gérer ceci, grâce à la création d’une arraylist en shell ( découverte très intéressante et surprenante ). | ||
+ | *J’ai rajouté une option qui permet à l’utilisateur de rentrer ses arguments dans n’importe quel ordre du moment où il met les drapeaux (Flags) correspondants devant. (L’utilitaire Help) l’aidera à les connaître. | ||
+ | *De même, il m’a été imposé de ne pas faire une gestion sophistiquée des images à base de système de fichiers à couche. Pour ce, j’utilise le système de fichier ext4, qui ne gère pas les couches | ||
+ | |||
+ | == Amélioration possibles == | ||
+ | |||
+ | *J’avais essayé pendant un moment de gérer des versions d'images en leur accordant un nombre aléatoire pour les différencier, mais ça m’a paru compliqué à gérer par la suite. Cependant, ça pourrait être une idée d’amélioration du script et de gestion de bordure et généralisation. | ||
+ | *Gérer une architecture différente de Debian (une architecture Redhat par exemple). | ||
+ | |||
+ | = Fonctionnement de l'application = | ||
+ | |||
+ | == Images == | ||
+ | |||
+ | Creation d'une image : | ||
+ | |||
+ | [[Fichier:Create image.png|600px]] | ||
+ | |||
+ | Suppression d'une image : | ||
+ | |||
+ | [[Fichier:Image delete.png]] | ||
+ | |||
+ | Liste des images : | ||
+ | |||
+ | [[Fichier:Image list.png]] | ||
+ | |||
+ | == Bridges == | ||
+ | |||
+ | Creation d'un bridge et liste : | ||
+ | |||
+ | [[Fichier:bridge_create_ls.png]] | ||
+ | |||
+ | Suppression d'un bridge : | ||
+ | |||
+ | [[Fichier:bridge_remove.png]] | ||
+ | |||
+ | == Conteneurs == | ||
+ | |||
+ | Creation d'un conteneur : | ||
+ | |||
+ | [[Fichier:Create container.png]] | ||
+ | |||
+ | Suppression d'un conteneur : | ||
+ | |||
+ | [[Fichier:Container remove.png]] | ||
+ | |||
+ | Liste des conteneurs en cours d'execution : | ||
+ | |||
+ | [[Fichier:Container list.png]] | ||
+ | |||
+ | = Mandataire inverse = | ||
+ | |||
+ | Pour réaliser l'architecture avec un mandataire inverse apache2, on va mettre en place deux bridges, un sera considéré comme public, et l'autre comme étant privé. Le mandataire inverse sera présent sur le bridge considéré comme public, tandis que l'autre serveur apache2 (qui fera office de serveur HTTP) sera présent sur le bridge privé. | ||
+ | |||
+ | Le mandataire inverse sera également présent sur le bridge privé, ainsi il aura deux adresses ip. Une publique, et une autre privée. Lorsque l'on accèdera au mandataire inverse via son adresse ip publique, il va transmettre la requête au serveur HTTP non accessible. | ||
+ | |||
+ | Réalisons cela grâce à nos conteneurs, tout d'abord nous allons créer deux bridges : | ||
+ | |||
+ | [[Fichier:Mandataire 1.png]] | ||
+ | |||
+ | On vérifie que les deux bridges sont correctement faits | ||
+ | |||
+ | [[Fichier:Mandataire 2.png]] | ||
+ | |||
+ | Ensuite, nous allons créer deux images, une sera apache2_reverse et l'autre apache2. Puis nous allons monter l'image apache2_reverse dans le but de modifier sa configuration. | ||
+ | |||
+ | [[Fichier:Mandataire 3.png]] | ||
+ | |||
+ | |||
+ | [[Fichier:Mandataire 4.png]] | ||
+ | |||
+ | On modifie le fichier /etc/apache2/sites-enabled/000-default afin d'y rajouter les lignes suivantes : | ||
+ | |||
+ | ProxyPass / 192.168.42.2 | ||
+ | ProxyPassReverse / 192.168.42.2 | ||
+ | |||
+ | De cette manière, toutes les requêtes réalisées à 10.0.0.2 (l'ip du mandataire inverse) sera transmis au conteneur possédant le serveur HTTP à l'ip 192.168.42.2. | ||
+ | |||
+ | Maintenant que la configuration est terminée, on peut demarrer nos conteneurs : | ||
+ | |||
+ | [[Fichier:Mandataire 5.png]] | ||
+ | |||
+ | [[Fichier:Mandataire 6.png]] | ||
+ | |||
+ | = Annexes = | ||
+ | |||
+ | '''Ext4''' | ||
+ | |||
+ | Est une évolution du système de fichier ext3, qui est actuellement le système de fichier le plus utilisé sous Linux. Il présente de nombreux avantages et optimisations par rapport à l'ancienne version, tout en assurant une rétro-compatibilité. Ext4 est stable et est le système de fichier par défaut sous 9.10. Outre le fait qu'il puisse gérer les volumes d'une taille allant jusqu'à un exbioctet (260 octets), la fonctionnalité majeure de ext4 est l'allocation par extent qui permettent la pré-allocation d'une zone contiguë pour un fichier, pour minimiser la fragmentation. L'option extent est activée par défaut depuis le noyau Linux 2.6.23 ; avant cela, elle devait être explicitement indiquée lors du montage de la partition. | ||
+ | |||
+ | '''Debootstrap''' | ||
+ | |||
+ | Est un outil permettant d'installer un système de base Debian dans un sous-répertoire d'un autre système déjà installé. Il ne nécessite pas de CD d'installation, mais un accès à un référentiel Debian. Il peut également être installé et exécuté à partir d'un autre système d'exploitation. Ainsi, on peut par exemple utiliser debootstrap pour installer Debian sur une partition non utilisée d'un système Gentoo en cours d'exécution. Il peut également être utilisé pour créer un rootfs pour une machine d'une architecture différente, appelée "cross-debootstrapping". Il existe également une version largement équivalente écrite en C: cdebootstrap, qui est plus petite. Debootstrap ne peut utiliser qu'un seul référentiel pour ses packages. Si l’on doit fusionner des paquets provenant de différents référentiels (comme le fait apt) pour créer un rootfs, ou si l’on doit personnaliser automatiquement le rootfs, on utilise Multistrap. | ||
+ | |||
+ | '''cgroups : (control groups)''' | ||
+ | |||
+ | Est une fonctionnalité du noyau Linux pour limiter, compter et isoler l'utilisation des ressources (processeur, mémoire, utilisation disque, etc.). | ||
+ | Un groupe de contrôle est une suite de processus qui sont liés par le même critère. Ces groupes peuvent être organisés hiérarchiquement, de façon que chaque groupe hérite des limites de son groupe parent. Le noyau fournit l'accès à plusieurs contrôleurs (sous-systèmes) à travers l'interface cgroup2. Par exemple, le contrôleur « memory » limite l'utilisation de la mémoire, « cpuacct » comptabilise l'utilisation du processeur, etc. | ||
+ | |||
+ | Les Control groups peuvent être utilisés de plusieurs façons : | ||
+ | |||
+ | *En accédant au système de fichier virtuel cgroup manuellement | ||
+ | *En créant et gérant des groupes à la volée en utilisant des outils tels que cgcreate, cgexec, cgclassify (de libcgroup) | ||
+ | *Le « démon de moteur de règles » qui peut automatiquement déplacer les processus de certains utilisateurs, groupes ou commandes vers un cgroup en respectant ce qui est spécifié dans la configuration. | ||
+ | *Indirectement à travers d'autres logiciels qui utilisent cgroups, tels que la virtualisation LXC8, libvirt, systemd, Open Grid Scheduler/Grid Engine9. | ||
+ | |||
+ | '''Unshare :''' | ||
+ | |||
+ | unshare() permet à un processus de dissocier les parties de son contexte d'exécution qui sont actuellement partagées avec d'autres processus. Une partie du contexte d'exécution, comme l'espace de noms, est implicitement partagée lorsqu'un processus est créé avec fork(2) ou vfork(2), pendant que d'autres parties, comme la mémoire virtuelle, peuvent être partagées par une demande explicite lors de la création d'un processus avec clone(2). | ||
+ | Le principal intérêt de unshare() est de permettre à un processus de contrôler son contexte d'exécution partagé sans avoir à créer un nouveau processus. | ||
+ | |||
+ | '''chroot (change root):''' | ||
+ | |||
+ | Est un appel système qui a également donné son nom à une commande des systèmes d'exploitation Unix permettant de changer le répertoire racine d'un processus de la machine hôte. | ||
+ | En d'autres termes, la commande “chroot” permet de changer le répertoire racine vers un nouvel emplacement. | ||
+ | Chroot peut être utilisé dans deux cas : | ||
+ | |||
+ | *En tant que bascule d'environnement pour prendre le contrôle d'une installation Linux depuis un autre système. | ||
+ | *En tant que prison pour empêcher un utilisateur de remonter dans l'arborescence pour l'emprisonner dans un répertoire spécifique (ce qui peut être utilisé avec un serveur FTP pour que les utilisateurs ne remontent pas dans l'arborescence du système). | ||
+ | |||
+ | '''Les Veth ''' | ||
+ | |||
+ | Virtual Ethernet permet aux partitions logiques de communiquer entre elles sans avoir à affecter de matériel physique aux partitions logiques. | ||
+ | |||
+ | On peut créer des adaptateurs Ethernet virtuels sur chaque partition logique et les connecter à des réseaux locaux virtuels. Les communications TCP / IP sur ces réseaux locaux virtuels sont acheminées via le microprogramme du serveur. | ||
+ | |||
+ | Un adaptateur Ethernet virtuel fournit une fonction similaire à celle d’un adaptateur Ethernet 1 Gb. Une partition logique peut utiliser des adaptateurs Ethernet virtuels pour établir plusieurs connexions interpartition haut débit au sein d'un même système géré. Les partitions logiques AIX, IBM® i, Linux et Virtual I / O Server et les environnements Windows intégrés à la plate-forme System i peuvent communiquer entre eux via TCP / IP via les ports de communication Ethernet virtuels. | ||
+ | |||
+ | Les cartes Ethernet virtuelles sont connectées à un commutateur Ethernet virtuel de type IEEE 802.1q (VLAN). Grâce à cette fonction de commutateur, les partitions logiques peuvent communiquer entre elles à l'aide d'adaptateurs Ethernet virtuels et en affectant des ID de VLAN leur permettant de partager un réseau logique commun. Les cartes Ethernet virtuelles sont créées et les attributions d'ID de VLAN sont effectuées à l'aide de la console HMC. Le système transmet les paquets en les copiant directement de la mémoire de la partition logique émettrice vers les mémoires tampons de réception de la partition logique réceptrice sans aucune mise en mémoire tampon intermédiaire du paquet. | ||
+ | |||
+ | Le nombre d'adaptateurs Ethernet virtuels autorisés pour chaque partition logique varie en fonction du système d'exploitation. | ||
+ | |||
+ | AIX 5.3 et versions ultérieures prennent en charge jusqu'à 256 cartes Ethernet virtuelles pour chaque partition logique. | ||
+ | La version 2.6 du noyau Linux prend en charge jusqu'à 32 768 cartes Ethernet virtuelles pour chaque partition logique. Chaque partition logique Linux peut appartenir à un maximum de 4 094 réseaux locaux virtuels. | ||
+ | |||
+ | '''MANIFEST ''' | ||
+ | |||
+ | Un MANIFEST contient des informations sur une image, telles que les couches, la taille et le digest (hash). La commande docker manifest fournit également aux utilisateurs des informations supplémentaires telles que le système d'exploitation et l'architecture pour lesquelles une image a été créée. | ||
+ | |||
+ | Une liste de manifests est une liste de couches d'image créée en spécifiant un ou plusieurs noms d'image (idéalement plusieurs). Il peut ensuite être utilisé de la même manière qu'un nom d'image dans les commandes docker pull et docker run, par exemple. | ||
+ | |||
+ | Idéalement, une liste de manifestes est créée à partir d'images de fonctions identiques pour différentes combinaisons os / arch. Pour cette raison, les listes de manifestes sont souvent appelées «images multi-arch». Cependant, un utilisateur peut créer une liste de manifestes pointant vers deux images: une pour Windows sur amd64 et une pour Darwin sur amd64. | ||
+ | |||
+ | '''API REST''' | ||
+ | |||
+ | Une API compatible REST, ou « RESTful », est une interface de programmation qui fait appel à des requêtes HTTP pour obtenir (GET), mettre à jour PUT), publier (POST) et supprimer (DELETE) des données ou bien simplement interagir avec des services. | ||
+ | |||
+ | Aujourd’hui, l’utilisation croissante du Cloud et des architectures micro-services Web font que l’API REST est devenu un standard de facto. Pour donner un exemple, la plupart des grandes entreprises du Web exposent une API REST afin de pouvoir avoir accès à leurs données et/ou interagir avec leurs services. | ||
+ | |||
+ | Dans le cas spécifique de Docker. Le moteur expose une API REST, cette dernière est utilisée par l’interface en ligne de commande. Ce qui est intéressant, c’est qu’il suffit maintenant de bind le moteur Docker sur une adresse IP et un port, et on peut interagir avec le moteur à distance. En pratique, c’est que qui est fait quand on orchestre Docker avec Kubernetes par exemple. | ||
+ | |||
+ | La plupart des commandes du clien ont un équivalent direct en point de terminaison d’API (par exemple, la commande docker ps va faire un GET /containers au moteur Docker). | ||
+ | |||
+ | ''' Les SWAP''' | ||
+ | |||
+ | L'espace d'échange, aussi appelé par son terme anglais swap space ou simplement swap, est une zone d'un disque dur faisant partie de la mémoire virtuelle de l’ordinateur. Il est utilisé pour décharger la mémoire vive physique (RAM) de l’ordinateur lorsque celle-ci arrive à saturation. | ||
+ | |||
+ | L'espace d'échange, se trouve généralement sous une forme de partition de disque dur – on parle alors de partition d'échange. Il peut aussi se présenter sous forme de fichier – on parle alors de fichier d'échange. | ||
+ | |||
+ | Par défaut, la plupart des distributions calculent et s'attribue automatiquement un espace d'échange suffisant lors de leurs installation (sa taille est régulièrement égale au double de la taille de la RAM). | ||
+ | |||
+ | |||
+ | '''Namespace : ''' | ||
+ | |||
+ | Le terme espace de noms (namespace) désigne en informatique un lieu abstrait conçu pour accueillir des ensembles de termes appartenant à un même répertoire, comme dans l'exemple suivant où les espaces de noms sont nommés « Jean-Paul » et « Jean-Pierre » : | ||
+ | Jean-Paul <br> | ||
+ | Jean-Pierre <br> | ||
+ | Mes livres <br> | ||
+ | Mes BD <br> | ||
+ | Mes CD <br> | ||
+ | Mes CD <br> | ||
+ | Propriétés : | ||
+ | |||
+ | Un espace de noms peut être vu comme une fonction F qui, à un ensemble de symboles S, associe un ensemble O d'objets (à prendre au sens large). Ces objets peuvent être des entiers, des réels, des objets informatiques, des lieux, des personnes, etc. | ||
+ | En programmation informatique, les espaces de noms sont généralement des injections seulement car s'il y a deux objets nommés distincts alors leur nom est différent et un objet nommé peut avoir plusieurs noms. | ||
+ | Dans le cas d'un réseau Ethernet, les espaces de noms sont des bijections car chaque carte réseau a une adresse Ethernet unique et, à partir d'une telle carte, il est possible de retrouver l'adresse Ethernet correspondante. | ||
+ | Enfin, s'il y a un ensemble d'écoliers dans une classe et considérons leur prénom comme ensemble de symboles, l'espace de noms est surjectif seulement. En effet, tous les écoliers ont un prénom, mais plusieurs peuvent avoir le même prénom. Ce dernier cas, arrive parfois en programmation dans plusieurs situations (ex. : table de hachage), ce qui produit des collisions. Pour distinguer les objets nommés, on peut étendre les noms par des préfixes (dans le cas de la programmation) ou ajouter le nom de la personne ou son adresse (dans le cas des écoliers). Dans ce cas, on parle d'extension de l'espace de noms. On peut aussi remplacer les noms ambigus par des pseudonymes ou des alias. Dans ce cas, on parle de modifier l'espace de noms. | ||
+ | '''Système de fichiers ''' | ||
+ | Le système de fichiers (par exemple: zfs) permet l'existence de volumes virtuels (datasets sous zfs), qui partagent un même espace physique, mais peuvent être montés à des endroits différents d'une même hiérarchie de fichiers, ce qui permet la dé-duplication entre des volumes différents et a l'avantage de ne pas imposer à chaque volume virtuel de réserver un espace fixe difficile à modifier après coup. | ||
=Documents Rendus= | =Documents Rendus= | ||
+ | https://archives.plil.fr/hmalti/projet_gestion_containers | ||
+ | |||
+ | https://projets-ima.plil.fr/mediawiki/images/d/de/Rapport_projet_IMA4_hmalti.pdf |
Version actuelle datée du 9 mai 2019 à 22:50
Sommaire
Présentation générale
Description
L'intitulé de mon projet est "Système minimal de gestion de conteneurs", il consiste à réaliser une solution logicielle permettant de créer, détruire, configurer et lister des conteneurs.
Un conteneur Linux est un ensemble de processus qui sont isolés du reste du système. Un conteneur s'exécute à partir d'une image distincte qui fournit tous les fichiers nécessaires à la prise en charge des processus qu'il contient :
- Les caractéristiques systèmes : taille mémoire maximale, nombre de CPUs utilisés, débit disque maximal etc.
- Une configuration réseau : liste de commutateurs virtuels de rattachement avec leurs adresses.
En fournissant une image qui contient toutes les dépendances d'une application, le conteneur assure donc la portabilité de l'application entre les divers environnements (développement, test puis production) ainsi que la scalabilité.
Les conteneurs sont une solution au problème de la façon dont les logiciels peuvent fonctionner de manière fiable lorsqu'ils sont déplacés d'un environnement informatique à l'autre. Cela pourrait être du portable du développeur à un environnement de test, d'un environnement de test à la production, et peut-être d'une machine physique dans un centre de données à une machine virtuelle dans un cloud privé ou public.
Les avantages majeurs de l'utilisation des conteneurs sont :
- La taille : En effet un conteneur prend quelques dizaines de mégaoctets de taille, contrairement à une VM qui peut prendre plusieurs Gigabytes à cause de son système d'exploitation.Ce qui rend l'utilisation des conteneurs très populaire sur les serveurs qui peuvent en contenir plusieurs. Et dans le cloud où l'on paie ce qu'on consomme.
- Le temps : Une application conteneurisée peut être démarrée instantanément et quand il est nécessaire peut disparaître libérant des ressources sur son hôte tandis que les machines virtuelles peuvent prendre plusieurs minutes pour démarrer leurs systèmes d'exploitation et commencer à exécuter les applications qu'elles hébergent.
- La modularité : Plutôt que d'exécuter une application complexe entière dans un seul conteneur, l'application peut être divisée en modules (tels que la base de données, l'interface de l'application, etc.) Les applications créées de cette manière sont plus faciles à gérer car chaque module est relativement simple et des modifications peuvent être apportées aux modules sans avoir à reconstruire l'application entière. Étant donné que les conteneurs sont si légers, les modules individuels (ou micro services) ne peuvent être instanciés que lorsqu'ils sont nécessaires et sont disponibles presque immédiatement.
Avec une telle architecture, on suit la philosophie UNIX du “KISS”
- Les applications n’ont pas de dépendance système.
- Les mises à jours sont déployables facilement.
- La consommation de ressources est optimisée.
Les containers sont donc proches des machines virtuelles, mais présentent des avantages importants. Alors que la virtualisation consiste à exécuter de nombreux systèmes d’exploitation sur un seul et même système, les conteneurs se partagent le même noyau de système d’exploitation et isolent les processus de l’application du reste du système.
Plutôt que de virtualiser le hardware comme l’hyperviseur, le conteneur virtualise le système d’exploitation. Il est donc nettement plus efficient qu’un hyperviseur en termes de consommation des ressources système.
Concrètement, il est possible d’exécuter près de 4 à 6 fois plus d’instances d’applications avec un conteneur qu’avec des machines virtuelles comme Xen ou KVM sur le même hardware.
Objectifs
L'objectif de ce projet est de réaliser un écosystème minimal permettant de gérer des images et des instances de conteneurs.
- Les commandes seront réalisées en shell et les informations stockées dans des fichiers textes. Une configuration réseau devra être mise en place.
- Il est demandé de réaliser un système de gestion de conteneurs comme Docker mais en plus simple.
- Il est interdit de lancer un démon ou de perturber le réseau de l'hôte avec des règles de filtrage de paquets.
- Un contrôle des instances se fera par "crontab" et les instances seront connectées via des commutateurs virtuels Linux classiques.
- La gestion sophistiquée des images à base de système de fichiers à couche n'est pas permise.
- Avant de lancer un conteneur, le système de fichiers (un fichier lui-même) sera copié et le conteneur sera démarré sur la copie.
Analyse du projet
Positionnement par rapport à l'existant
Lorsqu'on parle aujourd'hui de gestion de conteneurs on parle systématiquement de la solution Docker. Mon automatisation de cette gestion ne pourra bien évidemment jamais concurrencer Docker. Le but ici, est de réussir à reproduire un système de gestion minimal ressemblant à Docker en plus simple et de parvenir à le maîtriser.
Analyse du premier concurrent
Docker Enterprise Edition est sans doute la solution de gestion de conteneurs commerciale la plus connue. Il fournit une plate-forme intégrée, testée et certifiée pour les applications exécutées sur les systèmes d'exploitation Linux ou Windows et les fournisseurs de cloud. L’écosystème de Docker est riche et complet, cela va de la gestion de conteneurs, à l’orchestration (Docker Swarm ou son concurrent Google Kubernetes) tout en passant par la gestion du réseau, de l’intégration du cloud avec Docker machine, des applications multi conteneurs avec Docker Compose.
Aujourd’hui, selon les créateurs de Docker, plus de 3,5 millions d’applications ont été containérisées en utilisant cette technologie, et plus de 37 milliards d’applications containérisées ont été téléchargées.
De même, d’après le système de monitoring cloud DataDog, 18,8% des utilisateurs avaient adopté la plateforme en 2017. De son côté, RightScale estime que l’adoption de la plateforme dans l’industrie du Cloud a augmenté de 35% en 2017 à 49% en 2018. Des géants comme Oracle et Microsoft l’ont adopté, au même titre que presque toutes les entreprises du Cloud.
Analyse du second concurrent
Rocket (rkt) est un outil édité par CoreOS et est le concurrent de Docker. C'est un moteur de conteneur d'application développé pour les environnements de production modernes basés sur le cloud. Il présente une approche pod-native, un environnement d’exécution enfichable et une surface bien définie qui le rend idéal pour une intégration avec d’autres systèmes.
L'unité d'exécution principale de rkt est le "pod": un ensemble d'une ou plusieurs applications s'exécutant dans un contexte partagé (les pods de rkt sont synonymes du concept du système d'orchestration Kubernetes). Rkt permet aux utilisateurs d'appliquer différentes configurations (telles que les paramètres d'isolation) au niveau du pod et au niveau plus granulaire par application. L’architecture de rkt signifie que chaque pod s’exécute directement dans le modèle de processus Unix classique (c’est-à-dire qu’il n’y a pas de démon central), dans un environnement isolé et autonome. rkt implémente un format de conteneur standard moderne et ouvert, la spécification App Container (appc), mais peut également exécuter d'autres images de conteneur, telles que celles créées avec Docker.
Depuis son introduction par CoreOS en décembre 2014, le projet rkt a considérablement mûri et est largement utilisé. Il est disponible pour la plupart des principales distributions Linux et chaque version de rkt construit des packages rpm / deb autonomes que les utilisateurs peuvent installer. Ces packages sont également disponibles dans le référentiel Kubernetes pour permettre de tester l'intégration rkt + Kubernetes. rkt joue également un rôle central dans la manière dont Google Container Image et CoreOS Container Linux exécutent Kubernetes. Les éditeurs se concentrent sur la sécurité (le plus gros point faible de Docker), la compatibilité et une intégration aux standards. Le but étant de fournir les mêmes fonctionnalités que docker et être complémentaire.
Docker comparé à Rkt
- Sécurité de l'image du conteneur :
L’un des avantages de Docker est qu’il existe un registre public à partir duquel tout le monde peut télécharger des images optimisées du serveur d’applications. Donc, si vous voulez un serveur Nginx optimisé pour une application Web Magento, vous en obtiendrez un du registre Docker. Cependant, il y a un danger caché à cela. Un attaquant peut remplacer une image de serveur par une autre infectée par un logiciel malveillant. Avant la version 1.8, Docker n’avait aucun moyen de vérifier l’authenticité d’une image de serveur. Mais dans la version 1.8, une nouvelle fonctionnalité appelée Docker Content Trust a été introduite pour signer et vérifier automatiquement la signature d'un éditeur. Dans rkt, la vérification de la signature est effectuée par défaut. Ainsi, dès qu'une image de serveur est téléchargée, elle est vérifiée avec la signature de l'éditeur pour voir si elle est falsifiée.
- Prévention des attaques d'élévation de privilèges «root»
Docker s'exécute avec les privilèges de super-utilisateur «root» et crée de nouveaux conteneurs en tant que sous-processus. Le problème avec cela est qu'une vulnérabilité dans un conteneur ou un confinement médiocre peut donner à un attaquant un accès de niveau racine au serveur entier. Rkt a proposé une meilleure solution: les nouveaux conteneurs ne sont jamais créés à partir d'un processus root. De cette manière, même si une rupture de conteneur se produit, l'attaquant ne peut pas obtenir les privilèges root.
- Flexibilité dans la publication ou le partage d'images
Lors du développement d'applications, il peut être nécessaire de partager des images de conteneur avec vos partenaires technologiques. Si vous souhaitez partager des conteneurs Docker, vous devez configurer un registre privé spécial sur vos serveurs ou l'héberger dans un compte payant Docker pour le partager avec vos partenaires. Pour rkt, vous n’avez besoin que de votre serveur Web. Rkt utilise le protocole HTTPS pour télécharger des images et utilise une méta-description sur le serveur Web pour pointer vers l'emplacement. C’est donc un serveur de moins à maintenir et plus facile à accéder pour les partenaires.
- Taille de la base de code
Docker ajoute toutes ses fonctionnalités à un seul fichier programme monolithique, ce qui signifie que le nombre de lignes de code ne cesse d’augmenter à chaque nouvelle version. Et voici un problème de sécurité. Une seule vulnérabilité dans l'une des milliers de lignes de code peut compromettre l'intégralité du programme et donner essentiellement un accès root à un attaquant. Au contraire, rkt utilise une architecture modulaire dans laquelle les fonctionnalités sont développées sous forme de fichiers binaires indépendants par différents fournisseurs. Le code de base est maintenu aussi petit que possible afin de réduire la surface d'attaque. Cela garantit que même si un sous-composant tel que etcd est compromis, les dommages seraient limités aux données auxquelles ce programme peut accéder.
- Portabilité à d'autres systèmes de conteneurs
De nouveaux systèmes de conteneurs sortent tout le temps. Avec Docker, la migration vers une nouvelle technologie de conteneur peut poser problème, car il utilise un format d’image propriétaire. En revanche, rkt utilise un format de conteneur open source appelé «appc». Ainsi, toute image de serveur créée à l'aide de rkt peut être facilement portée vers un autre système de conteneur à condition qu'elle suive le format ouvert «appc». En adhérant à un standard ouvert, rkt n’impose pas de verrouillage du fournisseur. Cela aide les propriétaires de système à migrer sans peine vers un autre système de conteneur mieux adapté à leurs besoins. [1]
Scénario d'usage du produit ou du concept envisagé
Toto est un développeur pas très à la mode, il ne connaît pas Docker! Il a de modestes connaissances en système et il essaye de développer une application. Il travaille sur un ordinateur portable ayant une configuration spécifique, avec des librairies et paquets spécifiques.
Les collègues développeurs de Toto travaillent sur des machines présentant des configurations légèrement différentes.
L'application que Toto développe repose sur cette configuration et dépend de librairies et de paquets avec une version particulière.
En parallèle, l'entreprise de Toto dispose d'environnements de test et de production qui ont leurs propres systèmes d’exploitation avec une version particulière ainsi que leurs propres paquets et librairies. Toto souhaite émuler ces environnements autant que possible localement, mais sans avoir à payer les coûts liés à la recréation des environnements de serveur. Comment faire pour que son application en développement puisse fonctionner dans ces environnements, passer l'assurance qualité et être déployée sans prise de tête, sans réécriture et sans correctifs ?
La réponse est simple : il lui suffit d'utiliser des conteneurs. Le conteneur qui accueille son application contient toutes les configurations et librairies nécessaires. Il peut ainsi le déplacer entre les environnements de développement, de test et de production, sans aucun effet secondaire. La crise est évitée,Toto pourra continuer à travailler et ne se fera pas licencié, il travaillera plus efficacement, apportera un gain de temps et d’argent à son entreprise et ne se cassera plus la tête à considérer le système présent sur les environnements de production et de tests. De plus, il utilisera le système de gestion de conteneurs que je lui réserve !
Les conteneurs Linux peuvent aussi être utilisés pour résoudre des problèmes liés à des situations qui nécessitent un haut niveau de portabilité, de configurabilité et d'isolation. Quelle que soit l'infrastructure (sur site, dans le cloud ou hybride), les conteneurs répondent à la demande.
Réponse à la question difficile
La question difficile qui m'a été posée était mes avantages par rapport à mon premier concurrent : Docker, c'est à dire pourquoi utiliserait-on un système léger de gestion de conteneurs plutôt que Docker.
Pour répondre à cette question, il faut comprendre comment Docker fonctionne : Docker est un logiciel modulaire qui comprend plusieurs couches. L'une de ses couches est le docker deamon (dockerd), le deamon est codé afin d'exposer une API REST. Ainsi, il est bind sur des sockets au sein de la machine.
L'autre inconvénient de Docker c'est qu'il va manipuler les Iptables [2] afin de pouvoir gérer le réseau des conteneurs, cela peut venir perturber les configurations réseaux déjà présentes sur la machine. Mon système n'utilisera que les commutateurs logiciels (bridges linux) standards. Ainsi, il n'y aura aucune altération du routage des paquets au sein de la machine.
De plus, je n'utiliserai pas de gestion sophistiquée des images à base de système de fichiers à couche. Avant de lancer un conteneur, le système de fichiers (un fichier lui-même) est copié, et le conteneur est démarré sur cette copie.
Préparation du projet
Cahier des charges
Ce projet peut se découper en cinq grandes parties :
- Commencer par faire un état de l'art afin de me forger des connaissances solides sur les conteneurs et leur orchestration.
- Créer, configurer et supprimer des conteneurs "à la main".
- Créer un programme shell (qu'on lance dans le contexte d'un conteneur) qui permet d'automatiser la gestion des conteneurs, pour :
- Lancer une instance.
- Lister les instances en cours d'exécution (identifiant, nom de l'image d'origine, temps d'exécution) tout en mettant à jour le fichier des instances (s'il le faut).
- Arrêter une instance, détruire ses ressources réseau tout en conservant son image disque dans un état stable ainsi que ses caractéristiques.
- Suppression d'une instance avec la destruction de son image disque et ses caractéristiques:
- Stocker les informations sur les conteneurs qui tournent dans un fichier texte, paramétrer finement la configuration réseau.
- Mettre en place une application de test : une architecture de ferme de serveurs Web privée avec un accès par mandataire inverse. Les serveurs Web et le mandataire inverse seront des conteneurs.
L'objectif est de créer un programme qui se présentera comme suit (on le nommera baleine) :
- Crée un nouveau conteneur (lui ajouter des contraintes avec les cgroups, ajouter le bridge auquel l'ajouter ainsi que son adresse ip) :
baleine container create
- Retourne la liste des conteneurs qui tournent sur le système, avec leurs adresses ip, leurs limitations cgroups, etc
baleine container list
- Supprimer un conteneur :
baleine container remove
- Stopper le conteneur :
baleine container stop
- Redémarrer un conteneur:
baleine container restart
- Crée un nouveau pont (bridge) linux afin d'interconnecter les containers
baleine bridge create
- Renvoie la liste des ponts linux ainsi que le nombre de containers connectés dessus
baleine bridge list
- Crée une nouvelle image avec la taille indiquée
baleine image create
- Liste les images présentes sur le système
baleine image list
- importe une image depuis une image Docker
baleine image import
- exporte une image vers une image Docker
baleine image export
Afin de lister les conteneurs, les images, et les bridges, il sera nécessaire de les stocker dans un fichier. Pour les conteneurs, ce fichier sera /var/lib/baleine/manifeste/containers/nom_conteneur.manifest, il se présentera sous la forme :
nom_container: nom_image: pid: nom_bridge: starting_time:
Pour les images, ce fichier sera /var/lib/baleine/manifeste/images/nom_image.manifest, il se présentera sous la forme :
nom_image: taille: chemin:
Pour les bridges, ce fichier sera /var/lib/baleine/manifeste/bridges/nom_bridge.manifest, il se présentera sous la forme :
nom_bridge:
Choix techniques : matériel et logiciel
Ce projet est purement informatique, il ne nécessite aucun matériel. Par contre, au moment où je ferai l'infrastructure avec les conteneurs, j'aurai besoin d'un serveur ainsi qu'une machine virtuelle.
Liste des tâches à effectuer
- État de l’art de la conteneurisation.
- Créer, configurer et supprimer des conteneurs "à la main".
- Créer un script shell pour: Lancer une instance, lister les instances en cours d'exécution, les arrêter ou encore les supprimer.
- Stocker les informations des conteneurs.
- Paramétrer la configuration réseau.
- Mettre en place une application de tests avec des conteneurs (infrastructure ferme serveurs web avec accès par mandataire inverse).
Calendrier prévisionnel
Le projet étant à rendre au mois de Mai, prenant en compte une marge d'erreur et d'amélioration éventuelle, le calendrier prévisionnel optimal que je me suis fixée se trouve sur le diagramme de Gantt suivant :
Réalisation du Projet
Feuille d'heures
Cette feuille d'heures a été faite approximativement. En effet, ayant apprécié le sujet, j'y ai consacré un temps sans vraiment compter, et j'aurai voulu aller plus loin pour répondre à toutes les consignes du cahier des charges.
Tâche | Prélude | Heures S1 | Heures S2 | Heures S3 | Heures S4 | Heures S5 | Heures S6 | Heures S7 | Heures S8 | Heures S9 | Heures S10 | Total |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Analyse du projet | 10 | 10 | ||||||||||
État de l'art | 2 | 8 | 2 | 3 | 15 | |||||||
Création-configuration des conteneurs à la main | 6 | 5 | 4 | 15 | ||||||||
Configuration des interfaces et création du bridge | 6 | 6 | ||||||||||
Script Shell | 8 | 4 | 3 | 2 | 4 | 21 | ||||||
Gestion du stockage des informations des conteneurs | 6 | 6 | ||||||||||
Paramétrage de la configuration réseau | 8 | 8 | ||||||||||
Mise en application de l'application des tests | 4 | 4 | ||||||||||
Rédaction du rapport | 4 | 4 | ||||||||||
Rédaction du Wiki | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 11 |
Total | 13 | 9 | 7 | 6 | 5 | 9 | 12 | 5 | 10 | 11 | 13 | 100 |
Prologue
Semaine 1
La première semaine a été dédiée à l’étude de l'état de l'art de la conteneurisation. Et ce, afin de me familiariser et d'en apprendre un maximum sur le sujet, comprendre les subtilités de l’utilité des conteneurs ainsi que leurs évolutions dans le temps. Le sujet était passionnant et très intéressant, les sources étaient multiples et j’ai dû filtrer, traduire, composer mon propre état de l’art en récoltant de chaque source ce que je trouvais intéressant à mentionner. Cette quête de l’information m’a beaucoup apportée et initiée à la recherche, cependant elle m’a pris un peu plus d’une séance, mais c’était une partie très agréable à faire.
Semaine 2
Durant cette semaine, il m'a été recommandé de réaliser des TP dédiés aux GIS sur la virtualisation et me familiariser avec les commandes permettant de créer des conteneurs à la main, ce que j'ai réussi à faire. Une fois arrivée à la partie où il faut limiter / restreindre les cpu, la mémoire, le nombre d'entrées/sorties ou encore l'interdiction d'accès à des périphériques, les commandes fonctionnaient mais il fallait tester si elles fonctionnaient bien et qu'elles délimitaient bien. Il a fallu trouver des idées pour tester la mémoire par exemple : allouer de la mémoire en boucle infinie et renvoyer le nombre de fois où ça a été fait afin de vérifier que ça respecte bien la taille donnée dans le cgroup. Cela ne fonctionnait point, et la compréhension des cgroups m'a prise plus de temps que prévu , ce qui m'a retardé...
Semaine 3
Suite au blocage de la semaine d’avant, je n’ai pas réussi à avancer, j’ai dû réaliser quelques essais mais en vain, j’ai essayé de limiter le nombre de CPUs à utiliser par le conteneur , l’écriture sur le fichier cpuset.cpus en mentionnant le PID du processus du conteneur dans le fichier tasks du pseudo fichier model1 se faisait correctement. Le problème était la vérification, en essayant de lancer des processus différents qui obligeraient l'utilisation d’un autre CPU, le conteneur prenait d’autre CPUs même si j’avais fait en sorte qu’il en ait pas le droit. De même, j’ai essayé de limiter les entrées sorties mais en vain. Seule la restriction mémoire a fonctionné, description des détails dans la section “création des conteneurs à la main” ci-dessous. J’ai fini par abandonner les restrictions par les cgroups (et faire ça en parallèle) et entamer d’autres tâches qui me paraissaient prioritaires et ce, en attendant l’aide de mon encadrant.
Semaine 4
Lors de cette semaine, j'ai installé Docker, suivi le Get started pour manier les commandes, découvert son fonctionnement pour finalement mieux comprendre ce qui est demandé de faire dans le projet. J'ai lu de nombreux passages du livre Docker Deep Dive [3] de Nigel Poulton qui est une référence dans le monde de Docker. C'est un livre qui décrit l'histoire de Docker à travers le temps mais aussi qui rentre dans le détail technique de ses fonctionnalités. Ce document m'a été bénéfique afin de comprendre les tenants et aboutissants de Docker. Cela m'a aidé à imaginer comment mon script allait fonctionner. Ainsi, pour mon script, je vais m’inspirer de la syntaxe de docker afin de créer des conteneurs, d'exécuter une commande à l’intérieur de ceux-ci, de créer des bridges réseaux pour les conteneurs, etc.
Semaine 5
Durant cette semaine, j’ai commencé à réfléchir au cahier des charges précis et clair du projet et j’ai écrit une première ébauche de ce que devra être le format de stockage des fichiers, pour les conteneurs, pour les images et pour les bridges réseau.
J’ai donc établi l’architecture logicielle de mon application. Les informations sur les conteneurs, les images et les interfaces réseaux se trouveront dans des fichiers au format CSV qui seront manipulés par mon application.
En parallèle, il m’a fallu comprendre comment fonctionnaient les veth linux qui permettent de connecter deux espaces de nom réseau différents, et comment les utiliser dans le cadre de mon projet. Il faudra donc faire en sorte de créer une paire de veth par conteneur au moins, et l’une des extrémités de la paire veth sera à mettre dans l’espace de nom du conteneur. L’autre extrémité sera à connecter au bridge Linux côté hôte.
A la fin de cette séance j’ai réussi à mettre en place plusieurs conteneurs qui communiquent grâce à leurs interfaces virtuelles via un bridge mis dans l’espace de nom hôte, puis j’ai commencé à réfléchir à la façon d’écrire mon script qui fera cela de façon automatique. J’ai tenté d’envisager les éventuelles problématiques que je rencontrerai.
Par exemple, une de mes difficultés est la suivante : lorsque je lance le unshare, il faut faire attention à ce que je garde la main sur le terminal qui lance le unshare, car c’est sur ce terminal que tournera le script. Ainsi, après avoir recherché sur le net, j’ai appris l'existence de l’utilitaire “screen” qui est un multiplexeur de terminal.
Semaine 6
Lors de cette semaine j'ai essayé de résoudre le problème du "disown" (pour permettre à mon unshare de continuer à tourner lorsque le shell qui le lance s'arrête) mais en vain. À la fin de chaque exécution, le unshare n’apparaît plus dans la liste des processus. Je me suis aussi documentée sur comment Docker parvenait à franchir cette étape grâce au runc afin de m'en inspirer. J'ai préféré donc me mettre à écrire le script me permettant de créer l'interface réseau ainsi que sa configuration lors du lancement du conteneur. Je me suis rendue compte que j'avais mal considéré la structure et le squelette de mes scripts en ne prenant pas en compte les formats d'images de lancement des conteneurs ! Une documentation technique s'est imposée.
Semaine 7
Durant cette semaine, j’ai été boostée et j'ai bien avancée car pleins de problèmes se sont débloqués ce qui m’a énormément motivée !
En effet, le souci des cgroup (mémoire) a été réglé, il était difficile de comprendre pourquoi ça ne fonctionnait pas alors que la méthode décrite sur tous les forums avait l’air simple et fonctionnelle. Il se trouve qu’en limitant la mémoire grâce à “memory.limit_in_bytes” et sans changer memory.swappiness, en arrivant à la limite fixée, le SWAP (défini en annexe) rentrait en action et procurait de l’espace à notre mémoire. Ainsi, nous n’avions pas une limite mémoire fonctionnelle. La solution est de le fixer à 0. Ainsi, notre mémoire était complètement limitée ! L’idée maintenant est de découvrir l’utilité de tous les pseudo fichiers des cgroups concernant les CPU et parvenir à limiter de la même manière les CPU.
Suite au travail de la semaine précédente, une bonne partie de l’état de l’art sur les images a été écrite, ce qui m’a permis d’avoir une compréhension plus détaillée et précise à ce sujet. Il en a résulté d’une bonne avancée côté script. En effet, on peut désormais en lançant le script créer une image, en choisissant sa taille, stocker son nom, sa taille et son chemin dans un fichier texte portant son nom.
Il faudra entreprendre le script permettant de lister les images. Pour ce dernier, il faudra faire une sorte de manifeste (défini en annexe) de chaque image, qui décrira cette dernière. A chaque création d’une image, il conviendra d’alimenter ce manifeste. Un format de manifeste d’image est déjà décrit par l’Open Container Initiative (norme que suit Docker). Cette spécification est disponible à l’adresse : [4]
Semaine 8
Durant cette semaine là, je me suis occupée principalement de l’organisation des manifestes. Le souci c’est que je ne parvenais pas à créer plusieurs images portant le même nom. J’ai essayé donc d’attribuer à chaque image une petite extension contenant un chiffre aléatoire ce qui la distinguerait d’une autre image portant le même nom. Mais cela rendrait plus difficile la manipulation de son manifeste et la récupération de ses informations.
Semaine 9
Pendant cette semaine, je me suis concentrée sur la configuration réseau du conteneur mais j’ai rencontré un certain nombre de soucis. En effet, je ne parvenais pas à exécuter une commande dans le namespace du conteneur. J’utilisais la commande ip netns exec, cependant cette commande attend un nom de namespace et non un identifiant de namespace. Autrement dit je ne pouvais pas utiliser ip netns exec avec l’argument /proc/PID/ns/net, il me fallait en amont créer un nouveau namespace.
Semaine 10
Étant donné qu’il m’a été interdit de lancer un démon ou de perturber le réseau de l'hôte avec des règles de filtrage de paquets, j’utilise de simples bridges linux. En cette dernière séance officielle de projet, j’ai réussi à :
- créer un bridge depuis le script et créer son manifeste
- le supprimer
- lister les bridges présents
- Mettre up/down un bridge
- lister et supprimer les images
Mais il reste des choses à faire :
- Faire en sorte qu’en une seule commande on arrive à créer une image, créer à partir de la copie de cette dernière le conteneur.
- créer la ferme web pour le test avec mandataire inverse
- Utiliser "cron" pour contrôler les instances
- Importer / exporter une image depuis/vers Docker
- Écrire un Help
Semaines vacances + semaines supplémentaires
Il était évident qu’il fallait rajouter des heures supplémentaires pour mener à bien ce projet, je n’ai pas compté les heures ni le temps consacré à réaliser ce projet.
Je suis revenue sur le problème de la configuration réseau que j’avais rencontré deux semaines plus tôt. J’ai découvert que sur Linux, il était impossible d'avoir des interfaces avec des noms de plus de 16 caractères. Et puisque le seul moyen de nommer l’interface de “tel “ conteneur était de reprendre la variable “NOM_CONTAINER” et lui appliquer par exemple la commande : eth0@vif1 Je me suis donc orientée vers la commande nsenter qui permet de faire ce que je souhaite, c’est à dire d'exécuter une commande à l’intérieur du namespace d’un process. Nsenter me permettra ainsi entre autres d’ajouter une adresse ip à une interface au sein d’un conteneur.
Autre souci auquel je me suis heurtée : le nom des interfaces virtuelles à ajouter au container : le noyau Linux ne permet pas d’avoir des noms d’interface de plus de 16 octets. Ainsi, je ne pouvais pas donner à mes paires veth le nom du conteneur, au risque de faire planter le programme.
J’ai ainsi trouvé une autre solution, de manière à avoir des interfaces réseaux qui soient uniques à un conteneur et qu’on puisse les identifier à l’aide du nom du conteneur. Ma solution consiste à prendre le nom du container, à le hasher en SHA1, puis à prendre les 3 premiers digits significatifs.
Par exemple prenons un container nommé “moncontainer”, le sha1 donne 412c637eebd95f6a5bb63292421b3107b07f076e. Maintenant supposons que l’on souhaite avoir deux interfaces réseau sur ce conteneur. Nous allons récupérer les trois premiers chiffres du hash : 412 puis on rajoute un 4 ème chiffre correspondant à l’indice de l’interface. La première interface du conteneur aura ainsi l’identifiant 4121 et la seconde l’identifiant 4122.
Ainsi, on crée deux vif, la première sera la suivante : vif4121 +--------------> eth0 , et la seconde sera : vif4121 +----------------> eth1.
De cette manière on s’assure qu’il est peu probable que deux conteneurs tombent sur le même identifiant de vif.
Une fois que la structure globale du projet était fonctionnelle, je me suis penchée sur les détails du cahier des charges :
- Une mise en place d’un programme permettant de stopper simplement le conteneur.
- Un autre script permettant de démarrer un conteneur déjà existant et “stoppé” .
- Un script pour exécuter des programmes directement sur le conteneur.
- Une meilleure gestion des “list-images/conteneurs/bridges” . En effet, j’ai rajouté des options afin de faciliter l’extraction d’informations des manifestes. J’ai rajouté des “:” avant chaque information. Ainsi, des “cut -d ‘:’ ” serait faisable pour extraire et gérer d’une meilleure façon les manifestes.
- Après cela, je voulais vérifier que je pouvais lancer Apache2 sur mon conteneur
- Un nettoyage “clean-up” du code et ajout de commentaires pour que le code soit lisible et compréhensible par n’importe quel lecteur.
État de l'art
Définition
Un conteneur Linux est un processus ou un ensemble de processus isolés du reste du système. Tous les fichiers nécessaires à leur exécution sont fournis par une image distincte, ce qui signifie que les conteneurs Linux sont portables et fonctionnent de la même manière dans les environnements de développement, de test et de production. Ainsi, ils sont bien plus rapides que les pipelines de développement qui s'appuient sur la réplication d'environnements de test traditionnels.
L’utilité de la conteneurisation
L'incidence de la virtualisation sur l'informatique moderne est profonde. Elle permet aux entreprises d'améliorer considérablement la rentabilité et la flexibilité des ressources informatiques. Mais la virtualisation a un coût, notamment au niveau de l'hyperviseur et des systèmes d'exploitation invités, qui requièrent chacun de la mémoire et d’éventuelles licences coûteuses. Il en résulte une augmentation de la taille de chaque machine virtuelle, ce qui limite le nombre de VM qu'un serveur peut héberger. L'objectif de la conteneurisation vise à virtualiser les applications sans trop alourdir le système. L'idée n'est pas nouvelle : depuis plusieurs années déjà, des systèmes d'exploitation tels que OpenVZ, FreeBSD, Solaris Containers et Linux-VServer prennent cette fonctionnalité en charge. Mais c'est la récente introduction de plateformes ouvertes, telle que Docker, qui a remis sous les feux de la rampe la conteneurisation et son potentiel en matière d'applications distribuées évolutives.
Évolution
La plupart des applications tournent sur des serveurs. Dans le passé, nous ne pouvions exécuter qu’une seule application par serveur. Le monde des systèmes ouverts de Windows et Linux n’avait pas les technologies pour exécuter en toute sécurité plusieurs applications sur un même serveur. Bien sûr, chaque fois qu’une entreprise avait besoin d’une nouvelle application, le service informatique allait acheter un nouveau serveur. Et la plupart du temps personne ne connaissait parfaitement les exigences de performance de la nouvelle application ! Cela signifie que le service informatique devait deviner souvent à la volée le modèle et la taille des serveurs à acheter.
En conséquence, le service informatique a fait la seule chose à faire: acheter de gros serveurs rapides avec beaucoup de résilience. Après tout, la dernière chose que quelqu'un souhaitait, y compris l'entreprise, était des serveurs surchargés. Les serveurs surchargés peuvent entraîner une perte de clients et de revenus. Alors, l’IT généralement achetait des serveurs plus gros que ce qui était réellement nécessaire. Cela a entraîné un énorme nombre de serveurs fonctionnant entre 5 et 10% de leur capacité potentielle. Un gaspillage tragique du capital et des ressources de l'entreprise!
Hello VMware !
Pour tout cela, VMware, Inc. a fait un cadeau au monde: la machine virtuelle (VM). Et presque du jour au lendemain, le monde s'est transformé en un endroit bien meilleur! Nous avons enfin acquis une technologie qui nous permettrait de gérer en toute sécurité plusieurs applications sur un seul serveur tout en étant isolées les unes des autres.
C'était une révolution, il n’était plus nécessaire de se procurer un tout nouveau serveur surdimensionné à chaque fois que l'entreprise demandait une nouvelle application.
Tout à coup, tout est devenu bien plus dynamique, lancer une machine virtuelle pour tester une application ou la lancer en production est rapide, contrairement à l'achat de nouveaux serveurs. De plus, la supervision et le management de machines virtuelles est aisé, on peut facilement dupliquer, supprimer, relancer une machine virtuelle. Ce nouveau dynamisme au sein de l'informatique a été un réel tournant à 180 ° !
VMwarts
Même si les machines virtuelles sont géniales, elles ne sont pas parfaites! Le fait que chaque machine virtuelle nécessite son propre système d'exploitation dédié est un défaut majeur. Chaque OS consomme du CPU, de la RAM et du stockage qui pourraient autrement être utilisés pour alimenter davantage les applications. Chaque système d'exploitation a besoin de correctifs et de surveillance. Et dans certains cas, chaque système d'exploitation nécessite une licence. Le modèle de machine virtuelle présente également d'autres défis. Les machines virtuelles sont lentes à démarrer et à la portabilité pas terrible - migrer et déplacer des charges de travail de machine virtuelle entre hyperviseurs et les plates-formes cloud est plus difficile qu'il ne le faut.
Bonjour les conteneurs!
Pendant longtemps, les grands acteurs du Web tels que Google utilisaient des conteneurs. C'est une technologie pour remédier à ces faiblesses du modèle de VM. Dans le modèle de conteneur, le conteneur est à peu près analogue à la VM. La différence est que chaque conteneur n’a pas besoin d’un système d’exploitation complet. En fait, tous les conteneurs d’un même hôte partagent un même système d’exploitation. Cela libère énormément de quantités de ressources système telles que le processeur, la RAM et le stockage. Il réduit également les coûts de licence potentiels et réduit les frais généraux liés aux correctifs de système d’exploitation et autres entretiens. Les conteneurs sont également rapides à démarrer et ultra-portables. Déplacement de charges de travail de conteneur depuis votre ordinateur portable vers le cloud, puis vers des ordinateurs virtuels.
1979: Unix V7
Au cours de l’histoire unix du développement des conteneurs d’Unix V7 en 1979, l’appel système chroot a été introduit, modifiant le répertoire racine d’un processus et de ses fils dans un nouvel emplacement du système de fichiers. Cette avancée a marqué le début de l'isolation des processus: la séparation de l'accès aux fichiers pour chaque processus. Chroot a été ajouté à BSD en 1982. Un programme exécuté dans un tel environnement ne peut pas accéder aux fichiers et aux commandes situés en dehors de cette arborescence de répertoires environnementaux. Cet environnement modifié est appelé une ” prison” chroot.
2000: JBS FreeBSD
Près de deux décennies plus tard, en 2000, un petit fournisseur d’hébergement en environnement partagé est arrivé dans l’historique upBSD des conteneurs avec les FreeBSD jails afin de séparer clairement ses services de ceux de ses clients pour des raisons de sécurité et de facilité d’administration. Les jails FreeBSD permettent aux administrateurs de partitionner un système informatique FreeBSD en plusieurs systèmes indépendants plus petits, appelés jails, avec la possibilité d'attribuer une adresse IP à chaque système et une configuration.
2001: Linux VServer
Comme les jails FreeBSD, Linux VServer est un mécanisme jail qui permet de partitionner les ressources des conteneurs (systèmes de fichiers, adresses réseau, mémoire) sur un système informatique. Introduite en 2001, cette virtualisation de système d’exploitation est mise en œuvre en appliquant un correctif au noyau Linux. Des correctifs expérimentaux sont toujours disponibles, mais le dernier correctif stable a été publié en 2006.
2004: conteneurs Solaris
En 2004, la première version bêta publique de Solaris Containers, qui associe des contrôles des ressources système et une séparation des limites fournie par les zones, permettait de tirer parti de fonctionnalités telles que les instantanés et le clonage à partir de ZFS.
2005: Open VZ (Open Virtuzzo)
Il s'agit d'une technologie de virtualisation au niveau du système d'exploitation pour l'historique de conteneurs de Linux openVZ, qui utilise un noyau Linux corrigé pour la virtualisation, l'isolation, la gestion des ressources et les points de contrôle. Le code n'a pas été publié dans le noyau officiel de Linux.
2006: conteneurs de processus
Process Containers (lancé par Google en 2006) a été conçu pour limiter, comptabiliser et isoler l'utilisation des ressources (CPU, mémoire, E / S de disque, réseau) d'un ensemble de processus. Il a été renommé «Control Groups (cgroups)» un an plus tard et a finalement été fusionné avec le noyau Linux 2.6.24.
2008: LXC
LXC (LinuX Containers) a été la première et la plus complète implémentation du gestionnaire de conteneurs Linux. Il a été implémenté en 2008 à l'aide des “namespace ” cgroups de Linux et fonctionne sur un seul noyau Linux sans nécessiter de correctifs.
Le noyau de Linux 2.6.24 intègre une prise en charge fondamentale de la conteneurisation pour assurer une virtualisation au niveau du système d'exploitation et permettre à un même hôte d'exécuter plusieurs instances Linux isolées, baptisées « conteneurs Linux », ou LXC (LinuX Containers). LXC repose sur la notion de groupes de contrôle Linux, les cgroups. Ici, chaque groupe de contrôle offre aux applications une isolation totale des ressources (notamment processeur, mémoire et accès E/S), et ce sans recourir à des machines virtuelles à part entière. Les conteneurs Linux proposent également une isolation complète de leurs espaces de noms. Les fonctions telles que les systèmes de fichiers, les ID réseau et les ID utilisateur, ainsi que tout autre élément généralement associé aux systèmes d'exploitation, peuvent donc être considérés comme « uniques » du point de vue de chaque conteneur.
2011: Warden
CloudFoundry a commencé Warden en 2011, en utilisant LXC dans le début et plus tard en le remplaçant par owncloud. Warden peut isoler des environnements sur n’importe quel système d’exploitation, s’exécutant en tant que démon et fournissant une API pour la gestion des conteneurs. Il a développé un modèle client-serveur pour gérer une collection de conteneurs sur plusieurs hôtes, et Warden inclut un service permettant de gérer les groupes de contrôle, les name space et le cycle de vie des processus.
2013: LMCTFY
LMCTFY a démarré en 2013 en tant que version à source ouverte de la pile de conteneurs de Google, fournissant des conteneurs d'applications Linux. Les applications peuvent être configurées en «conteneur», en créant et en gérant leurs propres sous-conteneurs. Le déploiement actif dans LMCTFY a été arrêté en 2015 après que Google ait commencé à contribuer à libcontainer, qui fait désormais partie des concepts LMCTFY de base, qui fait maintenant partie de la Open Container Foundation.
2013: Docker
Lorsque Docker est apparu en 2013, la popularité des conteneurs a explosé. Ce n’est pas une coïncidence: l’histoire croissante des conteneurs de docker a toujours été liée à l’utilisation de docker et des conteneurs.
Docker a explosé sur scène en 2013, et cela a causé de l'excitation dans les cercles informatiques. La technologie de conteneur d'application fournie par Docker promet de changer la façon dont les opérations informatiques sont réalisées de la même manière que la technologie de virtualisation quelques années auparavant.
Autres exemples, Docker propose des outils de génération automatisée de build. Ces outils aident les développeurs à passer plus facilement d'un code source à des applications conteneurisées, ou à travailler avec des outils de "configuration as a code", tels que Chef, Ansible, Puppet et autres, afin d'automatiser ou de rationaliser le processus de build.
La gestion des versions permet aux développeurs de suivre l'évolution des versions des conteneurs, de comprendre les différences, voire de revenir à des versions antérieures le cas échéant. Et sachant que tout conteneur peut servir d'image de base à un autre, il est d'autant plus facile de réutiliser des composants aisément partageables via un registre public (ou privé).
Tout comme Warden, Docker a également utilisé LXC à ses débuts et a par la suite remplacé ce gestionnaire de conteneurs par sa propre bibliothèque, libcontainer (plus tard apellée containerD). Mais il ne fait aucun doute que Docker s’est séparé du pack en offrant un écosystème complet pour la gestion des conteneurs.
2017: Les outils de conteneur deviennent matures
Des centaines d'outils ont été développés pour faciliter la gestion des conteneurs. Alors que ces types d'outils existent depuis des années, 2017 est l'année où beaucoup d'entre eux ont gagné leurs galons. Il suffit de regarder Kubernetes; Depuis son adoption dans la Cloud Native Computing Foundation (CNCF) en 2016, VMWare, Azure, AWS et même Docker ont annoncé leur soutien, en plus de leurs infrastructures.
Alors que le marché continue de croître, certains outils ont permis de définir certaines fonctions de la communauté des conteneurs. Ceph et REX-Ray établissent des normes pour le stockage de conteneurs, tandis que Flannel connecte des millions de conteneurs dans des centres de données. Et dans CI / CD, Jenkins change complètement la façon dont nous construisons et déployons des applications. Adoption de rkt et Containerd par la CNCF
L’écosystème de conteneurs est unique dans la mesure où il repose sur un effort et un engagement de la communauté vis-à-vis de projets open source. Le don de Docker du projet Containerd à la CNCF en 2017 est emblématique de ce concept, ainsi que l'adoption de CNCF du conteneur KRT (prononcé « fusée ») l'exécution dans le même temps. Cela a conduit à une plus grande collaboration entre les projets, à plus de choix pour les utilisateurs et à une communauté centrée sur l'amélioration de l'écosystème des conteneurs.
Kubernetes grandit
En 2017, le projet open-source a montré de grandes avancées pour devenir un k8s logo.pngtechnology plus mature. Kubernetes prend en charge des classes d'applications de plus en plus complexes, permettant ainsi la transition de l'entreprise vers le cloud hybride et les microservices. A DockerCon à Copenhague, Docker a annoncé qu'ils soutiendront le conteneur Kubernetes orchestrateur et Azure et AWS est tombé en ligne avec AKS (Azure Services Kubernetes) et EKS, un service Kubernetes pour rivaliser avec ECS propriétaire. Il s’agissait également du premier projet adopté par le CNCF et commandait une liste croissante de fournisseurs de services d’intégration de systèmes tiers. Kubernetes semble avoir un avenir prometteur en tant que plate-forme d'orchestration de facto.
Docker : un LXC augmenté
Les plateformes de conteneurisation d'applications, telles que Docker, ne remplacent pas les conteneurs Linux. L'idée consiste à utiliser LXC comme base, puis à ajouter des capacités de niveau supérieur. Par exemple, une plate-forme comme Docker autorise la portabilité entre machines (qui exécutent aussi Docker) et permet ainsi à une application et à ses composants d'exister en tant qu'objet mobile unique. LXC seul permet la mobilité, mais la build est liée à la configuration du système. Donc la déplacer sur une autre machine peut introduire des différences susceptibles d'empêcher le conteneur de l'application de s'exécuter à l'identique (voire de s'exécuter tout court).
Mais le recours à LXC était un problème depuis le début. Tout d’abord, LXC est spécifique à Linux. C’était un problème pour un projet qui avait aspirations à être multi-plateforme. Deuxièmement, être dépendant d'un outil externe pour quelque chose d'aussi essentiel au projet était un risque énorme qui pourrait entraver le développement.
En conséquence, Docker. Inc. a développé son propre outil appelé libcontainer en tant que remplacement pour LXC. Le but de libcontainer était d'être une plate-forme agnostique outil permettant à Docker d’avoir accès à la plate-forme de base des conteneurs -blocs qui existent dans le système d'exploitation.
Libcontainer a remplacé LXC en tant que pilote d'exécution par défaut dans Docker 0.9.
Aujourd’hui, Dave Bartoletti, analyste chez Forrester, pense que seulement 10% des entreprises utilisent actuellement des conteneurs en production, mais près d’un tiers sont en phase de test. Docker a généré 762 millions de dollars de revenus en 2016. Les conteneurs ont transformé le monde de l’informatique car ils utilisent des systèmes d’exploitation partagés. Cette technologie permet à un data center ou à un fournisseur Cloud d’économiser des dizaines de millions de dollars par an, mais tout dépend bien sûr des risques qu’il serait prêt à prendre. Cependant, quelques préoccupations existent concernant l’assurance qu’auront les développeurs d’innover librement en utilisant des conteneurs. Les développeurs devraient pouvoir choisir les outils et les frameworks qu’ils souhaitent utiliser sans avoir à demander systématiquement la permission. L’utilisation d’un conteneur pourrait en effet étouffer la créativité d’un développeur… Le choix incombe, alors à l’entreprise d'investir ou non dans les conteneurs. Avec les avantages et les inconvénients qui se contrebalancent, tout peut se résumer au goût du risque.
Différence avec la virtualisation
Bien sûr, la conteneurisation a de nombreux avantages par rapport à la virtualisation mais on ne peut pas affirmer que cette technologie est 100% parfaite, elle a aussi son lot d’inconvénients. Ces deux schémas ci dessus, d’un environnement de virtualisation et d’un environnement de conteneurisation permettent de bien identifier les différences entre ces deux technologies.
Tout d’abord, les conteneurs partagent un seul et unique système d’exploitation, de ce fait, l’échange de données entre les conteneurs est plus simple et plus rapides que pour les VM. De plus comme chaque conteneur de ne contient pas de système d’exploitation propre à lui, les conteneurs sont donc réduits et prennent moins de place et moins de ressource Serveur (environ 10 fois plus petit qu’un VM). Le temps de création et de suppression d’un conteneur est par la même occasion réduit. Les conteneurs facilitent l’évolution technique, par exemple si dans un environnement de VM, l’on souhaite faire évoluer les OS de plusieurs VM, il faut le faire manuellement sur chaque Machines. Ce problème n’est pas présent pour la conteneurisation car toute l’infrastructure repose sur un seul système d’exploitation. Mais la conteneurisation peut avoir quelques inconvénients, comme tous les conteneurs ne reposent que sur un seul système d’exploitation, la diversification des systèmes d’exploitation n’est pas possible avec la conteneurisation, « ou est plus compliquée à mettre en place ». Les conteneurs sont isolés pour assurer la sécurité et empêcher les malwares de se transmettre entre les conteneurs, mais il est évident que les machines virtuelles seront toujours plus isolées que les conteneurs. Même si les conteneurs ont un grand nombre d’avantages, leur apparition ne sonne pas la fin de la virtualisation. Aujourd’hui, les machines virtuelles sont intégrées dans de nombreuses entreprises comportant des réseaux de grande taille. Pour utiliser entièrement la technologie de conteneurisation, ces entreprises devraient remanier tout leur système informatique ce qui est impensable. Mais de nouvelles entreprises ont vu le potentiel de la conteneurisation et on donc créer leur système informatique en fonction.
Les images
La figure présente une vue de haut niveau de la relation entre les images et conteneurs. Nous utilisons les commandes : "docker container run" et "docker service create" pour démarrer un ou plusieurs conteneurs à partir d'une seule image. Cependant, une fois qu’on a démarré un conteneur à partir d'une image, les deux constructions deviennent dépendantes les unes des autres et on ne peut pas supprimer l'image avant que le dernier conteneur l'utilisant ait été arrêté et détruit. Tenter de supprimer une image sans s'arrêter et la destruction de tous les conteneurs l’utilisant entraînera l’erreur suivante :
$ docker image rm <image-name> Error response from daemon: conflict: unable to remove repository reference \ "<image-name>" (must force) - container <container-id> is using its referenc\ ed image <image-id>
Les images sont généralement petites
Le but d'un conteneur est d'exécuter une application ou un service. Cela signifie que l'image à partir de laquelle le conteneur est créé doit contenir tous les systèmes d'exploitation et tous les fichiers requis pour exécuter l'application / service. Cependant, les conteneurs sont connus pour être rapide et léger. Cela veut dire que les images à partir desquelles ils sont construits sont généralement petites et dépouillées de toutes les parties non essentielles. Par exemple, les images Docker ne sont pas livrées avec 6 shells différents. Elles sont généralement livrées avec un seul shell minimaliste, ou aucun shell du tout. Elles ne contiennent pas non plus de kernel - tous les conteneurs s'exécutant sur le Docker hôte partagent un accès à au kernel de l’hôte du kernel. Pour ces raisons, on dit parfois que les images ne contiennent pas suffisamment de système d'exploitation (généralement elles contiennent que des fichiers et des objets du système de fichiers liés au système d'exploitation).
L’image officielle d’Alpine Linux Docker fait environ 4 Mo de taille et représente un bon exemple de la petite taille des images Docker. Cependant, un exemple plus typique pourrait être quelque chose comme l’image officielle Ubuntu Docker qui fait actuellement environ 120 Mo. Ceux-ci sont clairement dépouillés de la plupart des parties non essentielles ! Les images Windows ont tendance à être plus volumineuses que les images Linux, à cause de la façon dont le système d'exploitation Windows fonctionne. Par exemple, la dernière version de .NET image (microsoft / dotnet:latest) est supérieure à 2 Go lorsqu’elle est extrait sans compression. L'image de Windows Server 2016 Nano Server dépasse légèrement 1 Go lorsqu'elle est tirée et non compressé.
Un hôte Docker correctement installé n'a aucune image dans son référentiel local. Le référentiel d'images local sur un hôte Docker basé sur Linux est généralement situé à l'emplacement suivant:
/ var / lib / docker / <storage-driver>.
On peut vérifier si notre hôte Docker contient des images dans son référentiel local avec la commande suivante:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
Nom de l'image
Dans le cadre de chaque commande, nous devions spécifier quelle image extraire. Pour ce faire, nous avons besoin d’un peu de contexte sur la manière de stocker les images. Registres d'images : Les images Docker sont stockées en ligne dans des registres d'images. Le registre le plus courant est Docker Hub [6]. Il existe d'autres registres, y compris des registres tiers . Cependant, le client Docker est avisé et utilise par défaut Docker Hub.
Les registres d'images contiennent plusieurs référentiels d'images. À leur tour, les référentiels d’images peuvent contenir plusieurs images. Cela pourrait être un peu déroutant, alors la figure montre une image d'un registre d'images contenant 3 référentiels, et chaque référentiel contient une ou plusieurs images.
Dépôts officiels et non officiels
Docker Hub a également le concept de référentiels officiels et non officiels des dépôts.
Comme le nom l'indique, les référentiels officiels contiennent des images qui ont été vérifiées par Docker, Inc. Cela signifie qu'ils doivent contenir un code à jour et de haute qualité, sécurisé, bien documenté et conforme aux meilleures pratiques. Les référentiels non officiels sont tout le contraire.
Nom et marquage des images
Le format pour docker : image pull quand on travaille avec une image d'un dépôt officiel, c'est:
docker image pull <repository>: <tag>
Dans les exemples Linux précédents, nous avons téléchargé des images Alpine et Ubuntu. avec les deux commandes suivantes:
docker image pull alpine:latest and docker image pull ubuntu:latest
Les commandes suivantes montrent comment téléchargé différentes images d’un référentiel officiel:
$ docker image pull mongo:3.3.11 //This will pull the image tagged as `3.3.11` //from the official `mongo` repository. $ docker image pull redis:latest //This will pull the image tagged as `latest` //from the official `redis` repository. $ docker image pull alpine //This will pull the image tagged as `latest` //from the official `alpine` repository.
Quelques points à noter sur les commandes ci-dessus : Tout d’abord, si on ne spécifie pas de balise d’image après le nom du référentiel, Docker suppose que nous nous référons à la dernière image.
Deuxièmement, la dernière balise n’a aucun pouvoir magique! Juste parce qu'une image est étiquetée comme dernière ne garantit pas que c’est l’image est la plus récente dans un dépôt!
Images et couches
Une image Docker est juste un groupe de couches en lecture seule faiblement connectés. Docker s’occupe d’empiler ces couches et de les représenter comme un seul objet unifié. Il y a plusieurs façons de voir et d'inspecter les couches qui composent une image:
Toutes les images Docker commencent par une couche de base, et au fur et à mesure que des modifications sont apportées, le contenu est ajouté, les nouvelles couches sont ajoutées en haut.
Les hachages d'image (digests)
L’image n’est en réalité qu’un objet de configuration répertoriant les couches et ainsi que certaines métadonnées (telle que son nom par exemple).
Les couches constituant une image sont totalement indépendantes et n’ont aucune notion de faire partie d'une image collective.
Chaque image est identifiée par un identifiant de cryptage qui est un hachage de l'objet config. Chaque couche est identifiée par un identifiant de cryptage qui est un hachage du contenu qu’elle contient.
Cela signifie que changer le contenu de l'image, ou de l'une de ses couches, provoquera le changement des cryptages associés. En conséquence, les images et les couches sont immuables.
Lorsqu’on push et pull les images, nous compressons leurs couches pour économiser la bande passante. Mais la compression d’une couche change son contenu ! Cela signifie que le contenu du hachage ne correspond plus après l'opération de push et pull ! Ceci est évidemment un problème. Par exemple, lorsqu’on place une couche d’image sur Docker Hub, Docker Hub tente de vérifier que l'image est arrivée sans être altérée en route. Pour ce faire, il exécute un hachage sur la couche et vérifie s'il correspond au hash qui a été envoyé avec la couche. Parce que la couche a été compressée (modifiée) la vérification du hachage échouera.
Pour résoudre ce problème, chaque couche reçoit également un élément appelé hachage de distribution. C’est un hachage de la version compressée de la couche. Quand une couche est push et pull du registre, son hash de distribution est inclus, et c'est ce qui est utilisé pour vérifier que la couche est arrivée sans être altérée. Ce modèle de stockage à contenu adressable améliore considérablement la sécurité en nous offrant un moyen d’imprimer et de superposer des données après des opérations de push et pull. Cela évite aussi les collisions d'identifiants pouvant survenir si les identifiants d'image et de couche étaient générés de manière aléatoire.
Images multi-architecturales
Docker prend désormais en charge les images multi-plateformes et multi-architectures. Cela signifie qu’un référentiel d’images et une balise doivent avoir une image pour Linux sur x64 et Linux sur PowerPC, etc. Pour ce faire, l’API de registre prend en charge un fat manifest ainsi qu’une image manifest. Les fat manifests listent les architectures supportées par une image particulière, tandis que l'image manifest, liste les couches qui composent une image particulière.
Supposons qu’on utilise Docker sous Linux x64. Quand on pull une image depuis Docker hub, notre client Docker adresse les demandes d’API pertinentes à Docker API tournant sur Docker Hub. Si un fat manifest existe pour cette image, il sera analysé pour voir s’il existe une entrée pour Linux sur x64. Si elle existe, l'image manifest pour cette image est récupérée et analysée pour les couches réelles qui composent l'image. Les couches sont identifiées par leurs identifiants de cryptage et sont extraites du Registre “blob”.
Supprimer des images
Lorsqu’on n’a plus besoin d'une image, on peut la supprimer de notre hôte Docker avec la commande :
docker image rm. rm
Supprimer les images “pull” dans les étapes précédentes avec la commande:
docker image rm
Si l'image qu’on essaye de supprimer est utilisée par un conteneur en cours d'exécution, on ne pourra pas la supprimer. Il faudra Arrêter et supprimer tous les conteneurs avant d'essayer la suppression à nouveau. Un raccourci pratique pour nettoyer un système et supprimer toutes les images sur un Docker hôte est d’exécuter la commande docker image rm et lui transmettre une liste de tous les ID d'image sur le système en appelant l'image de menu fixe ls avec l'indicateur -q.
docker image rm $(docker image ls -q) -f
Création des conteneurs "à la main"
Création des fichiers disque des conteneurs
En un premier temps, je crée un fichier de 10 Gio = 10 240 * 1 Mio et je spécifie que tous les octets sont égaux à 0. Et ce, grâce à la commande :
dd if=/dev/zero of=/home/pifou/hmalti bs=1024k count=10240
Création du système de fichiers
Ici, j’associe un système de fichiers du type “ext4” à mon fichier. (Je le formate à ce “format”) :
mkfs.ext4 /home/pifou/hmalti/containter_hmalti
Montage du système de fichiers J’ai créé le répertoire qui contiendra les rootfs montés :
mount -t ext4 -o loop /home/pifou/hmalti/hmalti /mnt/container_hmalti
Déploiement de l'arborescence Debian
Il faut exporter le proxy de l'école dans la variable d’environnement correspondante pour pouvoir télécharger les paquetages.
export http_proxy=http://proxy.polytech-lille.fr:3128
Ensuite j’utilise l'outil debootstrap dans /mnt/container_hmalti pour créer une arborescence Debian de base.
debootstrap --include=apache2,vim,nano stable /mnt/container_hmalti
Puis il faut renseigner le fichier fstab du container_hmalti :
echo "proc /proc proc defaults 0 0" >> /mnt/container_hmalti/etc/fstab
Pour démonter le fichier :
umount /mnt/container_hmalti
Pour le remonter :
mount -t ext4 -o loop /home/pifou/hmalti/hmalti /mnt/container_hmalti
Afin de vérifier que le fichier est bien créé
ls /mnt/container_hmalti
Isolation du conteneur et création :
unshare -p -f -m -n -u chroot /mnt/container_hmalti /bin/sh -c "mount /proc ; /bin/bash"
Et à ce moment là, on peut vérifier que notre conteneur est bien isolé on peut lister les processus qui tournent dessus :
ps aux (on ne trouve que 3 processus, ce qui est logique )
De même pour ce qui est de l’adresse réseau de notre conteneur :
ip l (Dans le conteneur nous ne voyons pas les interfaces réseau de l'hôte car nous ne sommes pas dans le même espace de nom)
ls (on retrouve le contenu du conteneur et non celui de l’hôte)
Imposer des limitations aux conteneurs
Ici, le pid que l’on met est celui du processus du conteneur dans le namespace de l’hôte ( par exemple : si on fait un ps aux sur le conteneur et on voit :
Ça veut dire que le numéro du PID du processus fils d’unshare est : 3 dans le namespace du conteneur, il ne sera pas le même vis à vis du hôte, pour le voir on fait un petit ps aux et on voit :
Le PID du processus fils du unshare porte le numéro : 6410 Ce sera donc ce dernier PID que l’on mettra dans “tasks”
Restriction sur l’utilisation mémoire :
Le bout de code ci-dessous permet d'allouer 10K de mémoire toutes les secondes en boucle
mkdir /sys/fs/cgroup/memory/model1 echo 2048000 > /sys/fs/cgroup/memory/model1/memory.limit_in_bytes echo 2048000 > /sys/fs/cgroup/memory/model1/memory.memsw.limit_in_bytes echo 0 > /sys/fs/cgroup/memory/model1/memory.swappiness echo $pid > /sys/fs/cgroup/memory/test/tasks
- une boucle pour monitorer la mémoire :
while true ; do cat memory.stat | egrep 'total_cache |total_rss |total_swap |total_.*_anon' cat memory.usage_in_bytes echo '---' sleep 1 done
On s'aperçoit comme indiqué dans les documentations que memory.usage_in_bytes est approximatif, la vraie valeur étant : total_cache+total_rss+total_swap - le programme /tmp/alloc s'arrête sur 1945600 octets alloués - la boucle de monitoring s'arrête sur : total_cache 24576 total_rss 1970176 total_swap 0 total_inactive_anon 0 total_active_anon 1970176 2048000 --- - le noyau indique :
[90844.999641] Task in /test killed as a result of limit of /test [90844.999647] memory: usage 2000kB, limit 2000kB, failcnt 0 [90844.999649] memory+swap: usage 2000kB, limit 2000kB, failcnt 2445774 [90844.999650] kmem: usage 36kB, limit 9007199254740988kB, failcnt 0 [90844.999651] Memory cgroup stats for /test: cache:0KB rss:1964KB rss_huge:0KB mapped_file:0KB dirty:0KB writeback:0KB swap:0KB inactive_anon:1024KB active_anon:940KB inactive_file:0KB active_file:0KB unevictable:0KB
Explication de la structure du code
Le plus difficile pour moi fut de trouver la bonne structure et l’architecture qui répondait correctement au cahier des charges. L’erreur que j’ai commise est de ne pas avoir fait un schéma papier et ne pas avoir pris le temps de structurer et généraliser mon script pour qu’il s’adapte à tout utilisateur. Le plus important est d’avoir compris l’utilité et l’importance de cette technique dans un projet et d’en tirer une leçon.
Il y a 20 scripts, j’ai choisis de les diviser comme ce qui suit :
help : indique toutes les fonctionnalités, les commandes et flags associés
baleine help
create image : La commande pour simplement créer une image est :
< image create -i “nom_image” -s “taille_image” -r “chemin_image” -p “proxy” >
car on pourrait vouloir créer une image mais ne pas vouloir lancer un conteneur de dessus, d’où la séparation de la création du conteneur et de l’image.. Il faut noter que dans ce script, il crée l’image dans le PATH_BALEINE : /var/lib/baleine/images.
Cependant, la copie de l’image sur laquelle le conteneur est lancé se fait dans le PATH : /var/lib/baleine/containers/nom_container Il crée un manifest contenant le nom de l’image, sa taille, son chemin. Ainsi, on établit un ordre logique dans la structure. Et l’utilisation sera généralisée à tout utilisateur qui se sert de l’application. Ce script crée aussi un mannifest* (voir la définition en annexe) de l’image, contenant : son nom, sa taille et son chemin. create bridge : On crée un bridge grâce à la commande < bridge create -b “nom_bridge” -a “ADDR_IPV4” >
create container : ce script permet de lancer une instance et crée des interfaces réseau virtuelles relié au commutateur/bridge cité plus tôt. Et ce avec la commande :
< container create -i “nom_image” -c “nom_container” -b “nom_bridge” -a “ADDR_IPV4” -r “repertoire” -p ”programme”>
Mais avant ceci, il copie l’image dans : /var/lib/baleine/containers/nom_container Puis il la monte dans : /mnt/baleine/nom_container De même, il crée un manifeste contenant le nom du conteneur, l’image depuis laquelle il a été monté, le nom de son bridge, son PID ainsi que son "starting_time" ( pour savoir depuis quand le conteneur tourne ).
remove image : Ce script permet de détruire complètement une image et supprimer son manifest avec la commande :
< image remove “NAME_IMAGE_TO_REMOVE”>
remove container : ce script éradique l’instance et détruit son image disque et de ses caractéristiques, avec :
< container remove “NAME_CONTAINER_TO_REMOVE”>
remove bridge: Ici, on supprime simplement un bridge spécifique ( en entrant le nom de ce dernier en paramètre ).
< bridge remove “NAME_BRIDGE_TO_REMOVE”>
stop container : arrête une instance, et détruit naturellement ses ressources réseau. Mais son image disque et ses caractéristiques restent conservées dans un état stable pour qu’il puisse faire se lancer à nouveau depuis.
< container stop -c “NAME_CONTAINER_TO_STOP”>
list images: liste toutes les images qui ont été créées ( nom, taile , chemin ) , ceci se fait à partir du manifest.
< image list >
list containers : de même, ici on liste tous les conteneurs qui tournent (nom du conteneur , le nom de l’image qui l’a lancé, PID , temps d’exécution )
< container list >
list bridges: idem, on liste tous les bridges
< bridge list >
up/down bridge : ici nous pouvons mettre en up ou down un bridge
< bridge -b “NAME_BRIDGE” up > < bridge -b “NAME_BRIDGE” down >
Options ajoutées
- Pour faciliter l’utilisation, un help équivalent à un manuel a été mis en place, que l’on peut visualiser en exécutant la commande suivante : baleine.sh help
- Une option a été mise en place concernant les interfaces réseau lors de la création du conteneur. En effet, si l’on souhaite créer plusieurs interfaces sur un même conteneur avec plusieurs bridges, on arrive à gérer ceci, grâce à la création d’une arraylist en shell ( découverte très intéressante et surprenante ).
- J’ai rajouté une option qui permet à l’utilisateur de rentrer ses arguments dans n’importe quel ordre du moment où il met les drapeaux (Flags) correspondants devant. (L’utilitaire Help) l’aidera à les connaître.
- De même, il m’a été imposé de ne pas faire une gestion sophistiquée des images à base de système de fichiers à couche. Pour ce, j’utilise le système de fichier ext4, qui ne gère pas les couches
Amélioration possibles
- J’avais essayé pendant un moment de gérer des versions d'images en leur accordant un nombre aléatoire pour les différencier, mais ça m’a paru compliqué à gérer par la suite. Cependant, ça pourrait être une idée d’amélioration du script et de gestion de bordure et généralisation.
- Gérer une architecture différente de Debian (une architecture Redhat par exemple).
Fonctionnement de l'application
Images
Creation d'une image :
Suppression d'une image :
Liste des images :
Bridges
Creation d'un bridge et liste :
Suppression d'un bridge :
Conteneurs
Creation d'un conteneur :
Suppression d'un conteneur :
Liste des conteneurs en cours d'execution :
Mandataire inverse
Pour réaliser l'architecture avec un mandataire inverse apache2, on va mettre en place deux bridges, un sera considéré comme public, et l'autre comme étant privé. Le mandataire inverse sera présent sur le bridge considéré comme public, tandis que l'autre serveur apache2 (qui fera office de serveur HTTP) sera présent sur le bridge privé.
Le mandataire inverse sera également présent sur le bridge privé, ainsi il aura deux adresses ip. Une publique, et une autre privée. Lorsque l'on accèdera au mandataire inverse via son adresse ip publique, il va transmettre la requête au serveur HTTP non accessible.
Réalisons cela grâce à nos conteneurs, tout d'abord nous allons créer deux bridges :
On vérifie que les deux bridges sont correctement faits
Ensuite, nous allons créer deux images, une sera apache2_reverse et l'autre apache2. Puis nous allons monter l'image apache2_reverse dans le but de modifier sa configuration.
On modifie le fichier /etc/apache2/sites-enabled/000-default afin d'y rajouter les lignes suivantes :
ProxyPass / 192.168.42.2 ProxyPassReverse / 192.168.42.2
De cette manière, toutes les requêtes réalisées à 10.0.0.2 (l'ip du mandataire inverse) sera transmis au conteneur possédant le serveur HTTP à l'ip 192.168.42.2.
Maintenant que la configuration est terminée, on peut demarrer nos conteneurs :
Annexes
Ext4
Est une évolution du système de fichier ext3, qui est actuellement le système de fichier le plus utilisé sous Linux. Il présente de nombreux avantages et optimisations par rapport à l'ancienne version, tout en assurant une rétro-compatibilité. Ext4 est stable et est le système de fichier par défaut sous 9.10. Outre le fait qu'il puisse gérer les volumes d'une taille allant jusqu'à un exbioctet (260 octets), la fonctionnalité majeure de ext4 est l'allocation par extent qui permettent la pré-allocation d'une zone contiguë pour un fichier, pour minimiser la fragmentation. L'option extent est activée par défaut depuis le noyau Linux 2.6.23 ; avant cela, elle devait être explicitement indiquée lors du montage de la partition.
Debootstrap
Est un outil permettant d'installer un système de base Debian dans un sous-répertoire d'un autre système déjà installé. Il ne nécessite pas de CD d'installation, mais un accès à un référentiel Debian. Il peut également être installé et exécuté à partir d'un autre système d'exploitation. Ainsi, on peut par exemple utiliser debootstrap pour installer Debian sur une partition non utilisée d'un système Gentoo en cours d'exécution. Il peut également être utilisé pour créer un rootfs pour une machine d'une architecture différente, appelée "cross-debootstrapping". Il existe également une version largement équivalente écrite en C: cdebootstrap, qui est plus petite. Debootstrap ne peut utiliser qu'un seul référentiel pour ses packages. Si l’on doit fusionner des paquets provenant de différents référentiels (comme le fait apt) pour créer un rootfs, ou si l’on doit personnaliser automatiquement le rootfs, on utilise Multistrap.
cgroups : (control groups)
Est une fonctionnalité du noyau Linux pour limiter, compter et isoler l'utilisation des ressources (processeur, mémoire, utilisation disque, etc.). Un groupe de contrôle est une suite de processus qui sont liés par le même critère. Ces groupes peuvent être organisés hiérarchiquement, de façon que chaque groupe hérite des limites de son groupe parent. Le noyau fournit l'accès à plusieurs contrôleurs (sous-systèmes) à travers l'interface cgroup2. Par exemple, le contrôleur « memory » limite l'utilisation de la mémoire, « cpuacct » comptabilise l'utilisation du processeur, etc.
Les Control groups peuvent être utilisés de plusieurs façons :
- En accédant au système de fichier virtuel cgroup manuellement
- En créant et gérant des groupes à la volée en utilisant des outils tels que cgcreate, cgexec, cgclassify (de libcgroup)
- Le « démon de moteur de règles » qui peut automatiquement déplacer les processus de certains utilisateurs, groupes ou commandes vers un cgroup en respectant ce qui est spécifié dans la configuration.
- Indirectement à travers d'autres logiciels qui utilisent cgroups, tels que la virtualisation LXC8, libvirt, systemd, Open Grid Scheduler/Grid Engine9.
Unshare :
unshare() permet à un processus de dissocier les parties de son contexte d'exécution qui sont actuellement partagées avec d'autres processus. Une partie du contexte d'exécution, comme l'espace de noms, est implicitement partagée lorsqu'un processus est créé avec fork(2) ou vfork(2), pendant que d'autres parties, comme la mémoire virtuelle, peuvent être partagées par une demande explicite lors de la création d'un processus avec clone(2). Le principal intérêt de unshare() est de permettre à un processus de contrôler son contexte d'exécution partagé sans avoir à créer un nouveau processus.
chroot (change root):
Est un appel système qui a également donné son nom à une commande des systèmes d'exploitation Unix permettant de changer le répertoire racine d'un processus de la machine hôte. En d'autres termes, la commande “chroot” permet de changer le répertoire racine vers un nouvel emplacement. Chroot peut être utilisé dans deux cas :
- En tant que bascule d'environnement pour prendre le contrôle d'une installation Linux depuis un autre système.
- En tant que prison pour empêcher un utilisateur de remonter dans l'arborescence pour l'emprisonner dans un répertoire spécifique (ce qui peut être utilisé avec un serveur FTP pour que les utilisateurs ne remontent pas dans l'arborescence du système).
Les Veth
Virtual Ethernet permet aux partitions logiques de communiquer entre elles sans avoir à affecter de matériel physique aux partitions logiques.
On peut créer des adaptateurs Ethernet virtuels sur chaque partition logique et les connecter à des réseaux locaux virtuels. Les communications TCP / IP sur ces réseaux locaux virtuels sont acheminées via le microprogramme du serveur.
Un adaptateur Ethernet virtuel fournit une fonction similaire à celle d’un adaptateur Ethernet 1 Gb. Une partition logique peut utiliser des adaptateurs Ethernet virtuels pour établir plusieurs connexions interpartition haut débit au sein d'un même système géré. Les partitions logiques AIX, IBM® i, Linux et Virtual I / O Server et les environnements Windows intégrés à la plate-forme System i peuvent communiquer entre eux via TCP / IP via les ports de communication Ethernet virtuels.
Les cartes Ethernet virtuelles sont connectées à un commutateur Ethernet virtuel de type IEEE 802.1q (VLAN). Grâce à cette fonction de commutateur, les partitions logiques peuvent communiquer entre elles à l'aide d'adaptateurs Ethernet virtuels et en affectant des ID de VLAN leur permettant de partager un réseau logique commun. Les cartes Ethernet virtuelles sont créées et les attributions d'ID de VLAN sont effectuées à l'aide de la console HMC. Le système transmet les paquets en les copiant directement de la mémoire de la partition logique émettrice vers les mémoires tampons de réception de la partition logique réceptrice sans aucune mise en mémoire tampon intermédiaire du paquet.
Le nombre d'adaptateurs Ethernet virtuels autorisés pour chaque partition logique varie en fonction du système d'exploitation.
AIX 5.3 et versions ultérieures prennent en charge jusqu'à 256 cartes Ethernet virtuelles pour chaque partition logique. La version 2.6 du noyau Linux prend en charge jusqu'à 32 768 cartes Ethernet virtuelles pour chaque partition logique. Chaque partition logique Linux peut appartenir à un maximum de 4 094 réseaux locaux virtuels.
MANIFEST
Un MANIFEST contient des informations sur une image, telles que les couches, la taille et le digest (hash). La commande docker manifest fournit également aux utilisateurs des informations supplémentaires telles que le système d'exploitation et l'architecture pour lesquelles une image a été créée.
Une liste de manifests est une liste de couches d'image créée en spécifiant un ou plusieurs noms d'image (idéalement plusieurs). Il peut ensuite être utilisé de la même manière qu'un nom d'image dans les commandes docker pull et docker run, par exemple.
Idéalement, une liste de manifestes est créée à partir d'images de fonctions identiques pour différentes combinaisons os / arch. Pour cette raison, les listes de manifestes sont souvent appelées «images multi-arch». Cependant, un utilisateur peut créer une liste de manifestes pointant vers deux images: une pour Windows sur amd64 et une pour Darwin sur amd64.
API REST
Une API compatible REST, ou « RESTful », est une interface de programmation qui fait appel à des requêtes HTTP pour obtenir (GET), mettre à jour PUT), publier (POST) et supprimer (DELETE) des données ou bien simplement interagir avec des services.
Aujourd’hui, l’utilisation croissante du Cloud et des architectures micro-services Web font que l’API REST est devenu un standard de facto. Pour donner un exemple, la plupart des grandes entreprises du Web exposent une API REST afin de pouvoir avoir accès à leurs données et/ou interagir avec leurs services.
Dans le cas spécifique de Docker. Le moteur expose une API REST, cette dernière est utilisée par l’interface en ligne de commande. Ce qui est intéressant, c’est qu’il suffit maintenant de bind le moteur Docker sur une adresse IP et un port, et on peut interagir avec le moteur à distance. En pratique, c’est que qui est fait quand on orchestre Docker avec Kubernetes par exemple.
La plupart des commandes du clien ont un équivalent direct en point de terminaison d’API (par exemple, la commande docker ps va faire un GET /containers au moteur Docker).
Les SWAP
L'espace d'échange, aussi appelé par son terme anglais swap space ou simplement swap, est une zone d'un disque dur faisant partie de la mémoire virtuelle de l’ordinateur. Il est utilisé pour décharger la mémoire vive physique (RAM) de l’ordinateur lorsque celle-ci arrive à saturation.
L'espace d'échange, se trouve généralement sous une forme de partition de disque dur – on parle alors de partition d'échange. Il peut aussi se présenter sous forme de fichier – on parle alors de fichier d'échange.
Par défaut, la plupart des distributions calculent et s'attribue automatiquement un espace d'échange suffisant lors de leurs installation (sa taille est régulièrement égale au double de la taille de la RAM).
Namespace :
Le terme espace de noms (namespace) désigne en informatique un lieu abstrait conçu pour accueillir des ensembles de termes appartenant à un même répertoire, comme dans l'exemple suivant où les espaces de noms sont nommés « Jean-Paul » et « Jean-Pierre » :
Jean-Paul
Jean-Pierre
Mes livres
Mes BD
Mes CD
Mes CD
Propriétés :
Un espace de noms peut être vu comme une fonction F qui, à un ensemble de symboles S, associe un ensemble O d'objets (à prendre au sens large). Ces objets peuvent être des entiers, des réels, des objets informatiques, des lieux, des personnes, etc. En programmation informatique, les espaces de noms sont généralement des injections seulement car s'il y a deux objets nommés distincts alors leur nom est différent et un objet nommé peut avoir plusieurs noms. Dans le cas d'un réseau Ethernet, les espaces de noms sont des bijections car chaque carte réseau a une adresse Ethernet unique et, à partir d'une telle carte, il est possible de retrouver l'adresse Ethernet correspondante. Enfin, s'il y a un ensemble d'écoliers dans une classe et considérons leur prénom comme ensemble de symboles, l'espace de noms est surjectif seulement. En effet, tous les écoliers ont un prénom, mais plusieurs peuvent avoir le même prénom. Ce dernier cas, arrive parfois en programmation dans plusieurs situations (ex. : table de hachage), ce qui produit des collisions. Pour distinguer les objets nommés, on peut étendre les noms par des préfixes (dans le cas de la programmation) ou ajouter le nom de la personne ou son adresse (dans le cas des écoliers). Dans ce cas, on parle d'extension de l'espace de noms. On peut aussi remplacer les noms ambigus par des pseudonymes ou des alias. Dans ce cas, on parle de modifier l'espace de noms. Système de fichiers Le système de fichiers (par exemple: zfs) permet l'existence de volumes virtuels (datasets sous zfs), qui partagent un même espace physique, mais peuvent être montés à des endroits différents d'une même hiérarchie de fichiers, ce qui permet la dé-duplication entre des volumes différents et a l'avantage de ne pas imposer à chaque volume virtuel de réserver un espace fixe difficile à modifier après coup.
Documents Rendus
https://archives.plil.fr/hmalti/projet_gestion_containers
https://projets-ima.plil.fr/mediawiki/images/d/de/Rapport_projet_IMA4_hmalti.pdf