📘 Principe de la conteneurisation
1. Introduction
Nous avons vu en introduction qu’un besoin à la fois pour le développement et pour la mise en exploitation était de pouvoir déployer un logiciel dans un environnement. Pour cela, nous avons vu qu’un logiciel doit pouvoir s’entourer de toutes les dépendances et autres logiciels dont il a besoin.
En effet, la principale difficulté du déploiement réside dans le constat suivant : si l’environnement considéré ne fournit pas l’entourage logiciel requis, alors le logiciel ne pourra pas directement y fonctionner. Si l’entourage logiciel requis est présent, il reste nécessaire que chaque bibliothèque ou logiciel qu’il comporte soit dans la bonne version requise par notre logiciel à déployer, avec le risque que ce dernier se comporte différemment en fonction de ces versions.
À ce premier problème s’ajoute la préoccupation suivante : un logiciel déployé pourra essayer d’accéder à toutes les ressources de l’environnement où il s’exécute − données, logiciels, matériel − y compris des ressources auxquelles il n’est pas censé accéder. Cela peut poser des problèmes en cas d’effets de bord non prévus (par exemple le logiciel se met à modifier les mauvaises données à cause d’un défaut dans le code), ou bien dans un souci de sécurité (le logiciel peut essayer d’accéder à des informations confidentielles).
Dans ce contexte, ce chapitre a pour but de vous présenter une technique de déploiement appelée la conteneurisation, qui permet de définir tout un environnement d’exécution sous la forme d’un conteneur, et d’y installer un (ou plusieurs) logiciel(s). Ainsi, quel que soit le contexte dans lequel un conteneur sera déployé, les logiciels contenus dans ce dernier seront toujours exécutables avec le même entourage logiciel prêt à l’emploi. En outre, un conteneur est isolé de l’environnement dit d’accueil dans lequel il est exécuté, ce qui évite à un logiciel d’aller agir sur l’environnement d’accueil alors que ce n’est pas souhaité.
Dans ce cours, nous utiliserons la conteneurisation comme mode opératoire pour la phase de mise en exploitation d’un logiciel.
| La conteneurisation peut également servir lors de la phase de développement d’un logiciel, notamment afin de déployer dynamiquement les outils de construction de logiciels nécessaires dans un processus d’intégration continue. |
2. Problèmes de l’exécution directe dans l’environnement
Dans le contexte posé, on peut faire deux observations :
-
Pour fonctionner, la plupart des logiciels sont construits et configurés avec des hypothèses plus ou moins fortes sur leur environnement. En premier lieu, un logiciel est souvent contraint à ne pouvoir fonctionner sur un type particulier de matériel (processeur ARM, processeur x86, etc.) et sur une famille particulière de systèmes d’exploitation (systèmes Linux, systèmes Windows, systèmes Android, etc.). Ensuite, un logiciel peut nécessiter avoir à sa disposition des bibliothèques, données et autres logiciels particuliers pour son fonctionnement. En outre, le logiciel peut faire des hypothèses quant à l'emplacement de ces différentes resources sur le système de fichier, et quant aux versions des bibliothèques et logiciels qu’il utilise.
Ainsi, si un logiciel est toujours exécuté dans le même environnement, alors ces hypothèses ne poseront pas de problème. Mais, bien entendu, un logiciel est presque toujours destiné à pouvoir s’exécuter dans des environnements différents, ne serait-ce que sur différentes machines.
-
Malgré toutes les précautions que l’on puisse prendre lors d’un développement logiciel, il est toujours possible qu’un logiciel exprime un comportement indésirable. Cela peut provenir d’un défaut dans le logiciel, d’une faille de sécurité exploitée par un utilisateur, ou d’un problème indirect provenant d’une dépendance utilisée par le logiciel. Or, un comportement indésirable peut potentiellement modifier tout ce que contient l’environnement où le logiciel s’exécute (sauf utilisation de certaines techniques, voir encart "Limiter le champ d’action d’un logiciel dans un environnement" ci-après). Un risque est donc par exemple de voir le logiciel supprimer ou modifier des données critiques accessibles depuis le système.
3. Principe de la conteneurisation
Pour répondre aux problèmes mentionnés, à savoir d’une part la difficulté de produire un logiciel pouvant s’adapter à tout environnement, et d’autre part la gestion des risques que peut encourir un environnement en cas de comportement indésirable d’un logiciel, une technique consiste à installer et exécuter un logiciel au sein d’un conteneur.
Un conteneur est un "mini environnement" cloisonné dans lequel peut s’exécuter un logiciel, au sein d’un environnement existant appelé "d’accueil". Ce mini environnement comporte un système d’exploitation restreint, ainsi qu’un ensemble de données, logiciels et bibliothèques qui lui sont propres et qui ne sont pas partagées avec l’environnement d’accueil. Il n’est d’ailleurs par défaut pas possible d’accéder aux fichiers de l’environnement d’accueil depuis le conteneur.
Pour compléter cette isolation, le conteneur n’a pas accès à l’interface réseau de l’environnement d’accueil, et à la place possède une ou plusieurs interfaces réseau virtuelles. Cette interface permet au conteneur d’exister sur un réseau informatique virtuel local, ce qui permet à des conteneurs de communiquer entre eux par de simples accès réseau. Cette interface est aussi reliée à l’interface réseau de l’environnement d’accueil pour pouvoir accéder au réseau local et/ou à Internet, et pour pouvoir si souhaité recevoir des connexions réseau provenant de l’extérieur.
| Nous verrons ultérieurement les notions de réseau informatique et d’interface réseau dans des chapitres dédiés. |
En somme, un logiciel qui s’exécute dans un conteneur a l’impression d’être exécuté sur un tout autre ordinateur que l’environnement d’accueil, et n’a plus la connaissance de ce dernier.
| Une autre approche pour créer des environnements isolés d’un environnement d’accueil est appelée la virtualisation, avec des technologies comme QEMU/KVM ou VirtualBox. La virtualisation consiste à créer un ordinateur virtuel entier − y compris la partie matérielle − pour y installer système d’exploitation et logiciels. |
| La conteneurisation appartient à une famille d’approches que l’on nomme parfois virtualisation au niveau du système d’exploitation (OS-level virtualization en anglais). Dans ce cours, on évitera d’utiliser le terme "virtualisation" lorsqu’on parle de conteneurs, afin de limiter la confusion avec la "vraie" virtualisation (cf point précédent). |
Reprenons le modèle simplifié précédent et cette fois-ci considérons que nous fassions appel à des conteneurs afin d’exécuter certains logiciels. On obtient le schéma suivant :
Deux conteneurs A et B sont représentés et s’exécutent au sein d’un environnement d’accueil.
Chacun de ces conteneurs possède un ensemble de logiciels qui, lorsque exécutés, n’ont par défaut accès que aux ressources présentes au sein du conteneur.
Cette architecture facilite la gestion des dépendances.
Par exemple, si l’environnement d’accueil possède une version particulière d’une bibliothèque ou d’un logiciel X qui ne serait pas compatible avec ce dont nécessite un logiciel du conteneur A, cela ne pose pas de problème car on peut simplement installer la version exacte requise de X au sein du conteneur A.
De même, si les conteneurs A et B nécessitent chacun une version différente de X, alors il suffit que chaque conteneur possède sa propre copie de X à la version souhaitée.
On souhaite un déploiement équivalent à ce qui a été proposé dans le chapitre sur l’exécution de logiciels, mais en reposant cette fois-ci sur de la conteneurisation pour isoler les logiciels utilisés de l’environnement d’accueil.
La solution choisie consiste à :
-
Créer un premier conteneur pour y mettre Simplestblog et ses dépendances.
-
Créer un second conteneur pour y mettre MariaDB, ses dépendances et ses données.
On obtient donc :
À ce stade nous n’avons pas encore vu comment faire pour se connecter au port d’écoute du conteneur Simplestblog (ici, 5000), ni comment faire pour que Simplestblog puisse contacter MariaDB situé dans un autre conteneur.
Pour voir ces aspects-là, rendez-vous dans les chapitres ultérieurs sur la mise en réseau des conteneurs.
|
On souhaite un déploiement de Automatic-Image-Converter, mais là aussi en reposant sur de la conteneurisation pour isoler notre logiciel.
La solution choisie consiste en un unique conteneur dans lequel mettre Automatic-Image-Converter avec une plateforme d’exécution Java permettant de l’exécuter.
On obtient donc :
| À ce stade nous n’avons pas encore vu comment faire pour interagir avec les répertoires de travail situés à l’intérieur du conteneur, rendez-vous dans le chapitre sur la gestion des données des conteneurs. |
4. Principe de l’image de conteneur
Supposons que l’on prépare un conteneur contenant exactement le nécessaire pour exécuter un logiciel donné : on installe les bonnes bibliothèques et les bons logiciels aux bonnes versions, on prépare l’arborescence de fichiers d’une manière précise et on dépose des données aux bons endroits. On installe alors notre logiciel dans ce conteneur, et on constate qu’il est capable de fonctionner parfaitement au sein du "mini environnement" du conteneur. Notre logiciel est ainsi capable de fonctionner sans se soucier des particularités de l’environnement d’accueil, et en limitant fortement sa capacité à altérer cet environnement de manière indésirable.
Cependant la création d’un conteneur en partant de zéro est une tâche laborieuse. Ce serait donc rébarbatif de devoir repartir de zéro à chaque fois qu’un logiciel doit être déployé dans un nouvel environnement d’accueil.
Il est heureusement possible de facilement réutiliser un conteneur une fois qu’il a été créé, en le stockant sous la forme d’une image de conteneur. Une image de conteneur (ou simplement image) est obtenue en "gelant" le contenu d’un conteneur sous la forme d’un fichier compressé, auquel on ajoute un certain nombre de métadonnées. Ainsi, pour créer un nouveau conteneur à partir d’une image de conteneur, on effectue une simple copie à l’écriture (expliqué dans le chapitre sur la création de conteneurs) du contenu de l’image. On obtient un nouveau conteneur, identique à celui qui avait été gelé sous forme d’image au départ. Il est ensuite possible de créer autant de conteneurs que souhaité à partir d’une image de conteneur (un peu comme on crée des objets à partir d’une classe en programmation orientée objet).
| Dans la pratique courante, on ne crée jamais de conteneur à partir de zéro, et on crée toujours un conteneur à partir d’une image de conteneur (expliqué là aussi dans le chapitre sur la création de conteneurs). |
Une fois produite ou récupérée, une image de conteneur est toujours stockée au sein d’un stockage local d’images au sein de l’environnement d’accueil. Seules les images de ce stockage local peuvent être utilisées pour créer un nouveau conteneur. Il est également possible de créer un conteneur à partir d’une image de conteneur située sur un registre de conteneurs, qui est un stockage d’images distant accessible par le réseau (présenté plus en détail plus loin dans ce chapitre).