📘 Montage de répertoires/fichiers dans un conteneur
1. Problématique
Comme expliqué dans le chapitre précédent, un conteneur est un environnement simplifié possédant notamment un système de fichiers. Par défaut, ce système de fichiers contient les données présentes dans l’image de conteneur utilisée comme base pour créer un conteneur (ce qui inclut généralement tous les fichiers du système d’exploitation utilisé comme base dans l’image).
Mais un logiciel déployé dans un conteneur va également :
-
utiliser des données de différentes formes : fichiers de configurations, données d’entrée pour un calcul ou un traitement, données à (re)distribuer, clés de chiffrement, etc.
-
produire des données de différentes formes : données persistées/stockées, résultats de calculs, etc.
Comme vu dans les chapitres précédents, un début de déploiement du logiciel Simplestblog à l’aide de deux conteneurs donnerait la situation suivante :
On peut voir ici que :
-
Dans le conteneur pour Simplestblog, le processus a besoin de lire un fichier de configuration qui lui a été préparé.
-
Dans le conteneur pour la base de données, MariaDB, le processus a besoin de lire et écrire les fichiers où il stocke les tables de la base de données.
Comme vu dans les chapitres précédents, un début de déploiement du logiciel Automatic-Image-Converter à l’aide d’un conteneur donnerait la situation suivante :
On peut voir ici que dans le conteneur prévu le processus Automatic-Image-Converter a besoin :
-
De lire le contenu du répertoire d’entrée dans lequel on lui dépose des photos à convertir,
-
D'écrire dans le répertoire de sortie les images produites suite aux opérations de conversion.
Pour fournir ou récupérer les données requises ou produites par un logiciel conteneurisé, une possibilité est d’utiliser la copie de fichiers vers (ou depuis) un conteneur, comme vu avec la commande podman container cp.
Cependant, deux problèmes apparaissent avec cette manière de faire :
-
Cela nécessite un grand nombre d’actions manuelles tout au long de la vie d’un logiciel conteneurisé, pour lui fournir des fichiers lorsqu’il en a besoin, ou pour récupérer sans cesse des données qu’il aurait produites.
-
Les données d’un conteneur disparaissent dès la supression de ce dernier. Or, nous verrons qu’il est très courant − et même parfois souhaitable − de devoir supprimer un conteneur, par exemple effectuer une mise à jour logicielle à l’aide d’un conteneur "tout neuf". Et si les données de travail d’un conteneur n’ont pas été "rapatriées" avant la supression du conteneur, elles sont perdues !
Dans ce chapitre, pour résoudre ces deux problèmes, nous allons voir un nouveau méchanisme permettant de gérer les données d’un conteneur : le montage de répertoires/fichiers.
2. Caractère éphémère des données d’un conteneur
Avant d’étudier le principe du montage, creusons un peu une situation posée un peu plus haut : pourquoi arrive-t-il que l’on doive supprimer un conteneur ?
-
Mise à jour du système d’exploitation. Un conteneur est créé à partir d’une image de conteneur dans laquelle se trouve généralement un système d’exploitation (par exemple "Ubuntu 22.04"). Il peut arriver pour des raisons de sécurité ou pour des raisons techniques de nécessiter une version plus récente d’un système d’exploitation (par exemple "Ubuntu 224.04"), voire un système d’exploitation différent (par exemple "Fedora Server 42"). Dans ce cas, la seule solution est de créer un nouveau conteneur à partir d’une image qui contient le système d’exploitation souhaiter, puis de jeter l’ancien conteneur.
-
Mise à jour du logiciel déployé. Il est possible que le logiciel que l’on déploie soit déjà distribué à l’aide d’une image de conteneur pré-construite, et qu’on utilise cette image pour le déploiement. Dans ce cas, il est courant qu’une nouvelle image de conteneur soit distribuée pour chaque nouvelle version du logiciel. Ainsi, si on souhaite mettre à jour le logiciel, il faudra nécessaire créer un tout nouveau conteneur à partir de la nouvelle image de conteneur qui contient la nouvelle version du logiciel déployé, puis jeter l’ancien conteneur.
-
Remise à zéro. Parfois en s’exécutant un logiciel crée un certain nombre de fichiers temporaires, comme des fichiers de cache, qui peuvent s’accumuler dans le conteneur. On trouve également des situations où un logiciel se trompe et abime par erreur des parties importantes de son conteneur (suppression de fichiers importants, création d’un état corrompu, etc.). Des situations de ce type peuvent justifier de faire une complète "remise à zéro" en supprimant un conteneur et en le recréant à l’identique.
Pour être préparé à faire face à ce type de situations, il est d’usage de considérer un conteneur comme une entité éphémère et "jetable", qu’on est susceptible de supprimer et recréer à tout moment. Cette pratique courante est rendue possible par la combinaison de deux choses :
-
d’une part, la possibilité de créer une image de conteneur réutilisable, qui permet de recréer un nouveau conteneur avec presque aucun effort à partir d’une base figée − ce principe sera étudié en fin de semestre.
-
d’autre part, la possibilité de persister les données d’un conteneur à l’aide du montage de répertoires/fichiers, qui permet de ne pas perdre les données de travail d’un conteneur lors de sa suppression − ce principe est présenté dans la suite de ce chapitre.
3. Principe du montage de répertoire/fichier
Pour fournir des fichiers à un logiciel dans un conteneur, ou pour récupérer des fichiers produits par un logiciel dans un conteneur, il est possible de partager des fichiers entre l’environnement d’accueil et le conteneur par un montage de répertoire/fichier (en anglais bind mount).
3.1. Montage de répertoire
Voyons le principe d’un montage de répertoire :
-
Soit un répertoire
host_datadans l’environnement d’accueil. -
Lors de la création d’un conteneur, on prépare un répertoire vide
container_dataau sein du conteneur, et on active un montage du répertoirehost_data(de l’environnement d’accueil) au sein du répertoirecontainer_data(du conteneur). -
Dès lors, ces deux répertoires sont liés et contiendront toujours exactement les même fichiers : tout fichier présent dans
container_data(donc accessible depuis le conteneur) sera également présent danshost_data(donc accessible depuis l’environnement d’accueil), et inversement. Il en va de même pour toute suppression ou modification de fichier. En fait,host_dataetcontainer_datasont désormais le même répertoire (même avec des noms différents), accessible depuis deux environnements différents, à savoir l’environnement d’accueil et le conteneur.
Le montage de répertoire devient alors un puissant outil pour trois types de situations :
-
Confier des données à un conteneur: si depuis l’environnement d’accueil on dépose un fichier dans
host_data(par exemple un fichier de configuration), alors ce fichier sera accessible et utilisable par un logiciel exécuté dans le conteneur. -
Récupérer des données produites par un conteneur: si un logiciel dans le conteneur dépose un fichier dans
container_data(par exemple le résultat d’un calcul), alors ce fichier sera directement accessible depuis l’environnement d’accueil. -
Persister les données d’un conteneur: si le conteneur se retrouve supprimé (ce qui inclut le répertoire
container_data), le répertoirehost_dataest lui préservé ainsi que tout son contenu.
L’illustration ci-dessus présente trois conteneurs dans le même environnement d’accueil.
On s’intéresse à trois répertoires de l’environnement d’accueil, à savoir /data/x, /data/y et /data/z.
On souhaite que les conteneurs A et B puissent accéder à /data/x, que le conteneur B puisse accéder à /data/y, et que le conteneur C puisse accéder à /data/z.
On choisit donc, lors de la création de ces conteneurs, de réaliser les montages (représentés par les traits pointillés avec des extrémités en losanges) : /data/x est monté à la fois au sein du répertoire /foo au sein du conteneur A, et au sein du répertoire /bar du conteneur B.
On fait de même avec les autres répertoires et conteneurs : /data/y est monté dans /do dans le conteneur B, et /data/z est monté dans /wal dans le conteneur C.
Une fois ces montages en place, les situations suivantes deviennent possibles :
-
Si un fichier est déposé dans le répertoire
/data/yde l’environnement d’accueil, alors ce fichier sera accessible depuis le répertoire/dodu conteneur B. -
Si un fichier est déposé dans le répertoire
/dodu conteneur B, alors ce fichier sera accessible depuis le répertoire/data/yde l’environnement d’accueil. -
Si un fichier est déposé dans le répertoire
/data/zde l’environnement d’accueil, alors ce fichier sera accessible depuis le répertoire/waldu conteneur B. -
Si un fichier est déposé dans le répertoire
/waldu conteneur C, alors ce fichier sera accessible depuis le répertoire/data/zde l’environnement d’accueil. -
Si un fichier est déposé dans le répertoire
/data/xde l’environnement d’accueil, alors ce fichier sera accessible :-
depuis le répertoire
/foodu conteneur A, -
depuis le répertoire
/bardu conteneur B.
-
-
Si un fichier est déposé dans le répertoire
/foodu conteneur A, alors ce fichier sera accessible :-
depuis le répertoire
/data/xde l’environnement d’accueil, -
depuis le répertoire
/bardu conteneur B. -Si un fichier est déposé dans le répertoire/bardu conteneur B, alors ce fichier sera accessible : -
depuis le répertoire
/data/xde l’environnement d’accueil, -
depuis le répertoire
/foodu conteneur A.
-
| Comme vu dans cette illustration, il est possible pour un même répertoire ou fichier de l’environnement d’accueil d’être simultanément monté dans plusieurs conteneurs. Cela peut par exemple servir à un conteneur de produire des fichiers qui sont ensuite récupérés par un second conteneur. |
Automatic-Image-Converter opère sur un répertoire d’entrée contenant les fichiers d’images à traiter, et un répertoire de sortie où enregistrer les images une fois converties. Ces deux répertoires doivent être accessibles depuis l’extérieur si on souhaite pouvoir confier de nouvelles images à l’application, et récupérer les résultats produits.
On choisit donc de préparer un conteneur pour lequel on effectue deux montages de répertoire :
-
Le répertoire
/data/input_imagesde l’environnement d’accueil est monté dans le répertoire/inputsdu conteneur, -
Le répertoire
/data/output_imagesde l’environnement d’accueil est monté dans le répertoire/outputsdu conteneur.
On configure ensuite Automatic-Image-Converter pour utiliser /inputs comme répertoire d’entrée, et /outputs comme répertoire de sortie.
On obtient la situation suivante :
Ainsi, de manière transparente, tout utilisateur ou logiciel qui dépose des fichiers dans le répertoire /data/input_images de l’environnement d’accueil pourra ensuite récupérer les images converties dans /data/output_images.
Sans plus avoir besoin d’utiliser podman container cp, Automatic-Image-Converter n’est plus totalement isolé.
3.2. Montage de fichier
Voyons ensuite le principe d’un montage de fichier, très similaire au montage de répertoire :
-
Soit un fichier
host_filedans l’environnement d’accueil. -
Lors de la création d’un conteneur, on active un montage du fichier
host_file(de l’environnement d’accueil) sous la forme d’un fichiercontainer_file(du conteneur). -
Dès lors, ces deux fichiers sont liés et contiendront toujours exactement les même données. En fait,
host_fileetcontainer_filesont désormais le même fichier, mais accessible depuis deux environnements différents, à savoir l’environnement d’accueil et le conteneur.
Le montage de fichier est très utile pour les mêmes trois types de situations que le montage de répertoire :
-
Confier des données à un conteneur: si depuis l’environnement d’accueil on prépare des données dans un fichier
host_file(par exemple un fichier de configuration), alors ce fichier sera accessible et utilisable par un logiciel exécuté dans le conteneur. -
Récupérer des données produites par un conteneur: si un logiciel dans le conteneur écrit des données dans le fichier
container_file(par exemple le résultat d’un calcul), alors ce ces données seront accessibles depuis l’environnement d’accueil via le fichierhost_file. -
Persister les données d’un conteneur: si le conteneur se retrouve supprimé (ce qui inclut le fichier
container_file), le fichierhost_fileest lui préservé ainsi que tout son contenu.
Simplestblog requiert un fichier de configuration pour fonctionner. On souhaite préparer ce fichier depuis l’environnement d’accueil, pouvoir facilement mettre à jour ce fichier dans le futur, et s’assurer que ce fichier soit préservé en sécurité.
On choisit donc de créer le conteneur de Simplestblog avec un montage de fichier :
-
Le fichier
/data/simplestblog/test_config.pyde l’environnement d’accueil est monté comme fichier/config.pydans le conteneur.
On configure ensuite Simplestblog pour utiliser /config.py comme fichier de configuration.
Ensuite, le système de base de données que nécessite Simplestblog, à savoir MariaDB, a besoin d’écrire et de lire le contenu de la base de données au sein d’un répertoire à l’emplacement /var/lib/mysql.
On souhaite s’assurer que le contenu de la base de données soit préservé en sécurité.
On choisit donc de créer le conteneur de MariaDB avec un montage de répertoire :
-
Le répertoire
/data/simplestblog/database_datade l’environnement d’accueil est monté comme répertoire/var/lib/mysqldans le conteneur.
On obtient la situation suivante :
Ainsi, de manière transparente :
-
Le fichier de configuration peut facilement être modifié directement depuis l’environnement d’accueil,
-
Le fichier de configuration sera préservé en cas de suppression de conteneur,
-
Les données de MariaDB seront préservées en cas de suppression de conteneur.
Le tout sans plus avoir besoin d’utiliser podman container cp.
4. Mise en œuvre d’un montage de répertoire/fichier avec Podman
Le montage d’un répertoire/fichier se fait lors de la création d’un conteneur (par exemple lors d’un appel à podman container run) à l’aide de l’option suivante :
--mount type=bind,source="<répertoire/fichier accueil>",target="<répertoire/fichier conteneur>"
Il ne faut surtout pas ici mettre d’espace avant ou après les caractères égal = et virgule , !
|
avec :
-
<répertoire/fichier accueil>: un chemin vers un répertoire/fichier au sein de l’environnement d’accueil, -
<répertoire/fichier conteneur>: un chemin valide au sein du conteneur, au sein duquel sera monté le répertoire/fichier spécifié.
Même s’il existe plusieurs types de montages dans Podman, on ne verra dans ce cours que le montage de répertoire/fichier, qui se déclare toujours avec type=bind.
|
On souhaite créer un conteneur pour déployer Automatic-Image-Converter. Il nous faut pour cela faire en sorte que deux répertoires de l’environnement d’accueil soient montés au sein du conteneur : le répertoire d’entrée où sont déposées les images, et le répertoire de sortie où les conversions sont générées.
On suppose que le logiciel a déjà été préparé sous forme d’image de conteneur, et que l’image est disponible dans le registre de conteneurs du Gitlab Nantes Université sous le nom developpement_exploitation/resources/automatic-image-converter.
Cette image fait certaines hypothèses : le répertoire d’entrée où déposer les images doit se trouver dans le conteneur à l’emplacement /inputs, le répertoire de sortie doit se trouver dans le conteneur à l’emplacement /outputs, le format de conversion est spécifié par la variable d’environnement FORMAT, et la durée entre deux passes de conversion est donnée en millisecondes par la variable d’environnement DURATION.
On souhaite que notre conteneur :
-
soit nommé
aic, -
soit en mode détaché,
-
soit automatiquement supprimé à la fin de son exécution,
-
possède un montage du répertoire de l’environnement d’accueil
/data/input_imagesvers le répertoire/inputsdu conteneur, -
possède un montage du répertoire de l’environnement d’accueil
/data/output_imagesvers le répertoire/outputsdu conteneur, -
spécifie un format de conversion PNG,
-
spécifie une durée entre deux passes qui soit de 5000 ms,
-
utilise la version
16de l’image de conteneur.
On exécute donc la commande suivante :
$ podman container run --name aic \
--detach \
--rm \
--mount type=bind,source="/data/input_images",target="/inputs" \
--mount type=bind,source="/data/output_images",target="/outputs" \
--env FORMAT="png" \
--env DURATION="5000" \
docker-registry.univ-nantes.fr/gl/developpement_exploitation/resources/automatic-image-converter:16
Ainsi, toute image qu’on dépose dans l’environnement d’accueil à l’emplacement /data/input_images sera vue au sein du conteneur dans l’emplacement /inputs, puis sera traitée par la boucle d’exécution, et l’image convertie en PNG sera produite dans /outputs dans le conteneur, et apparaîtra donc dans l’emplacement /data/output_images de l’environnement d’accueil.
On souhaite déployer Simplestblog à l’aide de conteneurs. Il nous faut pour cela créer deux conteneurs : Simplestblog d’une part, et une base de données compatible MySQL d’autre part.
Pour la base de données, on souhaite utiliser l’image MariaDB disponible sur le registre Docker Hub à l’emplacement mariadb.
On souhaite que notre conteneur MariaDB :
-
soit nommé
simplestblog_db, -
soit en mode détaché,
-
soit automatiquement supprimé à la fin de son exécution,
-
possède un montage du répertoire de l’environnement d’accueil
/data/simplestblog/database_datavers le répertoire/var/lib/mysqldu conteneur, de manière à persister le contenu de la base de données sur l’environnement d’accueil, -
configure les variables d’environnement attendues par l’image MariaDB, c’est à dire :
-
MARIADB_ROOT_PASSWORD, le mot de passe administrateur de l’instance MariaDB, avec la valeur "my_very_very_secret_root_password", -
MARIADB_DATABASE, le nom de la base de donnée pour Simplestblog, avec la valeursimplestblog -
MARIADB_USER, le nom d’utilisateur du compte base de données, avec la valeur "simplestblog", -
MARIADB_PASSWORD, le mot de passe pour le compte base de données, avec la valeur "my_very_secret_db_password",
-
-
utilise la version
11de l’image de conteneur.
On exécute donc la commande suivante :
$ podman container run --name simplestblog_db \
--detach \
--rm \
--mount type=bind,source="/data/simplestblog/database",target="/var/lib/mysql" \
--env MARIADB_ROOT_PASSWORD="my_very_very_secret_root_password" \
--env MARIADB_DATABASE="simplestblog" \
--env MARIADB_USER="simplestblog" \
--env MARIADB_PASSWORD="my_very_secret_db_password" \
docker.io/mariadb:11
Ensuite, pour Simplestblog, on fait usage de l’image disponible dans le registre de conteneurs du Gitlab Nantes Université, accessible à l’emplacement developpement_exploitation/resources/simplestblog.
On suppose qu’une image de conteneur a déjà été préparée pour Simplestblog, et que cette image fait l’hypothèse qu’un fichier de configuration doit se trouver à l’emplacement /config.py au sein du conteneur.
Ce fichier doit notamment contenir les informations indiquant à Simplestblog comment se connecter à sa base de données.
On prépare le fichier configuration Simplestblog suivant, à l’emplacement /data/simplestblog/test_config.py dans l’environnement d’accueil :
SECRET_KEY = "my_very_secret_key"
DB_TYPE = "mysql"
MYSQL_DATABASE_USER = "simplestblog"
MYSQL_DATABASE_DB = "simplestblog"
MYSQL_DATABASE_PASSWORD = "my_very_secret_db_password"
MYSQL_DATABASE_HOST = "simplestblog_db"
MYSQL_DATABASE_PORT = "3306"
Le contenu de ce fichier de configuration est préparé de manière à être compatible avec le conteneur MariaDB préparé plus haut. Plus précisément on peut constater que :
-
MYSQL_DATABASE_USERcorrespond àMARIADB_USERdans la configuration du conteneur MariaDB, -
MYSQL_DATABASE_DBcorrespond àMARIADB_DATABASEdans la configuration du conteneur MariaDB, -
MYSQL_DATABASE_PASSWORDcorrespond àMARIADB_PASSWORDdans la configuration du conteneur MariaDB, -
MYSQL_DATABASE_HOSTcorrespond au nom du conteneur MariaDB, car le nom du conteneur est également un nom d’hôte permettant d’atteindre le dit conteneur au sein du réseau virtuel où il se trouve (à savoir le réseausimplestblog_networkdans notre exemple). -
MYSQL_DATABASE_PORTcorrespond au port sur lequel écoute le processus MariaDB (à savoir, par défaut, 3306)
Enfin on souhaite que notre conteneur Simplestblog :
-
soit nommé
simplestblog_app, -
soit en mode détaché,
-
soit automatiquement supprimé à la fin de son exécution,
-
possède un montage du fichier de l’environnement d’accueil
/data/simplestblog/test_config.pyvers le fichier/config.pydu conteneur, -
utilise la version
52de l’image de conteneur.
On exécute donc la commande suivante :
$ podman container run --name simplestblog_app \
--detach \
--rm \
--mount type=bind,source="/data/simplestblog/test_config.py",target="/config.py" \
docker-registry.univ-nantes.fr/gl/developpement_exploitation/resources/simplestblog:60
| Faire le déploiement avec les commandes proposées ici n’est pas suffisant, car il manque toujours la mise en place d’une communication entre les processus des deux conteneurs, ainsi que l’ouverture d’un port réseau permettant de se connecter à l’application web depuis le réseau de l’environnement d’accueil. Toutes ces notions de réseau seront introduites dans la suite du cours. |
|
Lorsque Podman est utilisé dans un environnement d’accueil qui exécute le système de contrôle d’accès obligatoire SELinux, il est nécessaire d’indiquer à SELinux que le conteneur a bien le droit de manipuler un répertoire monté.
Pour cela, il existe une option de montage Rassurez-vous, SELinux n’est pas activé en travaux pratiques, donc il n’est pas nécessaire de retenir cela ! |
5. Droits d’accès sur les fichiers créés dans un conteneur
Lorsqu’un logiciel exécuté dans un conteneur crée de nouveaux fichiers, il le fait par le biais d’un compte Linux virtuel qui est spécifique au conteneur. À cause de cela, il peut arriver que des fichiers soient créées dans un conteneur avec des droits d’accès étranges, notamment des droits d’accès qui empêchent votre propre compte Linux de lire, modifier ou supprimer ces fichiers depuis l’environnement d’accueil.
Si les fichiers créés par le conteneur n’ont pas vocation à sortir du conteneur, cela ne pose aucun problème. En revanche, si ces fichiers sont rendus disponibles dans l’environnement d’accueil par le biais d’un montage de répertoire, alors on peut se retrouver bien embêté à ne pas pouvoir supprimer, déplacer ou modifier ces fichiers !
Pour résoudre une situation de ce type, Podman met à disposition la commande suivante qui permet de passer temporairement outre ce problème de droit d’accès :
$ podman unshare
Une fois cette commande effectuée, on possède alors temporairement le droit de modifier n’importe quel fichier créé par un compte virtuel d’un conteneur Podman. Cette commande est indispensable pour faire le ménage dans les montages de répertoires !.
On souhaite créer un conteneur MariaDB, permettant de stocker une base de données MySQL.
On prépare et on exécute la commande suivante :
$ podman container run --name my_db \
--detach \
--rm \
--mount type=bind,source="/data/my_db",target="/var/lib/mysql" \
--env MARIADB_ROOT_PASSWORD="my_very_very_secret_root_password" \
docker.io/mariadb:11
On obtient ainsi une base de données MariaDB dont les données sont stockées dans l’environnement d’accueil à l’emplacement /data/my_db.
Plus tard, on stoppe ce conteneur :
$ podman container stop my_db
Et on souhaite supprimer les fichiers créées par MariaDB à l’emplacement /data/my_db :
$ rm -rf /data/my_db
Mais, patatras, on se retrouve face à une myriade d’erreurs "Permission non accordée" !
En effet, ces fichiers ont été créés dans le conteneur par un compte virtuel, et on ne possède donc pas le droit de les supprimer.
On demande donc les droits de modifier ces fichiers avec :
$ podman unshare
Puis on essaye à nouveau :
# rm -rf /data/my_db
Et cette fois-ci la suppression se fait sans problème.