Conteneurisation : principes de base
On l’a vu dans le chapitre précédent : l’intérêt du conteneur est qu’il ne rend pas nécessaire de capturer un système d’exploitation complet pour maintenir la reproductibilité du code source. Le conteneur va s’appuyer sur les fonctions du système d’exploitation installée sur la machine hôte, ce qui permet de livrer un artefact plus léger et plus maniable qu’une machine virtuelle.
Dans les explications qui suivent, on va se focaliser sur l’un des logiciels les plus populaires pour fabriquer des conteneurs : Docker
Docker : conteneurs et images
Lorsqu’on conteneurise avec Docker, il importe de faire une distinction entre un conteneur et une image.
un conteneur est un environnement isolé dans lequel on exécute une image docker. C’est cette image qui va permettre de ré-exécuter le code.
L’image
L’image issue de la conteneurisationa avec Docker est un paquet qui inclut le code source, sa pile logicielle, les dépendances-système et plus globalement l’environnement d’exécution du code source : les variables système et tout ce qui est nécessaire pour l’exécution d’un programme. Cette image a les caractéristiques suivantes :
- elle est immuable : on ne peut pas la modifier, il faut la recréer. Elle contient un système de fichiers qui lui aussi est immuable et ce système organise le code et les dépendances en plusieurs couches.
- une image est donc constituée de différentes couches. Chaque instruction du dockerfile constitue une couche, c’est la raison pour laquelle, afin d’économiser de la mémoire, il ne faut pas que les instructions soient trop redondantes (par exemple au lieu d’indiquer dans le Dockerfile :
RUN apt update && apt install -y python3
RUN apt install -y python3-piputiliser plutôt :
RUN apt update && apt install -y python3 python3-pip
Dans l’image ci-dessus, on voit les différentes couches dont se composent un conteneur, depuis les plus basses, le chargement d’une image de base (ici Ubuntu), jusqu’aux plus hautes, la compilation d’une image shiny en passant par les étapes intermédiaires indispensables : le chargement et l’installation de R, le chargement des dépendances systèmes nécessaires pour faire fonctionner les paquets de R indispensables au code source et le chargement et l’installation de ces paquets. On verra dans notre exemple qu’on peut faire l’économie d’une couche basse en privilégiant pour faire du code R l’usage d’une image de base adéquate comportant déjà R (Rocker) plutôt qu’Ubuntu.
- l’image est portable : elle peut être partagée et distribuée à partir d’un site web (par exemple dockerhub)
- elle est construite à partir d’une recette. Cette recette, il en sera question dans le chapitre suivant. Il s’agit du Dockerfile qui permet de reconstruire l’image dans le conteneur en utilisant une simple commande :
docker build -t <tag image> <chemin>[¹] bien sûr cette commande implique qu’on ait préalablement chargé Docker sur sa machine.
le conteneur
un conteneur est un environnement isolé dans lequel on exécute une image docker
- le conteneur est modifiable. On peut le démarrer, le déplacer, l’exécuter, le supprimer (sans supprimer l’image). On peut le modifier pendant son exécution
- le conteneur est isolé et est exécuté sur la machine hôte de façon isolée. Redisons-le une fois de plus : en dépit de cet isolement et contrairement à une machine virtuelle, Docker s’appuie en partie sur le système hôte pour exécuter le conteneur.
- éphémère : les conteneurs sont conçus pour être éphémères : on peut en créer autant que de besoin et les supprimer ensuite
- création : les conteneurs sont créés à partir des images, en utilisant la commande
docker run <image>[²]
[¹]: pour exécuter le code source sur les cimetières militaires, la commande sera la suivante : docker build -t war_cemeteries . Elle doit être lancée depuis le terminal à l’endroit où se trouve le Dockerfile. Elle crée à partir du répertoire courant (.) où se trouve un Dockerfile une image dont le nom (l’argument -t correspondant à “tag”) est war_cemeteries
[²]: pour créer un conteneur à partir de l’image war_cemeteries qu’on a créée, il faut lancer la commande suivante : docker run war_cemeteries Cela démarrera un conteneur en partant de l’image nommée (=taguée) war_cemeteries