Le projet Docker s’est imposé comme un standard pour la vir­tua­li­sa­tion de con­te­neurs avec le logiciel éponyme. L'image Docker est un concept central de l'uti­li­sa­tion de la pla­te­forme Docker. Dans cet article, nous vous ex­pli­que­rons comment les images Docker sont dé­ve­lop­pées et comment elles fonc­tion­nent.

Qu’est-ce qu’une image Docker ?

Vous avez peut-être déjà rencontré le terme « image » dans le contexte de la vir­tua­li­sa­tion à l’aide de machines vir­tuelles (VM). En général, une image VM constitue une copie d’un système d’ex­ploi­ta­tion, mais peut contenir d’autres com­po­sants installés tels que des bases données et des serveurs Web. Le terme vient d’une période où les softwares étaient dis­tri­bués sur des supports de données optiques comme des CD-ROM et DVD. Pour créer une copie locale du support de données, il fallait alors créer une « image » à l’aide d’un logiciel spé­ci­fique.

La vir­tua­li­sa­tion des con­te­neurs est la suite logique de la vir­tua­li­sa­tion des VM. Au lieu de vir­tua­li­ser un or­di­na­teur virtuel (machine) avec son propre système d’ex­ploi­ta­tion, une image Docker est en général cons­ti­tuée d’une seule ap­pli­ca­tion. Il peut s’agir d’un fichier binaire in­di­vi­duel ou d’une com­bi­nai­son de plusieurs com­po­sants logiciel.

Pour exécuter l’ap­pli­ca­tion, un conteneur est d’abord créé à partir de l’image. Tous les con­te­neurs exécutés sur un hôte Docker ont recours au même noyau de système d’ex­ploi­ta­tion. Par con­sé­quent, les con­te­neurs Docker et les images Docker sont, en général, beaucoup plus légers que les machines vir­tuelles de tailles com­pa­rables et leurs images.

Les con­te­neurs Docker et les images Docker sont des concepts in­trin­sè­que­ment liés. À ce titre, non seulement un conteneur Docker peut être créé à partir d’une image Docker, mais une nouvelle image peut également être créée à partir d’un conteneur en cours d’exécution. C’est la raison pour laquelle nous disons que les images Docker et les con­te­neurs Docker sont comme l’œuf et la poule :

Commande Docker Des­crip­tion Analogie avec l’œuf et la poule
docker run <image-id> Crée un conteneur Docker à partir d’une image Le poussin sort de l’œuf
docker commit <container-id> Crée une image Docker à partir d’un conteneur La poule pond un nouvel œuf

Dans le système bio­lo­gique qui lie l’œuf et la poule, un poussin unique est produit à partir d’un œuf unique. L’œuf est détruit dans le processus. Par contraste, une image Docker peut être employée pour créer un nombre illimité de con­te­neurs si­mi­laires. Cette re­pro­duc­ti­bi­lité fait de Docker une pla­te­forme idéale pour des ap­pli­ca­tions et des services ex­ten­sibles.

Une image Docker constitue un modèle immuable qui peut être employé de manière répétée pour créer des con­te­neurs Docker. L’image contient toutes les in­for­ma­tions et dé­pen­dances requises pour exécuter un conteneur, y compris toutes les bi­blio­thèques de programme basiques et in­ter­faces uti­li­sa­teur. Un en­vi­ron­ne­ment de ligne de commande (« shell ») et une im­plé­men­ta­tion de la bi­blio­thèque C standard sont en général présents à bord. Voici un aperçu de l’image of­fi­cielle « Alpine Linux » :

Noyau Linux Bi­blio­thèque C standard Commande Unix
Depuis l’hôte musl libc BusyBox

À côté de ces com­po­sants basiques qui com­plè­tent le noyau Linux, une image Docker contient en général des logiciels ad­di­tion­nels. Voici quelques exemples ci-dessous de com­po­sants logiciel pour dif­fé­rentes zones d’ap­pli­ca­tion. Veuillez noter qu’une image Docker contient en général une petite sélection des com­po­sants exposés :

