Les images Docker

Le projet Docker s’est imposé comme un standard pour la virtualisation de conteneurs avec le logiciel éponyme. L'image Docker est un concept central de l'utilisation de la plateforme Docker. Dans cet article, nous vous expliquerons comment les images Docker sont développées et comment elles fonctionnent.

Qu’est-ce qu’une image Docker ?

Vous avez peut-être déjà rencontré le terme « image » dans le contexte de la virtualisation à l’aide de machines virtuelles (VM). En général, une image VM constitue une copie d’un système d’exploitation, mais peut contenir d’autres composants installés tels que des bases données et des serveurs Web. Le terme vient d’une période où les softwares étaient distribué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écifique.

La virtualisation des conteneurs est la suite logique de la virtualisation des VM. Au lieu de virtualiser un ordinateur virtuel (machine) avec son propre système d’exploitation, une image Docker est en général constituée d’une seule application. Il peut s’agir d’un fichier binaire individuel ou d’une combinaison de plusieurs composants logiciel.

Pour exécuter l’application, un conteneur est d’abord créé à partir de l’image. Tous les conteneurs exécutés sur un hôte Docker ont recours au même noyau de système d’exploitation. Par conséquent, les conteneurs Docker et les images Docker sont, en général, beaucoup plus légers que les machines virtuelles de tailles comparables et leurs images.

Les conteneurs Docker et les images Docker sont des concepts intrinsèquement 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 conteneurs Docker sont comme l’œuf et la poule :

Commande Docker

Description

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 biologique 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 conteneurs similaires. Cette reproductibilité fait de Docker une plateforme idéale pour des applications et des services extensibles.

Une image Docker constitue un modèle immuable qui peut être employé de manière répétée pour créer des conteneurs Docker. L’image contient toutes les informations et dépendances requises pour exécuter un conteneur, y compris toutes les bibliothèques de programme basiques et interfaces utilisateur. Un environnement de ligne de commande (« shell ») et une implémentation de la bibliothèque C standard sont en général présents à bord. Voici un aperçu de l’image officielle « Alpine Linux » :

Noyau Linux

Bibliothèque C standard

Commande Unix

Depuis l’hôte

musl libc

BusyBox

À côté de ces composants basiques qui complètent le noyau Linux, une image Docker contient en général des logiciels additionnels. Voici quelques exemples ci-dessous de composants logiciel pour différentes zones d’application. Veuillez noter qu’une image Docker contient en général une petite sélection des composants exposés :

Zone d’application

Composants logiciel

Langages de programmation

PHP, Python, Ruby, Java, JavaScript

