Fonctionnement des images Docker
Rappelez-vous que nous avons dit que l’image conteneur devient l’unité que nous utilisons pour distribuer les applications. Nous avons également mentionné que le conteneur est dans un format standardisé qui est utilisé à la fois par nos développeurs et nos équipes d’exploitation.
Ici, nous allons examiner les différences entre les logiciels, les packages et les images, tels qu’ils sont utilisés dans Docker. Connaître les différences entre ces concepts va nous aider à mieux comprendre le fonctionnement des images Docker.
Nous allons également présenter brièvement les rôles du système d’exploitation qui s’exécute sur l’hôte et ceux du système d’exploitation qui s’exécute dans le conteneur.
Logiciels packagés dans un conteneur
Les logiciels packagés dans un conteneur ne se limitent pas aux applications que nos développeurs créent. Quand nous parlons de logiciels, nous faisons référence au code d’application, aux packages système, aux fichiers binaires, aux bibliothèques, aux fichiers de configuration et au système d’exploitation qui s’exécute dans le conteneur.
Supposons par exemple que nous développions un portail de suivi des commandes pour les différents points de vente de notre entreprise. Nous devons examiner la pile complète des logiciels qui vont exécuter notre application web. L’application que nous créons est une application .NET Core MVC. Nous prévoyons de la déployer à l’aide de nginx en tant que serveur proxy inverse sur Ubuntu Linux. Tous ces composants logiciels font partie de l’image conteneur.
Qu’est-ce qu’une image conteneur ?
Une image conteneur est un package portable qui contient des logiciels. C’est cette image qui, quand elle est exécutée, devient notre conteneur. Le conteneur est l’instance en mémoire d’une image.
Une image conteneur est non modifiable. Une fois que vous avez créé une image, vous ne pouvez plus la changer. La seule façon de modifier une image est d’en créer une nouvelle. Ceci garantit que l’image que nous utilisons en production est la même que celle utilisée dans le développement et l’assurance qualité.
Qu’est-ce que le système d’exploitation hôte ?
Le système d’exploitation hôte est le système d’exploitation sur lequel le moteur Docker s’exécute. Les conteneurs Docker s’exécutant sur Linux partagent le noyau de l’OS hôte. Ils ne nécessitent pas d’OS de conteneur tant que le fichier binaire peut accéder directement à l’OS hôte.
Cependant, les conteneurs Windows ont besoin d’un système d’exploitation de conteneur. Le conteneur dépend du noyau du système d’exploitation pour gérer des services comme le système de fichiers, la gestion du réseau, la planification des processus et la gestion de la mémoire.
Qu’est-ce que le système d’exploitation de conteneur ?
L’OS de conteneur est celui qui fait partie de l’image packagée. Nous pouvons inclure différentes versions des systèmes d’exploitation Linux ou Windows dans un conteneur. Cette flexibilité nous permet d’accéder à des fonctionnalités spécifiques de l’OS, ou d’installer des logiciels supplémentaires que nos applications peuvent utiliser.
L’OS de conteneur est isolé de l’OS hôte. Il s’agit de l’environnement dans lequel nous déployons et exécutons notre application. Combinée avec le caractère non modifiable de l’image, cette isolation signifie que l’environnement de notre application qui s’exécute dans la phase de développement est le même que celui de la phase de production.
Dans notre exemple, nous utilisons Ubuntu Linux en tant qu’OS de conteneur, et cet OS ne diffère pas de celui du développement ou de la production. L’image que nous utilisons est toujours la même.
Qu’est-ce que Unionfs
(Stackable Unification File System, Système de fichiers d’unification empilable) ?
Nous utilisons Unionfs
pour créer des images Docker. Unionfs
est un système de fichiers qui vous permet d’empiler plusieurs répertoires, appelés branches, de manière à donner l’impression que leur contenu a été fusionné. Le contenu est cependant physiquement conservé séparément. Unionfs
vous permet d’ajouter et de supprimer des branches au fur et à mesure que vous créez votre système de fichiers.
Par exemple, supposons que nous générons une image pour notre application web à partir d’une version antérieure. Nous allons superposer la distribution Ubuntu en tant qu’image de base au-dessus di système de fichiers de démarrage. Nous allons ensuite installer nginx et notre application web. Nous plaçons en effet une couche avec nginx et l’application web au-dessus de l’image Ubuntu d’origine.
Une couche finale inscriptible est créée une fois que le conteneur est exécuté à partir de l’image. Toutefois, cette couche n’est pas conservée quand le conteneur est détruit.
Qu’est-ce qu’une image de base ?
Une image de base est une image qui utilise l’image scratch
de Docker. L’image scratch
est une image conteneur vide qui ne crée pas de couche de système de fichiers. Cette image suppose que l’application que vous allez exécuter peut utiliser directement le noyau du système d’exploitation hôte.
Qu’est-ce qu’une image parente ?
Une image parente est une image conteneur à partir de laquelle vous créez vos images.
Par exemple, au lieu de créer une image à partir de scratch
, puis d’installer Ubuntu, nous allons utiliser une image déjà basée sur Ubuntu. Nous pouvons même utiliser une image sur laquelle nginx est déjà installé. Une image parente comprend généralement un système d’exploitation de conteneur.
Quelle est la principale différence entre une image de base et une image parente ?
Les deux types d’images nous permettent de créer une image réutilisable. Toutefois, les images de base nous permettent d’avoir plus de contrôle sur le contenu de l’image finale. Rappelez-vous ce qui a été dit : une image est immuable. Vous pouvez uniquement effectuer des ajouts à une image, vous ne pouvez rien enlever.
Sur Windows, vous pouvez uniquement créer des images conteneur basées sur des images conteneur de base Windows. Microsoft fournit et gère ces images conteneur de base Windows.
Qu’est-ce qu’un Dockerfile ?
Un Dockerfile est un fichier texte qui contient les instructions que nous utilisons pour générer et exécuter une image Docker. Les aspects suivants de l’image sont définis :
- L’image de base ou parente que nous utilisons pour créer l’image
- Les commandes pour mettre à jour le système d’exploitation de base et installer des logiciels supplémentaires
- Les artefacts de build à inclure, par exemple une application développée
- Les services à exposer, comme une configuration de stockage et une configuration réseau
- La commande à exécuter quand le conteneur est lancé
Établissons la correspondance de ces aspects à un exemple de Dockerfile. Supposons que nous créons une image Docker pour notre site web ASP.NET Core. Le fichier Dockerfile peut ressembler à l’exemple suivant :
# Step 1: Specify the parent image for the new image
FROM ubuntu:18.04
# Step 2: Update OS packages and install additional software
RUN apt -y update && apt install -y wget nginx software-properties-common apt-transport-https \
&& wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& add-apt-repository universe \
&& apt -y update \
&& apt install -y dotnet-sdk-3.0
# Step 3: Configure Nginx environment
CMD service nginx start
# Step 4: Configure Nginx environment
COPY ./default /etc/nginx/sites-available/default
# STEP 5: Configure work directory
WORKDIR /app
# STEP 6: Copy website code to container
COPY ./website/. .
# STEP 7: Configure network requirements
EXPOSE 80:8080
# STEP 8: Define the entry point of the process that runs in the container
ENTRYPOINT ["dotnet", "website.dll"]
Nous n’allons pas aborder ici la spécification de fichier Dockerfile, ni les détails de chaque commande de l’exemple précédent. Toutefois, notez qu’il existe plusieurs commandes dans ce fichier qui nous permettent de manipuler la structure de l’image. Par exemple, la commande COPY
copie le contenu d’un dossier spécifique de la machine locale vers l’image conteneur que nous créons.
Rappelez-vous que nous avons mentionné plus tôt que les images Docker utilisent unionfs
. Chacune de ces étapes crée une image conteneur mise en cache au moment où nous générons l’image conteneur finale. Ces images temporaires se superposent à l’image précédente et se présentent sous forme d’image unique, une fois toutes les étapes effectuées.
Enfin, notez la dernière étape, l’étape 8. L’élément ENTRYPOINT
dans le fichier indique le processus qui s’exécute quand nous exécutons un conteneur à partir d’une image. S’il n’existe aucun ENTRYPOINT ou autre processus à exécuter, Docker considère que le conteneur n’a rien à faire, et celui-ci se ferme.
Comment gérer les images Docker
Les images Docker sont des fichiers volumineux qui sont initialement stockés sur votre PC. Nous avons besoin d’outils pour gérer ces fichiers.
Docker CLI et Docker Desktop nous permettent de gérer les images en les générant, en les listant, en les supprimant et en les exécutant. Nous gérons les images Docker en utilisant le client docker
. Le client n’exécute pas les commandes directement, il envoie toutes les requêtes au démon dockerd
.
Nous n’allons pas aborder ici toutes les commandes du client et leurs indicateurs, mais nous allons regarder quelques-unes des commandes les plus utilisées. La section En savoir plus de l’unité Résumé de ce module contient des liens vers la documentation Docker, qui couvre en détail toutes les commandes et tous les indicateurs de commande.
Comment générer une image
Nous utilisons la commande docker build
pour générer des images Docker. Supposons que nous utilisons la définition du Dockerfile précédent pour générer une image. Voici un exemple qui illustre la commande build :
docker build -t temp-ubuntu .
Voici la sortie générée par la commande build :
Sending build context to Docker daemon 4.69MB
Step 1/8 : FROM ubuntu:18.04
---> a2a15febcdf3
Step 2/8 : RUN apt -y update && apt install -y wget nginx software-properties-common apt-transport-https && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb && add-apt-repository universe && apt -y update && apt install -y dotnet-sdk-3.0
---> Using cache
---> feb452bac55a
Step 3/8 : CMD service nginx start
---> Using cache
---> ce3fd40bd13c
Step 4/8 : COPY ./default /etc/nginx/sites-available/default
---> 97ff0c042b03
Step 5/8 : WORKDIR /app
---> Running in 883f8dc5dcce
Removing intermediate container 883f8dc5dcce
---> 6e36758d40b1
Step 6/8 : COPY ./website/. .
---> bfe84cc406a4
Step 7/8 : EXPOSE 80:8080
---> Running in b611a87425f2
Removing intermediate container b611a87425f2
---> 209b54a9567f
Step 8/8 : ENTRYPOINT ["dotnet", "website.dll"]
---> Running in ea2efbc6c375
Removing intermediate container ea2efbc6c375
---> f982892ea056
Successfully built f982892ea056
Successfully tagged temp-ubuntu:latest
Si vous ne comprenez pas la sortie précédente, ne vous inquiétez pas. Notez cependant les étapes listées dans la sortie. Quand chaque étape s’exécute, une nouvelle couche est ajoutée à l’image que nous générons.
Notez également que nous exécutons un certain nombre de commandes pour installer des logiciels et gérer la configuration. Par exemple, à l’étape 2, nous exécutons les commandes apt -y update
et apt install -y
pour mettre à jour le système d’exploitation. Ces commandes s’exécutent dans un conteneur en cours d’exécution créé pour cette étape. Une fois la commande exécutée, le conteneur intermédiaire est supprimé. L’image mise en cache sous-jacente est conservée sur l’hôte de build et n’est pas supprimée automatiquement. Cette optimisation garantit que les builds ultérieures réutilisent ces images pour accélérer les temps de génération.
Qu’est-ce qu’une étiquette d’image ?
Une étiquette d’image est une chaîne de caractères utilisée pour la gestion de versions d’une image.
Dans l’exemple de build précédent, notez le dernier message de build qui indique « Successfully tagged temp-ubuntu: latest » (« temp-ubuntu: latest » étiqueté avec succès). Lors de la génération d’une image, nous nommons et nous étiquetons éventuellement l’image avec l’indicateur de commande -t
. Dans notre exemple, nous avons nommé l’image avec -t temp-ubuntu
, tandis que le nom de l’image résultante était étiqueté temp-ubuntu: latest. Si vous ne spécifiez pas d’étiquette, une image est étiquetée avec latest
.
Plusieurs étiquettes peuvent être affectées à une même image. Par convention, la version la plus récente d’une image se voit attribuer l’étiquette latest et une étiquette qui décrit le numéro de version de l’image. Quand vous publiez une nouvelle version d’une image, vous pouvez réaffecter l’étiquette latest pour référencer la nouvelle image.
Pour Windows, Microsoft ne fournit pas d’images conteneur de base avec l’étiquette latest. Pour les images conteneur de base Windows, vous devez spécifier une étiquette que vous souhaitez utiliser. Par exemple, l’image conteneur de base Windows pour Server Core est mcr.microsoft.com/windows/servercore
. Parmi ses étiquettes figurent ltsc2016
, ltsc2019
et ltsc2022
.
Voici un autre exemple. Supposons que vous voulez utiliser les images Docker des exemples .NET Core. Nous avons ici quatre versions de plateformes au choix :
mcr.microsoft.com/dotnet/core/samples:dotnetapp
mcr.microsoft.com/dotnet/core/samples:aspnetapp
mcr.microsoft.com/dotnet/core/samples:wcfservice
mcr.microsoft.com/dotnet/core/samples:wcfclient
Dans la liste d’images précédente, nous pouvons voir que Microsoft fournit plusieurs exemples de .NET Core. Les étiquettes spécifient les exemples auxquels l’image fait référence : ASP.NET, service WCF, etc.
Comment lister les images
Le logiciel Docker configure automatiquement un registre d’images local sur votre machine. Vous pouvez visualiser les images de ce registre avec la commande docker images
.
docker images
La sortie ressemble à l’exemple suivant :
REPOSITORY TAG IMAGE ID CREATED SIZE
tmp-ubuntu latest f89469694960 14 minutes ago 1.69GB
tmp-ubuntu version-1.0 f89469694960 14 minutes ago 1.69GB
ubuntu 18.04 a2a15febcdf3 5 weeks ago 64.2MB
Notez que l’image est listée avec son Nom, son Étiquette et un ID d’image. Rappelez-vous que nous pouvons appliquer plusieurs étiquettes à une image. La sortie précédente montre un exemple. Même si les noms des images sont différents, nous pouvons voir que les ID sont les mêmes.
L’ID d’image est un moyen pratique pour identifier et gérer les images, là où le nom ou l’étiquette d’une image peuvent être ambigus.
Comment supprimer une image
Vous pouvez supprimer une image du registre Docker local avec la commande docker rmi
. Cela est utile si vous devez économiser de l’espace sur le disque hôte du conteneur, car les couches d’images conteneur s’ajoutent jusqu’à atteindre l’espace total disponible.
Spécifiez le nom ou l’ID de l’image à supprimer. Cet exemple supprime l’image correspondant à l’exemple d’application web en utilisant le nom de l’image :
docker rmi temp-ubuntu:version-1.0
Vous ne pouvez pas supprimer une image si un conteneur l’utilise toujours. La commande docker rmi
retourne un message d’erreur, qui indique le conteneur qui repose sur l’image.
Nous avons exploré les principes de base des images Docker. Nous avons également vu comment gérer ces images et comment exécuter un conteneur à partir d’une image. Nous allons voir ensuite comment gérer les conteneurs.