Zone d’ap­pli­ca­tion Com­po­sants logiciel
Langages de pro­gram­ma­tion PHP, Python, Ruby, Java, Ja­vaS­cript
Outils de dé­ve­lop­pe­ment node/npm, React, Laravel
Systèmes de base de données MySQL, Postgres, MongoDB, Redis
Serveurs Web Apache, nginx, lighttpd
Caches et proxies Varnish, Squid
Systèmes de gestion de contenu WordPress, Magento, Ruby on Rails

En quoi une image Docker diffère-t-elle d’un conteneur Docker ?

Comme nous l’avons vu, les images Docker et les con­te­neurs Docker sont in­trin­sè­que­ment liés. Dès lors, en quoi ces deux concepts diffèrent-ils ?

Pour commencer, les images Docker sont inertes. Elles occupent de l’espace de stockage mais n’utilisent aucune ressource système. De plus, une image Docker ne peut pas être modifiée après sa création et, à ce titre, constitue un fichier « en lecture seule ». Soit dit en passant, il est possible d’ajouter des chan­ge­ments à une image Docker existante, mais ceci créera de nouvelles images. Il restera une version originale, inchangée de l’image.

Comme nous l’avons déjà mentionné, une image Docker peut être utilisée pour créer un nombre illimité de con­te­neurs si­mi­laires. En quoi un conteneur Docker diffère-t-il exac­te­ment d’une image Docker ? Un conteneur Docker constitue une instance en cours de fonc­tion­ne­ment (c’est-à-dire en cours d’exécution) d’une image Docker. Comme tout logiciel fonc­tion­nant sur un or­di­na­teur, un conteneur Docker en cours d’exécution consomme des res­sources système de mémoire et des cycles de CPU. Par ailleurs, le statut d’un conteneur change au fil de son cycle de vie.

Si cette des­crip­tion paraît trop abstraite, n’hésitez pas à utiliser cet exemple issu de votre vie quo­ti­dienne pour vous aider : imaginez que l’image Docker est un DVD. Le DVD en lui-même est inerte ; il reste dans sa boîte et ne fait rien. Il occupe la même place limitée dans la pièce de façon per­ma­nente. Le contenu « prend vie » uni­que­ment au moment où le DVD est lu dans un en­vi­ron­ne­ment spé­ci­fique (le lecteur DVD).

Au même titre que le film généré lors de la lecture du DVD, un conteneur Docker en cours d’exécution a un statut. Dans le cas d’un film, celui-ci comprend le moment de lecture actuel, la langue sé­lec­tion­née, les sous-titres, etc. Ce statut change au fil du temps, et un film en cours de lecture consomme de l’élec­tri­cité en per­ma­nence. Tout comme le fait qu’un nombre illimité de con­te­neurs iden­tiques peuvent être créés depuis une image Docker, le film présent sur un DVD peut être lu et relu encore et encore. Qui plus est, le film en cours de lecture peut être in­ter­rompu et relancé, à l’instar d’un conteneur Docker.

Concept Docker Analogie Mode Statut Con­som­ma­tion de res­sources
Image Docker DVD Inerte « Lecture-seule » / immuable Fixe
Conteneur Docker « en lecture » Film en cours de lecture Change au fil du temps Varie selon l’usage

Comment et où les images Docker sont-elles utilisées ?

De nos jours, Docker est employé dans toutes les phases du cycle de vie du software, y compris les phases de dé­ve­lop­pe­ment, de test et d’ex­ploi­ta­tion. Le concept central dans l’éco­sys­tème Docker est le conteneur qui est toujours créé depuis une image. À ce titre, les images Docker sont utilisées partout où Docker est employé. Jetons un œil à quelques exemples.

Les images Docker dans les en­vi­ron­ne­ments de dé­ve­lop­pe­ment locaux

Si vous dé­ve­lop­pez un logiciel sur votre propre appareil, vous voudrez que l’en­vi­ron­ne­ment de dé­ve­lop­pe­ment reste aussi cohérent que possible. La plupart du temps, vous aurez besoin de versions par­fai­te­ment com­pa­tibles du langage de pro­gram­ma­tion, des bi­blio­thèques et des autres com­po­sants logiciel. Si un seul des nombreux niveaux en in­te­rac­tion est modifié, cela peut ra­pi­de­ment perturber l’ensemble des autres niveaux. Ceci peut conduire à ce que le code source ne compile pas ou à ce que le serveur Web ne se lance pas. À cet égard, le caractère immuable d’une image Docker est d’une utilité phé­no­mé­nale. En tant que dé­ve­lop­peur, vous pouvez être sûr que l’en­vi­ron­ne­ment contenu dans l’image restera cohérent.

Les vastes projets de dé­ve­lop­pe­ment sont souvent menés à bien en équipe. Auquel cas, avoir recours à un en­vi­ron­ne­ment qui demeure stable au fil du temps est crucial pour la com­pa­ra­bi­lité et la re­pro­duc­ti­bi­lité. Tous les dé­ve­lop­peurs au sein d’une même équipe peuvent utiliser la même image, et lorsqu’un nouveau dé­ve­lop­peur rejoint une équipe, il peut trouver la bonne image Docker et commencer à tra­vail­ler im­mé­dia­te­ment. Lorsque des chan­ge­ments sont apportés à l’en­vi­ron­ne­ment de dé­ve­lop­pe­ment, une nouvelle image Docker est créée. Les dé­ve­lop­peurs peuvent alors obtenir la nouvelle image et sont im­mé­dia­te­ment à jour.

Les images Docker dans les ar­chi­tec­ture orientées service (AOS)

Les images Docker constitue la base des ar­chi­tec­ture orientées service modernes. En lieu et place d’une ap­pli­ca­tion mo­no­li­thique, des services in­di­vi­duels munis d’in­ter­faces bien définies sont dé­ve­lop­pés. Chaque service est empaqueté dans sa propre image. Les con­te­neurs lancés depuis ces dernières com­mu­ni­quent les uns avec les autres via le réseau et éta­blis­sent la fonc­tion­na­lité globale de l’ap­pli­ca­tion. L’en­cap­su­la­tion dans des images Docker dis­tinctes permet de dé­ve­lop­per et de maintenir les services in­dé­pen­dam­ment les uns des autres. Les dif­fé­rents services peuvent même être écrits dans des langages de pro­gram­ma­tion dif­fé­rents.

Les images Docker pour les four­nis­seurs d’hé­ber­ge­ment / PaaS

Les images Docker peuvent également être utilisées dans les data centers. Chaque service (par ex. les ré­par­ti­teurs de charge, serveur Web, etc.) peut être défini en tant qu’image Docker. Les con­te­neurs qui en résultent peuvent chacun supporter une certaine charge. Un logiciel d’or­ches­tra­tion supervise le conteneur, sa charge et son statut. Lorsque la charge augmente, l’or­ches­tra­teur lance des con­te­neurs ad­di­tion­nels à partir de l’image cor­res­pon­dante. Cette approche vous permet d’étendre ra­pi­de­ment les services pour répondre aux chan­ge­ments de con­di­tions.

Comment une image Docker est-elle dé­ve­lop­pée ?

À la dif­fé­rence des images des machines vir­tuelles, une image Docker n’est nor­ma­le­ment pas cons­ti­tuée d’un fichier unique. À la place, elle est composée d’une com­bi­nai­son de plusieurs com­po­sants dif­fé­rents dont voici un rapide aperçu (nous dé­tail­le­rons les choses un peu plus loin) :

  • Les couches de l’image con­tien­nent des données ajoutées par des opé­ra­tions ef­fec­tuées sur le système fichier. Les couches sont su­per­po­sées puis réduites à un niveau cohérent par un Union File System.
  • Une image parente prépare les fonctions basiques de l’image et l’ancre dans le ré­per­toire de fichiers principal de l’éco­sys­tème Docker.
  • Un manifeste d’image décrit la com­po­si­tion de l’image et identifie les couches de l’image.

