Créer ses propres images peut être d'une grand aide lorsque nous avons un besoin spécifique. Mais comment réaliser tout cela en conservant des bonnes pratiques? Voici quelques notions et des règles qu'il me semble essentiel de comprendre et de respecter.
Les Dockerfiles
Avant tout, c'est quoi un Dockerfile ? Il s'agit d'un fichier qui permet de construire une image Docker adaptée à nos besoins, étape par étape.
Docker se sert de ce fichier Dockerfile
pour créer des images automatiquement en lisant les instructions présentent dans le fichier. C'est ni plus, ni moins, qu'un fichier texte qui contient toutes les commandes nécessaires, dans l'ordre, pour construire une image donnée.
Un Dockerfile adhère à un format spécifique et à un ensemble d'instructions que vous pouvez trouver dans la documentation de référence Docker.
Cette image Docker va se composer de couches, chacune représentant une instruction Dockerfile. Les couches sont empilées, et chacune d'entre elles est un delta des changements par rapport à la couche précédente.
Cette notion de pile d'exécution est très importante car elle conduit à la notion de layer
qui va déterminer le poids final sur le disque de votre image.
Des containeurs éphémères, tu devras :
Cette ligne de conduite ne doit pas s'imposer que pour vos Dockerfiles. C'est pour moi le fondement même de Docker qui doit vous imposer cette pensée.
Je dois toujours construire mon application en ayant en tête que le container que je construis peut-être arrêté, détruit, puis reconstruit pour être remplacé par une nouvelle version sans que cela n'impacte mes données persistantes ou mon fonctionnement.
Le build context :
Lorsque vous allez contruire votre image avec la commande docker build
, docker va rechercher dans votre répertoire courant la présence d'un fichier Dockerfile
.
Le dossier courant va en quelque sorte devenir le dossier "racine" de la construction de l'image. C'est ce qu'on appelle le build context
. Tous les fichiers et dossiers présents dans ce répertoire courant seront envoyés à Docker. En aucun cas, Docker ne pourra récupérer des dossiers présents sur le disque en dehors de ce context de génération.
Il est possible de construire une image depuis un autre dossier en utilisant l'option -f
de docker build
.
Exclure des fichiers du context :
Il est possible d'exclure des fichiers non pertinents pour la génération de votre image et donc du context à l'aide d'un fichier .dockerignore
.
Ce fichier est très similaire au fichier gitignore
.
Minimiser le nombre de layers :
Lors de l'introduction, je vous ai parlé des layers
: ces couches générées par Docker lors de la construction de votre image.
Dans les anciennes versions de Docker, il était important de minimiser le nombre de couches des images pour s'assurer qu'elles restent performantes. Afin de faciliter notre tâche, Docker a introduit depuis plusieurs versions, des modifications au niveau des layers :
- Seules les instructions RUN, COPY, ADD créent des couches. Les autres instructions créent des images intermédiaires temporaires mais qui n'augmentent pas la taille de l'image finale.
Dans la mesure du possible, je vous conseille tout de même de limiter le nombre de couches générées afin d'éviter une taille de l'image trop excessive.
Trier vos arguments multi-lignes :
Un exemple concret vous permettra de comprendre rapidement :
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
Lister les arguments multi-lignes par ordre alphanumérique vous permettra une relecture plus facile du fichier. Cela facilitera votre maintenance du ficher et permet également d'éviter la duplication des packages.
Une barre oblique inverse \
vous permet également de revenir à la ligne.
Le cache APT, toujours tu videras :
L'une des premières instructions que vous allez réaliser à l'aide de l'instruction RUN
sera probablement ... APT-GET
.
Voici quelques règles d'utilisation :
- Toujours combiner
apt-get update
etapt-get install
dans la même ligne.- Utiliser
apt-get update
seul dans unRUN
entrainera des problèmes de cache et de version de paquets.- Toujours supprimer le cache
APT
après votre installation. Cela réduira le poids de l'image car il ne sera pas stocké dans lelayer
.
ADD ou COPY :
Les deux commandes, ADD
et COPY
sont similaires. Mais plus généralement, il est préférable d'utiliser COPY
.
Cette instruction permet uniquement la copie de fichiers stockés localement dans le container.
Alors que ADD
possède bien plus d'options. Comme par exemple l'extraction de fichier tar ou la copie de fichiers depuis une URL.
Exemple :
Nous n'avons pas vu toutes les subtilités de la création d'un Dockerfile
mais nous pouvons déjà réaliser notre première image afin de mettre en pratique une partie des bases apportées ici.
Pour cet exemple, je vais construire une image "custom" de PHP
avec apache
. Une image est déjà présente sur le Docker Hub : php:7-apache
Par contre cette image ne possède la librairie gd
d'installée par défaut. Nous allons donc créer notre image, à partir de cette base :
FROM php:7-apache
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& rm -rf /var/lib/apt/lists/*
- On part de l'image php:7-apache :
FROM php:7-apache
- Ensuite je mets à jour le dépôt APT et j'installe les dépendances pour la compilation de gd.
docker-php-ext-configure
etdocker-php-ext-install
vont compiler et installer gd pour moi, il s'agit de commandes mises à disposition dans l'image d'origine.- Enfin je supprime le cache
APT
Vous remarquerez que la compilation peut prendre un peu de temps, nous verrons dans un prochain article comment améliorer le temps de traitement avec l'utilisation de buildkit
.
Mais ce premier exemple vous montre un cas concret d'utilisation d'une image custom. Vous pouvez même intégrer la construction de l'image directement dans votre fichier docker-compose.yml
:
version: "3.7"
services:
apache:
build: ./apache/
image: monimage:latest
Ici on va construire l'image dans le context
( dossier ) ./apache/
grâce au fichier Dockerfile
présent dans ce dossier.
Une fois construite, l'image sera nommée via l'option image: monimage:latest
.vous pouvez donc utiliser la même image dans plusieurs stacks différentes !
Nous avons donc pu voir ensemble comment créer vos propres images Docker en respectant au mieux la philosophie des micro-services.
Construire ses images offre bien des avantages, on peut construire son container sur mesure. Mais attention un "grand pouvoir implique de grandes responsabilités" :
Effectivement, nous avons construit notre image "php - apache" en version 7.4.3, mais que va-t-il se passer à la sortie de la 7.4.4 de PHP ? Vous allez mettre vos images à jour ? Faudrait-il automatiser tout ça ?
D'autres questions vont également se poser : Mon build est trop long à générer, comment je peux gagner du temps ? Est-ce que je peux construire une image à partir d'une autre sans créer deux fichiers Dockerfile ?
Ce sont des notions que nous aurons l'occasion d'aborder dans un prochain article sur l'utilisation de buildkit
et du multi-stage
.
En tout cas n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter