Skip to main content

3 conseils pour reconstruire une image Docker plus rapidement (et gagner des secondes sur la CI !)

Écrit par
feature docker rebuild

28 décembre 2023

0 minutes de lecture

Docker est devenu un outil indispensable qui simplifie la création, la livraison et l’exécution des applications. Il permet aux développeurs de regrouper une application et toutes ses dépendances dans une unité standardisée appelée image de conteneur. 

# Example of a Dockerfile
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

Le Dockerfile est un simple document qui contient toutes les commandes nécessaires à la création d’une image de conteneur. Une fois exécutée, cette image sert de base à un conteneur Docker.

Ces conteneurs sont des packages légers, autonomes et exécutables qui regroupent tout ce dont un logiciel a besoin pour fonctionner : le code, les outils système, les bibliothèques et les paramètres.

Le principal atout des conteneurs est leur portabilité. Ils garantissent le bon fonctionnement de l’application, quel que soit l’environnement dans lequel elle est déployée. Ils ont considérablement simplifié le processus de développement logiciel et sont devenus un pilier de l’approche DevOps, en facilitant l’intégration et le déploiement continus.

Pourquoi créer des images Docker plus rapidement ?

Même si les images de conteneur sont indispensables à l’exécution des applications dans différents environnements, leur création peut parfois prendre du temps. C’est particulièrement vrai pour les applications volumineuses qui comportent de nombreuses dépendances.

# Building a container image can take time
docker build -t my-application .

Dans l’exemple ci-dessus, Docker lit le Dockerfile dans le répertoire actuel et crée une image appelée my-application. Selon la complexité et la taille de l’application, cette opération peut prendre beaucoup de temps.

L’art de créer des images Docker

La création d’images Docker est une étape essentielle du travail avec les conteneurs. Une image Docker est en quelque sorte un « instantané » d’un conteneur Docker à un moment donné. Elle sert de base à la création de nouveaux conteneurs Docker.

Pour créer une image Docker, il faut rédiger un Dockerfile, un fichier texte contenant toutes les commandes nécessaires. À la manière d’une recette, il indique à Docker quoi faire pour créer une image.

Voici un exemple de Dockerfile simple :

# Use an official Python runtime as a base image
FROM python:3.7-slim

# Set the working directory in the container
WORKDIR /app

# Add the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

Une fois votre Dockerfile prêt, vous pouvez créer une image à l’aide de la commande docker build. Voici à quoi cela peut ressembler :

docker build -t my-python-app .

Cette commande indique à Docker de créer une image à partir du Dockerfile du répertoire actuel (le . à la fin de la commande), et de lui attribuer le tag (-t) my-python-app.

Remarque : Si vous développez en Python ou faites partie d’une équipe DevOps qui crée des conteneurs pour des applications Python, je vous recommande vivement de consulter et de suivre les bonnes pratiques pour conteneuriser des applications Python avec Docker.

Les difficultés courantes liées à la création d’images Docker

La création d’images Docker n’est pas toujours simple. Les développeurs et les équipes DevOps peuvent rencontrer plusieurs difficultés courantes :

  • Gestion des dépendances : gérer les dépendances est l’une des difficultés les plus courantes. Celles de votre application peuvent évoluer au fil du temps, ce qui complique la mise à jour de votre image Docker. Pour y remédier, vous pouvez utiliser un gestionnaire de packages qui prend automatiquement en charge les dépendances.

  • Taille de l’image : il est également difficile de réduire autant que possible la taille des images Docker. Les images volumineuses prennent plus de temps à créer et à envoyer vers un dépôt Docker, ce qui peut ralentir votre pipeline CI/CD. Pour réduire leur taille, choisissez une image de base plus légère, supprimez les fichiers superflus et utilisez des builds multi-étapes afin de limiter le contenu de l’image finale.

  • Sécurité : la sécurité est une préoccupation majeure lors de la création d’images Docker. Vous devez vous assurer qu’elles ne contiennent aucune vulnérabilité que des attaquants pourraient exploiter. Pour vous aider, vous pouvez analyser vos images à la recherche de vulnérabilités avec des outils comme Docker Bench for Security et Snyk Container.

Conseil n° 1 : ajoutez un fichier .dockerignore à votre dépôt

Dans l’univers de Docker et de la sécurité des conteneurs, l’efficacité est essentielle. Si vous êtes développeur ou spécialiste DevOps, vous savez que chaque seconde compte dans les processus d’intégration continue (CI).

Un moyen simple et efficace d’accélérer la création d’images Docker consiste à ajouter un fichier .dockerignore à votre dépôt. Découvrons le fonctionnement d’un fichier .dockerignore, comment il peut accélérer la création de vos images Docker et comment ajouter un fichier .dockerignore à un dépôt, étape par étape.

Qu’est-ce qu’un fichier .dockerignore ?

Comme un fichier .gitignore, un fichier .dockerignore est un fichier texte qui définit des modèles de fichiers et de répertoires que Docker doit ignorer lors de la création d’une image à partir d’un Dockerfile. Autrement dit, il indique à Docker quels fichiers ou répertoires exclure du contexte de build.

# Sample .dockerignore file
node_modules
npm-debug.log
Dockerfile*
*.md

L’extrait de code ci-dessus montre un fichier .dockerignore qui indique à Docker d’ignorer le répertoire node_modules, tous les fichiers npm-debug.log, tous les Dockerfiles (quelle que soit leur extension) ainsi que les fichiers Markdown.

Comment un fichier .dockerignore accélère-t-il la création d’images Docker ?

Quand Docker crée une image à partir d’un Dockerfile, il envoie l’intégralité du contexte de build (c’est-à-dire le répertoire actuel et ses sous-répertoires) au daemon Docker. Sans fichier .dockerignore, celui-ci reçoit des fichiers et des répertoires superflus, ce qui peut considérablement rallonger la durée du build.

En ajoutant un fichier .dockerignore à votre dépôt, vous indiquez à Docker d’ignorer les fichiers et répertoires superflus et réduisez ainsi la taille du contexte de build. Docker peut alors créer les images plus rapidement, vous faisant gagner de précieuses secondes sur la CI.

Ajouter un fichier .dockerignore à un dépôt : guide étape par étape

Voici comment ajouter un fichier .dockerignore à votre dépôt :

1. Créez un fichier nommé .dockerignore à la racine de votre dépôt.

touch .dockerignore


2. Ouvrez le fichier .dockerignore nouvellement créé dans votre éditeur de texte préféré.

nano .dockerignore

3. Ajoutez des modèles pour les fichiers et les répertoires à ignorer lors de la création de l’image Docker. Par exemple, vous pouvez ignorer tous les fichiers journaux et le répertoire node_modules, comme indiqué ci-dessous :

*.log
node_modules/

4. Enregistrez et fermez le fichier .dockerignore. La prochaine fois que vous créerez une image Docker à partir de ce dépôt, Docker ignorera les fichiers et répertoires indiqués, et la création de votre image sera plus rapide !

Un fichier .dockerignore est un outil simple, mais puissant, qui peut considérablement réduire la durée de création des images Docker. En l’ajoutant à vos dépôts, vous pouvez rendre vos processus Docker et CI plus efficaces et plus sûrs.

Conseil n° 2 : utilisez un fichier de verrouillage des dépendances

Un fichier de verrouillage des dépendances est essentiel pour accélérer les builds Docker et les rendre déterministes. Il verrouille les versions des dépendances de votre projet afin que les prochains builds utilisent exactement les mêmes versions. Vous accélérez ainsi vos builds et améliorez la fiabilité et la sécurité de vos images Docker. 

Qu’est-ce qu’un fichier de verrouillage des dépendances et pourquoi est-il important ?

Un fichier de verrouillage des dépendances est généré par votre gestionnaire de packages et décrit les versions exactes et les dépendances de votre projet à un moment donné. C’est comme un instantané des dépendances de votre projet. Dans le contexte de Docker, il répertorie les versions logicielles utilisées pour créer une image Docker donnée.

Ce fichier est essentiel pour plusieurs raisons. Il garantit que vos builds Docker sont déterministes et reproductibles : chaque création de votre image Docker produit le même résultat. C’est crucial pour le débogage et la conformité en matière de sécurité, car vous savez toujours précisément ce que contient votre image Docker.

De plus, un fichier de verrouillage des dépendances peut considérablement accélérer vos builds Docker. Comme il contient les versions exactes de vos dépendances, Docker peut mettre ces couches en cache et ignorer l’étape d’installation lors des prochains builds, qui sont ainsi plus rapides.

Comment un fichier de verrouillage des dépendances accélère les builds et les rend déterministes

Prenons un Dockerfile simple sans fichier de verrouillage :

FROM node:20
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD [ "node", "app.js" ]

À chaque build, npm install récupère les dernières versions des dépendances définies dans package.json. Cette opération prend du temps et rend nos builds non déterministes, car les dépendances installées peuvent varier d’un build à l’autre.

Remarque : Si vous créez des applications Node.js avec Docker, je vous recommande vivement de consulter les 10 bonnes pratiques pour conteneuriser des applications Web Node.js avec Docker.

Utilisons maintenant un fichier de verrouillage :

FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD [ "node", "app.js" ]

Ici, nous copions le fichier package-lock.json dans l’image Docker et utilisons npm ci au lieu de npm install. La commande npm ci s’appuie sur le fichier de verrouillage pour installer les versions exactes des dépendances. Les builds sont ainsi déterministes et Docker peut mettre en cache la couche des dépendances pour accélérer les suivants.

Utiliser un fichier de verrouillage des dépendances avec Docker

Pour utiliser un fichier de verrouillage des dépendances avec Docker, assurez-vous qu’il est inclus dans le contexte de build. Pour cela, copiez-le dans votre image Docker à l’aide de la commande COPY dans votre Dockerfile.

Voici un exemple avec un projet Python utilisant Pipfile.lock :

FROM python:3.7
WORKDIR /app
COPY Pipfile Pipfile.lock ./
RUN pip install pipenv && pipenv install --system --deploy
COPY . .
CMD [ "python", "app.py" ]

Dans ce Dockerfile, nous copions Pipfile et Pipfile.lock dans l’image Docker. Nous utilisons ensuite pipenv install --system --deploy pour installer les versions exactes des dépendances définies dans Pipfile.lock. Vous obtenez ainsi des builds déterministes et optimisez leurs performances en tirant parti du mécanisme de mise en cache de Docker.

Un fichier de verrouillage des dépendances est essentiel pour accélérer les builds Docker et les rendre déterministes. Il garantit la cohérence et la fiabilité de vos images Docker, tout en renforçant leur sécurité : vous connaissez à tout moment les versions logicielles qu’elles contiennent.

Conseil n° 3 : regroupez les commandes selon leur probabilité de changement

Des pratiques efficaces renforcent la sécurité des conteneurs et facilitent le développement logiciel. La création d’images Docker ne fait pas exception. L’une de ces pratiques consiste à regrouper les commandes selon leur probabilité de changement. Cette méthode peut vous faire gagner un temps et des ressources considérables lors de la reconstruction d’images Docker. Voyons comment.

Le principe du regroupement des commandes dans Docker

Dans Docker, chaque commande écrite dans un Dockerfile crée une nouvelle couche dans l’image Docker. Docker met ces couches en cache pour accélérer la création des images. 

Lorsqu’il crée une image, Docker traite chaque commande dans l’ordre. Si une commande n’a pas changé depuis le dernier build, Docker réutilise la couche mise en cache au lieu d’exécuter la commande à nouveau, ce qui réduit considérablement la durée du build.

Cependant, si une seule ligne change, Docker ne peut pas utiliser le cache pour cette ligne ni pour celles qui suivent. Ainsi, si une ligne fréquemment modifiée se trouve au début du Dockerfile, le cache est mal exploité et Docker doit reconstruire la majeure partie de l’image à chaque fois. C’est là que le regroupement des commandes entre en jeu.

Reconstruire plus rapidement une image Docker en regroupant les commandes

En regroupant les commandes et en les organisant selon leur probabilité de changement, vous optimisez l’utilisation du cache Docker et accélérez considérablement la reconstruction des images Docker.

L’objectif est d’organiser votre Dockerfile de sorte que les commandes susceptibles de changer souvent figurent à la fin. Docker peut ainsi réutiliser les couches en cache des commandes inchangées ou rarement mises à jour, et ne reconstruire que les couches correspondant aux commandes modifiées.

Placez en premier les commandes les moins susceptibles de changer

Un autre conseil pour accélérer la reconstruction des images Docker consiste à placer en haut du Dockerfile les commandes les moins susceptibles de changer, afin d’améliorer l’utilisation du cache Docker.

Dans un Dockerfile, chaque commande que vous écrivez crée une nouvelle couche dans l’image Docker. Ces couches s’empilent les unes sur les autres pour former l’image Docker finale. Docker met en cache ces couches afin d’accélérer la création des images. Si une couche n’a pas changé (autrement dit, si la commande est identique à celle des builds précédents), Docker réutilise la version en cache au lieu de reconstruire cette couche.