Que devriez-vous faire si vous souhaitez convertir une image Docker en un fichier unique ? Vous pouvez faire ceci avec la commande « docker save » sur la ligne de commande. Ceci crée un fichier de sau­ve­garde .tar qui peut être fa­ci­le­ment déplacé d’un système à l’autre. Avec la commande suivante, une image Docker portant le nom « busybox » est rédigée dans un fichier « busyvox.tar » :

docker save busybox > busybox.tar

Souvent, l’output de la commande « docker save » est acheminé jusqu’à Gzip sur la ligne de commande. De cette manière, les données sont com­pres­sées après avoir été exportées vers le fichier .tar :

docker save myimage:latest | gzip > myimage_latest.tar.gz

Un fichier d’image créé via « docker save » peut être chargé dans l’hôte Docker local en tant qu’image Docker avec « docker load » :

docker load busybox.tar

Couches d’image

Une image Docker est cons­ti­tuée de couches en lecture seule. Chaque couche décrit les chan­ge­ments suc­ces­sifs apportés au système fichier de l’image. Pour chaque opération qui conduit à une chan­ge­ment au système fichier, une nouvelle couche est créée. On désigne en général l’approche utilisée ici sous le nom de « copy-on-write » (« copie sur écriture ») : un accès écrit crée une copie modifiée de l’image dans une nouvelle couche tandis que les données ori­gi­nales demeurent in­chan­gées. Si ce principe vous paraît familier, c’est parce que le logiciel de contrôle de version Git fonc­tionne de la même manière.

Nous pouvons afficher les couches d’une image Docker en utilisant la commande « docker image inspect » sur la ligne de commande. Cette commande renvoie un document JSON que nous pouvons traiter avec l’outil standard jq:

Docker image inspect <image-id> | jq -r '.[].RootFS.Layers[]'

Un système fichier spé­ci­fique est utilisé pour fusionner à nouveau les chan­ge­ments dans les couches. Cet Union File System superpose toutes les couches pour produire un dossier et une structure de fichiers cohérents sur l’interface. His­to­ri­que­ment, diverses tech­no­lo­gies connues sous le nom de « pilotes de stockage » ont été utilisées pour im­plé­men­ter l’Union File System. De nos jours, le pilote de stockage « overlay2 » est re­com­mandé dans la majorité des cas :

Pilote de stockage Com­men­taire
overlay2 Re­com­mandé aujourd’hui
aufs, overlay Utilisé dans d’anciennes versions

Il est possible d’afficher le pilote de stockage utilisé d’une image Docker. Nous pouvons utiliser la commande « Docker image inspect » sur la ligne de commande qui renvoie un document JSON que nous traitons avec l’outil standard jq :

Docker image inspect <image-id> | jq -r '.[].GraphDriver.Name'

Chaque couche d’image est iden­ti­fiée par une fonction de hachage claire, laquelle est calculée à partir des chan­ge­ments que contient la couche. Si deux images utilisent la même couche, cette couche sera seulement stockée lo­ca­le­ment une fois. Chacune des images utilisera ensuite la même couche. Ceci garantit un stockage local efficace et réduit les volumes trans­fé­rés lors de l’obtention des images.

Images parentes

Une image Docker possède en général une « image parente » sous-jacente. Dans la majorité des cas, l’image parente est définie par une directive FROM dans le fichier docker. L’image parente définit une base sur laquelle les images dérivées sont basées. Des couches ad­di­tion­nelles sont su­per­po­sées sur les couches d’image exis­tantes.