Outils de développement

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 conteneurs Docker sont intrinsèquement 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 changements à 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 conteneurs similaires. En quoi un conteneur Docker diffère-t-il exactement d’une image Docker ? Un conteneur Docker constitue une instance en cours de fonctionnement (c’est-à-dire en cours d’exécution) d’une image Docker. Comme tout logiciel fonctionnant sur un ordinateur, un conteneur Docker en cours d’exécution consomme des ressources 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 description paraît trop abstraite, n’hésitez pas à utiliser cet exemple issu de votre vie quotidienne 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 permanente. Le contenu « prend vie » uniquement au moment où le DVD est lu dans un environnement spécifique (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électionnée, les sous-titres, etc. Ce statut change au fil du temps, et un film en cours de lecture consomme de l’électricité en permanence. Tout comme le fait qu’un nombre illimité de conteneurs identiques 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 interrompu et relancé, à l’instar d’un conteneur Docker.

Concept Docker

Analogie

Mode

Statut

Consommation de ressources

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éveloppement, de test et d’exploitation. Le concept central dans l’écosystè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 environnements de développement locaux

Si vous développez un logiciel sur votre propre appareil, vous voudrez que l’environnement de développement reste aussi cohérent que possible. La plupart du temps, vous aurez besoin de versions parfaitement compatibles du langage de programmation, des bibliothèques et des autres composants logiciel. Si un seul des nombreux niveaux en interaction est modifié, cela peut rapidement 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énoménale. En tant que développeur, vous pouvez être sûr que l’environnement contenu dans l’image restera cohérent.

Les vastes projets de développement sont souvent menés à bien en équipe. Auquel cas, avoir recours à un environnement qui demeure stable au fil du temps est crucial pour la comparabilité et la reproductibilité. Tous les développeurs au sein d’une même équipe peuvent utiliser la même image, et lorsqu’un nouveau développeur rejoint une équipe, il peut trouver la bonne image Docker et commencer à travailler immédiatement. Lorsque des changements sont apportés à l’environnement de développement, une nouvelle image Docker est créée. Les développeurs peuvent alors obtenir la nouvelle image et sont immédiatement à jour.

Les images Docker dans les architecture orientées service (AOS)

Les images Docker constitue la base des architecture orientées service modernes. En lieu et place d’une application monolithique, des services individuels munis d’interfaces bien définies sont développés. Chaque service est empaqueté dans sa propre image. Les conteneurs lancés depuis ces dernières communiquent les uns avec les autres via le réseau et établissent la fonctionnalité globale de l’application. L’encapsulation dans des images Docker distinctes permet de développer et de maintenir les services indépendamment les uns des autres. Les différents services peuvent même être écrits dans des langages de programmation différents.

Les images Docker pour les fournisseurs d’hébergement / PaaS

Les images Docker peuvent également être utilisées dans les data centers. Chaque service (par ex. les répartiteurs de charge, serveur Web, etc.) peut être défini en tant qu’image Docker. Les conteneurs qui en résultent peuvent chacun supporter une certaine charge. Un logiciel d’orchestration supervise le conteneur, sa charge et son statut. Lorsque la charge augmente, l’orchestrateur lance des conteneurs additionnels à partir de l’image correspondante. Cette approche vous permet d’étendre rapidement les services pour répondre aux changements de conditions.

Comment une image Docker est-elle développée ?

À la différence des images des machines virtuelles, une image Docker n’est normalement pas constituée d’un fichier unique. À la place, elle est composée d’une combinaison de plusieurs composants différents dont voici un rapide aperçu (nous détaillerons les choses un peu plus loin) :

  • Les couches de l’image contiennent des données ajoutées par des opérations effectuées sur le système fichier. Les couches sont superposé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épertoire de fichiers principal de l’écosystème Docker.
  • Un manifeste d’image décrit la composition 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 sauvegarde .tar qui peut être facilement 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 compressé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 constituée de couches en lecture seule. Chaque couche décrit les changements successifs apportés au système fichier de l’image. Pour chaque opération qui conduit à une changement 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 originales demeurent inchangées. Si ce principe vous paraît familier, c’est parce que le logiciel de contrôle de version Git fonctionne 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écifique est utilisé pour fusionner à nouveau les changements 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. Historiquement, diverses technologies connues sous le nom de « pilotes de stockage » ont été utilisées pour implémenter l’Union File System. De nos jours, le pilote de stockage « overlay2 » est recommandé dans la majorité des cas :

Pilote de stockage

Commentaire

overlay2

Recommandé 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 identifiée par une fonction de hachage claire, laquelle est calculée à partir des changements que contient la couche. Si deux images utilisent la même couche, cette couche sera seulement stockée localement une fois. Chacune des images utilisera ensuite la même couche. Ceci garantit un stockage local efficace et réduit les volumes transfé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 additionnelles sont superposées sur les couches d’image existantes.

Lorsqu’elle « hérite » d’une image parente, une image Docker est placée dans un répertoire de fichiers qui contient toutes les images existantes. Vous vous demandez peut-être où le répertoire de fichiers principal commence ? Ses racines sont déterminées par quelques « images bases » spécifiques. 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 ? ».

Manifestes d’image

Comme nous l’avons vu, une image Docker est constituée de plusieurs couches. Vous pouvez avoir recours à la commande « docker image pull » pour tirer une image Docker d’un référentiel en ligne. Dans ce cas, aucun fichier unique n’est téléchargé. À la place, le Docker Daemon local télécharge les couches individuelles et les sauvegarde. Dès lors, d’où viennent les informations au sujet des couches individuelles ?

Les informations indiquant quelles couches d’image constituent une image Docker sont accessibles dans le manifeste d’image. Un manifeste d’image est un fichier JSON qui renferme une description complète d’une image Docker et contient ce qui suit :

  • Des informations au sujet de la version, du schème et de la taille
  • Des fonctions de hachage cryptographiques des couches d’image utilisées
  • Des informations au sujet des architectures de processeur disponibles

Pour identifier clairement une image Docker, une fonction de hachage cryptographique 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 individuelles.

D’où viennent les images Docker ?

Comme nous l’avons vu, les images Docker constituent une partie importante de l’écosystème Docker. Il existe de nombreuses manières différentes d’obtenir une image Docker dont deux méthodes basiques sur lesquelles nous allons nous attarder ci-dessous :

  1. Obtenir des images Docker existantes depuis un référentiel
  2. Créer de nouvelles images Docker

Obtenir des images Docker depuis un référentiel

Souvent, un projet Docker démarre lorsqu’une image Docker existante est téléchargée depuis un référentiel. Il s’agit d’une plateforme accessible via le réseau qui fournit les images Docker. L’hôte Docker local communique avec le référentiel pour télécharger une image Docker à la suite d’une commande « docker image pull ».

Des registres publiquement accessibles en ligne offrent un large éventail d’images Docker existantes à utiliser. Au moment où cet article a été rédigé, il existait plus de huit millions d’images Docker disponibles gratuitement au sein du référentiel Docker officiel « Docker Hub ». En plus des images Docker, l’« Azure Container Registry » de Microsoft comprend des images de conteneurs dans un large choix de formats différents. Vous pouvez également utiliser la plateforme pour créer vos propres référentiels de conteneurs privés.

Outre les référentiels en ligne mentionnés ci-dessus, vous pouvez également héberger vous-même un référentiel local. Les grandes organisations, 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 fourniture d’un référentiel en interne dans votre centre informatique privé.

Créer de nouvelles images Docker

Il peut arriver que souhaitiez créer une image Docker ad hoc pour un projet spécifique. La plupart du temps, vous avez la possibilité 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 changement est apporté, une nouvelle image Docker est créée. Il existe différentes manières de créer une nouvelle image Docker :

  1. Appuyez-vous sur l’image parente à l’aide de Dockerfile
  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 Dockerfile. Un Dockerfile contient des commandes spécifiques qui définissent l’image parente et tous les changements requis. Appeler la commande « docker image build » créera une nouvelle image Docker à partir du Dockerfile. 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

Historiquement, le terme « image » est issu de la « mise en image » d’un support de données. Dans le contexte des machines virtuelles (VMs), un instantané 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 modifications apportées au conteneur seront sauvegardées :

docker commit <container-id>

Par ailleurs, nous pouvons passer des instructions Dockerfile avec la commande « docker commit ». Les modifications encodées avec les instructions deviennent partie prenante de la nouvelle image Docker :

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

Nous pouvons utiliser la commande « docker image history » pour déterminer quelles modifications 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 différentes de faire cela. Vous pouvez utiliser un Dockerfile avec la directive spécifique « 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 debootstrap et préparer une distribution 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 importantes

Commande d’image Docker

Explication

Docker image build

Créer une image Docker à partir d’un Dockerfile

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 informations détaillé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 disponibles 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épertoire

Docker image push

Envoie une image Docker au répertoire

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