Le hic, c’est que Docker invalide le cache d’une couche si l’une des couches dont elle dépend a changé. Ainsi, si vous modifiez une commande au début de votre Dockerfile, toutes les couches qui suivent seront reconstruites, même si leurs commandes n’ont pas changé.

En plaçant au début de votre Dockerfile les commandes les moins susceptibles de changer, vous pouvez vous assurer que Docker réutilisera le cache de ces couches pour la plupart de vos builds. Cette approche réduit le nombre de couches que Docker doit reconstruire et accélère ainsi le processus de build.

Exemples pratiques de regroupement des commandes

Prenons un exemple de Dockerfile :

FROM debian:stretch

RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip

COPY . /app

WORKDIR /app

RUN pip3 install -r requirements.txt

CMD ["python3", "app.py"]

Dans ce Dockerfile, la commande COPY . /app est susceptible de changer fréquemment à mesure que vous mettez à jour le code de votre application. En déplaçant cette commande, ainsi que la commande RUN pip3 install -r requirements.txt qui la suit (et qui change également chaque fois que le fichier requirements.txt est modifié), à la fin du Dockerfile, Docker peut réutiliser le cache pour la commande RUN apt-get update && apt-get install -y python3 python3-pip, qui change moins souvent.

Le Dockerfile optimisé ressemblerait à ceci :

FROM debian:stretch

RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip

WORKDIR /app

COPY . /app

RUN pip3 install -r requirements.txt

CMD ["python3", "app.py"]

En regroupant les commandes selon leur probabilité de changer, les développeurs et les équipes DevOps peuvent optimiser la reconstruction des images Docker, gagner de précieuses secondes sur la CI et améliorer la sécurité des conteneurs ainsi que les pratiques de développement logiciel.

Conclusion

L’intérêt de reconstruire plus rapidement les images Docker ne se limite pas au temps gagné. Cela a une influence considérable sur l’efficacité du pipeline CI et sur l’ensemble du processus de développement logiciel.

Un processus de création d’images Docker plus rapide réduit la durée des builds dans le pipeline CI et permet aux développeurs d’obtenir plus rapidement des retours. Le développement logiciel s’en trouve accéléré : les développeurs peuvent itérer plus vite sur leurs modifications.

Cela permet également de réduire les coûts, car le pipeline CI consomme moins de ressources de calcul. Dans le cloud, où les coûts sont souvent directement liés à l’utilisation du processeur et à la durée des builds, accélérer la création des images Docker peut générer des économies importantes à long terme.

Enfin, la posture de sécurité globale de votre application peut s’améliorer. Des builds plus rapides signifient que vous obtenez plus tôt des retours sur les problèmes de sécurité potentiels dans vos images Docker, ce qui permet de les corriger plus rapidement et de réduire ainsi la période d’exposition.

En conclusion, adopter ces stratégies pour reconstruire plus rapidement les images Docker n’est pas un simple plus : c’est une nécessité dans le monde du développement logiciel au rythme effréné d’aujourd’hui. C’est essentiel non seulement pour accélérer et améliorer l’efficacité de votre pipeline CI, mais aussi pour renforcer la sécurité et la rentabilité de votre processus de développement logiciel.

La sécurité des conteneurs pour le DevSecOps

Détectez et corrigez gratuitement les vulnérabilités des conteneurs avec Snyk.

Publié dans:

Lire la suite

Live Stream

Les agents de remédiation démystifiés : pourquoi corriger vaut mieux que détecter

Découvrez comment l’agent de remédiation de Snyk utilise les renseignements de sécurité, l’analyse de la cassabilité et la validation pour transformer les vulnérabilités en pull requests prêtes à être fusionnées.

blog feature snyk secrets aazgdq
Blog

Stoppez la prolifération : Snyk Secrets est désormais en disponibilité générale

Snyk Secrets est désormais en disponibilité générale et apporte la détection contextuelle par ML, la prévention au moment du commit et une gouvernance unifiée des secrets à la Snyk AI Security Platform.

feature insights context
Blog

Dans les coulisses de la compromission de keyv sur npm : malware preinstall, provenance fiable et hooks d’IDE

keyv 6.0.0 et dix versions npm associées ont diffusé un malware à l’installation. Découvrez les versions touchées, les empreintes, les étapes de détection et l’ordre de remédiation recommandé.