Lorsqu’elle « hérite » d’une image parente, une image Docker est placée dans un ré­per­toire de fichiers qui contient toutes les images exis­tantes. Vous vous demandez peut-être où le ré­per­toire de fichiers principal commence ? Ses racines sont dé­ter­mi­nées par quelques « images bases » spé­ci­fiques. Dans la plupart des cas, une image de base est définie à l’aide de la directive « FROM scratch » (« de zéro » en français) dans le fichier docker. Il existe néanmoins d’autres manières de créer une image de base. Vous pouvez en savoir plus à ce sujet dans la section « D’où viennent les images Docker ? ».

Ma­ni­festes d’image

Comme nous l’avons vu, une image Docker est cons­ti­tuée de plusieurs couches. Vous pouvez avoir recours à la commande « docker image pull » pour tirer une image Docker d’un ré­fé­ren­tiel en ligne. Dans ce cas, aucun fichier unique n’est té­lé­chargé. À la place, le Docker Daemon local té­lé­charge les couches in­di­vi­duelles et les sau­ve­garde. Dès lors, d’où viennent les in­for­ma­tions au sujet des couches in­di­vi­duelles ?

Les in­for­ma­tions indiquant quelles couches d’image cons­ti­tuent une image Docker sont ac­ces­sibles dans le manifeste d’image. Un manifeste d’image est un fichier JSON qui renferme une des­crip­tion complète d’une image Docker et contient ce qui suit :

  • Des in­for­ma­tions au sujet de la version, du schème et de la taille
  • Des fonctions de hachage cryp­to­gra­phiques des couches d’image utilisées
  • Des in­for­ma­tions au sujet des ar­chi­tec­tures de pro­ces­seur dis­po­nibles

Pour iden­ti­fier clai­re­ment une image Docker, une fonction de hachage cryp­to­gra­phique du manifeste d’image est créée. Lorsque la commande « docker image pull » est exécutée, le fichier manifeste est té­lé­chargé. Le Docker Daemon local obtient alors les couches d’images in­di­vi­duelles.

D’où viennent les images Docker ?

Comme nous l’avons vu, les images Docker cons­ti­tuent une partie im­por­tante de l’éco­sys­tème Docker. Il existe de nom­breuses manières dif­fé­rentes d’obtenir une image Docker dont deux méthodes basiques sur les­quelles nous allons nous attarder ci-dessous :

  1. Obtenir des images Docker exis­tantes depuis un ré­fé­ren­tiel
  2. Créer de nouvelles images Docker

Obtenir des images Docker depuis un ré­fé­ren­tiel

Souvent, un projet Docker démarre lorsqu’une image Docker existante est té­lé­char­gée depuis un ré­fé­ren­tiel. Il s’agit d’une pla­te­forme ac­ces­sible via le réseau qui fournit les images Docker. L’hôte Docker local com­mu­nique avec le ré­fé­ren­tiel pour té­lé­char­ger une image Docker à la suite d’une commande « docker image pull ».

Des registres pu­bli­que­ment ac­ces­sibles en ligne offrent un large éventail d’images Docker exis­tantes à utiliser. Au moment où cet article a été rédigé, il existait plus de huit millions d’images Docker dis­po­nibles gra­tui­te­ment au sein du ré­fé­ren­tiel Docker officiel « Docker Hub ». En plus des images Docker, l’« Azure Container Registry » de Microsoft comprend des images de con­te­neurs dans un large choix de formats dif­fé­rents. Vous pouvez également utiliser la pla­te­forme pour créer vos propres ré­fé­ren­tiels de con­te­neurs privés.

Outre les ré­fé­ren­tiels en ligne men­tion­nés ci-dessus, vous pouvez également héberger vous-même un ré­fé­ren­tiel local. Les grandes or­ga­ni­sa­tions, par exemple, ont souvent recours à cette option pour fournir un accès protégé à des images Docker auto-créées à leurs équipes. Docker a créé le Docker Trusted Registry (DTR) à cette fin. Il s’agit d’une solution sur site qui assure la four­ni­ture d’un ré­fé­ren­tiel en interne dans votre centre in­for­ma­tique privé.

Créer de nouvelles images Docker

Il peut arriver que sou­hai­tiez créer une image Docker ad hoc pour un projet spé­ci­fique. La plupart du temps, vous avez la pos­si­bi­lité d’utiliser une image Docker existante et de l’adapter pour répondre à vos besoins. Gardez à l’esprit que les images Docker sont immuables et que lorsqu’un chan­ge­ment est apporté, une nouvelle image Docker est créée. Il existe dif­fé­rentes manières de créer une nouvelle image Docker :

  1. Appuyez-vous sur l’image parente à l’aide de Do­cker­file
  2. Générez-en une depuis le conteneur en cours d’exécution
  3. Créez une nouvelle image de base

L’approche la plus courante pour créer une nouvelle image Docker est de rédiger un Do­cker­file. Un Do­cker­file contient des commandes spé­ci­fiques qui dé­fi­nis­sent l’image parente et tous les chan­ge­ments requis. Appeler la commande « docker image build » créera une nouvelle image Docker à partir du Do­cker­file. Voici un rapide exemple :

# Créer un Dockerfile sur la ligne de commande
cat <<EOF > ./Dockerfile
FROM busybox
RUN echo "hello world"
EOF
# Créer une image Docker à partir d'un Dockerfile
Docker image build

His­to­ri­que­ment, le terme « image » est issu de la « mise en image » d’un support de données. Dans le contexte des machines vir­tuelles (VMs), un ins­tan­tané d’une image VM peut être créé. Un processus similaire peut être mené à bien avec Docker. Avec la commande « docker commit », nous pouvons créer une image d’un conteneur en cours d’exécution en tant que nouvelle image Docker. Toutes les mo­di­fi­ca­tions apportées au conteneur seront sau­ve­gar­dées :

docker commit <container-id>

Par ailleurs, nous pouvons passer des ins­truc­tions Do­cker­file avec la commande « docker commit ». Les mo­di­fi­ca­tions encodées avec les ins­truc­tions de­vien­nent partie prenante de la nouvelle image Docker :

docker commit --change <dockerfile instructions> <container-id>

Nous pouvons utiliser la commande « docker image history » pour dé­ter­mi­ner quelles mo­di­fi­ca­tions ont été apportées à l’image Docker par la suite :

Docker image history <image-id>

Comme nous l’avons vu, il est possible de baser une nouvelle image Docker sur une image parente ou sur le statut d’un conteneur en cours d’exécution. Mais comment faire pour créer une nouvelle image Docker ex nihilo ? Il existe deux manières dif­fé­rentes de faire cela. Vous pouvez utiliser un Do­cker­file avec la directive spé­ci­fique « FROM scratch » comme décrit ci-dessus. Ceci crée une nouvelle image de base minimale.

Si vous préférez ne pas utiliser l’image Docker scratch, vous devez utiliser un outil spécial tel que de­boots­trap et préparer une dis­tri­bu­tion Linux. Ceci sera ensuite empaqueté dans un fichier tarball avec la commande tar et importé dans l’hôte Docker local via « docker image import ».

Les commandes d’images Docker les plus im­por­tantes

Commande d’image Docker Ex­pli­ca­tion
Docker image build Créer une image Docker à partir d’un Do­cker­file
Docker image history Expose les étapes suivies pour créer une image Docker
Docker image import Crée une image Docker à partir d’un fichier tarball
Docker image inspect Expose des in­for­ma­tions dé­tail­lées pour une image Docker
Docker image load Charge un fichier image créé avec « docker image save »
Docker image ls / Docker images Liste les images dis­po­nibles sur l’hôte Docker
Docker image prune Supprime les images Docker non utilisées du fichier hôte
Docker image pull Tire une image Docker du ré­per­toire
Docker image push Envoie une image Docker au ré­per­toire
Docker image rm Supprime une image Docker de l’hôte Docker local
Docker image save Crée un fichier image
Docker image tag Tague une image Docker
Aller au menu principal