Comment optimiser les images Docker pour la production

L’auteur a sélectionné Code.org pour recevoir un don dans le cadre du programme Write for DOnations .

introduction

Dans un environnement de production, Docker facilite la création, le déploiement et l’exécution d’applications à l’intérieur de conteneurs. Les conteneurs permettent aux développeurs de regrouper les applications ainsi que toutes leurs nécessités et dépendances essentielles dans un seul package que vous pouvez transformer en image Docker et répliquer. Les images Docker sont générées à partir de Dockerfiles. Le fichier Dockerfile est un fichier dans lequel vous définissez l’image, le système d’exploitation de base qu’elle aura et les commandes à exécuter.

Les grandes images Docker peuvent allonger le temps nécessaire à la création et à l’envoi d’images entre clusters et fournisseurs de cloud. Si, par exemple, vous avez une image de la taille d’un gigaoctet à appliquer chaque fois qu’un de vos développeurs déclenche une génération, le débit que vous créez sur votre réseau s’accumule pendant le processus de CI / CD, ce qui rend votre application lente et vous coûte en fin de compte des ressources. . Pour cette raison, les images Docker adaptées à la production ne doivent contenir que le strict nécessaire.

Il existe plusieurs façons de réduire la taille des images Docker afin d’optimiser la production. Tout d’abord, ces images n’ont généralement pas besoin d’outils de développement pour exécuter leurs applications, il n’a donc pas besoin de les ajouter du tout. En utilisant un processus de génération multi-stage, vous pouvez utiliser des images intermédiaires pour compiler et générer le code, installer des dépendances et intégrer le tout dans la plus petite taille possible, puis copiez la version finale de votre application sur une image vide sans outils de génération. De plus, vous pouvez utiliser une image avec une base minuscule, telle que Alpine Linux. Alpine est une distribution Linux adaptée à la production car elle ne dispose que du strict nécessaire pour que votre application puisse fonctionner.

Dans ce didacticiel, vous allez optimiser les images Docker en quelques étapes simples, en les rendant plus petites, plus rapides et mieux adaptées à la production. Vous construirez des images pour un exemple d’API Go dans plusieurs conteneurs Docker différents, en commençant par Ubuntu et des images spécifiques à une langue, puis en passant à Alpine. Distribution. Vous utiliserez également des versions en plusieurs étapes pour optimiser vos images en vue de leur production. L’objectif final de ce didacticiel est de montrer la différence de taille entre l’utilisation d’images par défaut d’Ubuntu et de contreparties optimisées, ainsi que de montrer l’avantage des constructions à plusieurs étapes. Après avoir lu ce didacticiel, vous pourrez appliquer ces techniques à vos propres projets et pipelines CI / CD.

Conditions préalables

Avant de commencer, vous aurez besoin de:

Étape 1 - Téléchargement de l’exemple d’API Go

Avant d’optimiser votre image Docker, vous devez d’abord télécharger l’API sample à partir de laquelle vous construirez vos images Docker. L’utilisation d’une simple API Go présentera toutes les étapes clés de la création et de l’exécution d’une application dans un conteneur Docker. Ce tutoriel utilise Go car il s’agit d’un langage compilé tel que C++ ou Java, mais contrairement à eux , a une très petite empreinte.

Sur votre serveur, commencez par cloner l’exemple d’API Go:

git clone https://github.com/do-community/mux-go-api.git

Une fois le projet cloné, vous aurez un répertoire nommé + mux-go-api w sur votre serveur. Déplacez-vous dans ce répertoire avec + cd +:

cd mux-go-api

Ce sera le répertoire de base de votre projet. Vous construirez vos images Docker à partir de ce répertoire. À l’intérieur, vous trouverez le code source d’une API écrite avec Go dans le fichier + api.go +. Bien que cette API soit minimale et ne comporte que quelques points de terminaison, elle conviendra pour simuler une API prête pour la production aux fins de ce didacticiel.

Maintenant que vous avez téléchargé l’exemple d’API Go, vous êtes prêt à créer une image de base Ubuntu Docker, à laquelle vous pourrez comparer les images Docker optimisées les plus récentes.

Étape 2 - Construire une image de base Ubuntu

Pour votre première image Docker, il sera utile de voir à quoi ça ressemble quand vous commencez avec une image de base Ubuntu. Cela conditionnera votre exemple d’API dans un environnement similaire au logiciel que vous exécutez déjà sur votre serveur Ubuntu. Dans l’image, vous allez installer les différents packages et modules nécessaires à l’exécution de votre application. Vous constaterez cependant que ce processus crée une image Ubuntu plutôt lourde qui affectera le temps de construction et la lisibilité du code de votre fichier Docker.

Commencez par écrire un fichier Docker qui indique à Docker de créer une image Ubuntu, d’installer Go et d’exécuter l’exemple d’API. Assurez-vous de créer le fichier Docker dans le répertoire du référentiel cloné. Si vous avez cloné dans le répertoire personnel, il devrait s’agir de «+ $ HOME / mux-go-api».

Créez un nouveau fichier nommé + Dockerfile.ubuntu. Ouvrez-le dans + nano + ou dans votre éditeur de texte préféré:

nano ~/mux-go-api/Dockerfile.ubuntu

Dans ce fichier Docker, vous définissez une image Ubuntu et installez Golang. Ensuite, vous allez installer les dépendances nécessaires et construire le binaire. Ajoutez le contenu suivant à + ​​Dockerfile.ubuntu:

~ / mux-go-api / Dockerfile.ubuntu

FROM ubuntu:18.04

RUN apt-get update -y \
 && apt-get install -y git gcc make golang-

ENV GOROOT /usr/lib/go-
ENV PATH $GOROOT/bin:$PATH
ENV GOPATH /root/go
ENV APIPATH /root/go/src/api

WORKDIR $APIPATH
COPY . .

RUN \
 go get -d -v \
 && go install -v \
 && go build

EXPOSE 3000
CMD ["./api"]

En commençant par le haut, la commande + FROM + spécifie le système d’exploitation de base de l’image. Ensuite, la commande + RUN + installe le langage Go lors de la création de l’image. + ENV + définit les variables d’environnement spécifiques dont le compilateur Go a besoin pour fonctionner correctement. + WORKDIR + spécifie le répertoire dans lequel nous voulons copier le code, et la commande + COPY + prend le code du répertoire où se trouve + Dockerfile.ubuntu + et le copie dans l’image. La dernière commande + RUN installe les dépendances nécessaires au code source pour compiler et exécuter l’API.

Enregistrez et quittez le fichier. Vous pouvez maintenant lancer la commande + build + pour créer une image Docker à partir du fichier Docker que vous venez de créer:

docker build -f Dockerfile.ubuntu -t ubuntu .

La commande + build + construit une image à partir d’un fichier Docker. L’indicateur + -f + indique que vous voulez construire à partir du fichier + Dockerfile.ubuntu +, tandis que + -t + signifie tag, ce qui signifie que vous le balisez avec le nom + ubuntu +. Le dernier point représente le contexte actuel où se trouve + Dockerfile.ubuntu +.

Cela prendra un moment, alors n’hésitez pas à faire une pause. Une fois la construction terminée, vous aurez une image Ubuntu prête à exécuter votre API. Mais la taille finale de l’image pourrait ne pas être idéale; tout ce qui dépasse quelques centaines de Mo pour cette API serait considéré comme une image trop grande.

Exécutez la commande suivante pour répertorier toutes les images Docker et trouver la taille de votre image Ubuntu:

docker images

La sortie affiche l’image que vous venez de créer:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
ubuntu      latest  61b2096f6871    33 seconds ago
. . .

Comme indiqué dans le résultat, cette image a une taille de * 636 Mo * pour une API Golang de base, nombre qui peut varier légèrement d’une machine à l’autre. Sur plusieurs versions, cette taille importante affectera considérablement les temps de déploiement et le débit du réseau.

Dans cette section, vous avez construit une image Ubuntu avec tous les outils Go nécessaires et les dépendances pour exécuter l’API clonée à l’étape 1. Dans la section suivante, vous utiliserez une image Docker prédéfinie, spécifique à la langue, pour simplifier votre fichier Docker et rationaliser le processus de construction.

Étape 3 - Création d’une image de base spécifique à une langue

Les images prédéfinies sont des images de base ordinaires que les utilisateurs ont modifiées pour inclure des outils spécifiques à la situation. Les utilisateurs peuvent ensuite transférer ces images dans le référentiel d’images Docker Hub, permettant ainsi à d’autres utilisateurs d’utiliser l’image partagée au lieu de devoir écrire leurs propres fichiers Dockerfiles. Il s’agit d’un processus courant en situation de production. Vous pouvez trouver diverses images prédéfinies sur Docker Hub pour presque tous les cas d’utilisation. Au cours de cette étape, vous construirez votre exemple d’API en utilisant une image spécifique à Go sur laquelle le compilateur et les dépendances sont déjà installés.

Avec des images de base prédéfinies contenant déjà les outils dont vous avez besoin pour créer et exécuter votre application, vous pouvez réduire considérablement le temps de génération. Étant donné que vous commencez avec une base sur laquelle tous les outils nécessaires ont été préinstallés, vous pouvez ignorer leur ajout à votre fichier Docker, ce qui lui donne un aspect beaucoup plus propre et, en définitive, une réduction du temps de compilation.

Allez-y et créez un autre fichier Docker et nommez-le + Dockerfile.golang +. Ouvrez-le dans votre éditeur de texte:

nano ~/mux-go-api/Dockerfile.golang

Ce fichier sera beaucoup plus concis que le précédent car il contient toutes les dépendances, outils et compilateurs spécifiques à Go préinstallés.

Maintenant, ajoutez les lignes suivantes:

~ / mux-go-api / Dockerfile.golang

FROM golang:

WORKDIR /go/src/api
COPY . .

RUN \
   go get -d -v \
   && go install -v \
   && go build

EXPOSE 3000
CMD ["./api"]

En commençant par le haut, vous constaterez que l’instruction + FROM + est maintenant + golang: +. Cela signifie que Docker récupérera une image Go pré-construite de Docker Hub sur laquelle tous les outils Go nécessaires sont déjà installés.

Maintenant, encore une fois, construisez l’image Docker avec:

docker build -f Dockerfile.golang -t golang .

Vérifiez la taille finale de l’image avec la commande suivante:

docker images

Cela donnera un résultat similaire à celui-ci:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
golang      latest  eaee5f524da2    40 seconds ago
. . .

Bien que le fichier Docker lui-même soit plus efficace et que le temps de construction soit plus court, la taille totale de l’image a en fait augmenté. L’image Golang prédéfinie fait environ 744 Mo *, ce qui est considérable.

C’est le moyen privilégié de créer des images Docker. Il vous donne une image de base que la communauté a approuvée comme norme à utiliser pour la langue spécifiée, dans ce cas, Go. Cependant, pour préparer une image en vue de sa production, vous devez supprimer les éléments inutiles de l’application en cours d’exécution.

N’oubliez pas que l’utilisation de ces images volumineuses est acceptable lorsque vous n’êtes pas sûr de vos besoins. N’hésitez pas à les utiliser à la fois comme conteneurs jetables et comme base pour créer d’autres images. Pour des besoins de développement ou de test, lorsque vous n’avez pas besoin d’envisager l’envoi d’images via le réseau, vous pouvez parfaitement utiliser des images volumineuses. Mais si vous souhaitez optimiser les déploiements, vous devez faire de votre mieux pour rendre vos images aussi petites que possible.

Maintenant que vous avez testé une image spécifique à une langue, vous pouvez passer à l’étape suivante, qui consiste à utiliser la distribution légère Alpine Linux comme image de base pour alléger votre image Docker.

Étape 4 - Construire des images de base alpines

L’une des étapes les plus simples pour optimiser vos images Docker consiste à utiliser des images de base plus petites. Alpine est une distribution Linux légère conçue pour la sécurité et l’efficacité des ressources. L’image Alpine Docker utilise musl libc et BusyBox pour rester compacte, ne nécessitant pas plus de 8 Mo dans un conteneur pour s’exécuter. . La petite taille est due au fait que les paquets binaires ont été améliorés et divisés, ce qui vous permet de mieux contrôler ce que vous installez, ce qui permet de garder l’environnement aussi petit et efficace que possible.

Le processus de création d’une image Alpine est similaire à la création de l’image Ubuntu à l’étape 2. Tout d’abord, créez un nouveau fichier nommé + Dockerfile.alpine +:

nano ~/mux-go-api/Dockerfile.alpine

Maintenant, ajoutez cet extrait:

~ / mux-go-api / Dockerfile.alpine

FROM alpine:

RUN apk add --no-cache \
   ca-certificates \
   git \
   gcc \
   musl-dev \
   openssl \
   go

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
ENV APIPATH $GOPATH/src/api
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" "$APIPATH" && chmod -R 777 "$GOPATH"

WORKDIR $APIPATH
COPY . .

RUN \
   go get -d -v \
   && go install -v \
   && go build

EXPOSE 3000
CMD ["./api"]

Vous ajoutez ici la commande + apk add + pour utiliser le gestionnaire de paquets d’Alpine pour installer Go et toutes les bibliothèques dont il a besoin. Comme pour l’image Ubuntu, vous devez également définir les variables d’environnement.

Allez-y et construisez l’image:

docker build -f Dockerfile.alpine -t alpine .

Encore une fois, vérifiez la taille de l’image:

docker images

Vous recevrez une sortie similaire à celle-ci:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
alpine      latest  ee35a601158d    30 seconds ago
. . .

La taille est descendue à environ * 426Mo *.

La petite taille de l’image de base Alpine a réduit la taille de l’image finale, mais vous pouvez effectuer quelques opérations supplémentaires pour la réduire encore.

Ensuite, essayez d’utiliser une image Alpine pré-construite pour Go. Cela raccourcira le fichier Dockerfile et réduira également la taille de l’image finale. Étant donné que l’image Alpine pré-construite pour Go est construite avec Go compilé à partir des sources, son encombrement est considérablement réduit.

Commencez par créer un nouveau fichier appelé ++:

nano ~/mux-go-api/Dockerfile.golang-alpine

Ajoutez le contenu suivant au fichier:

~ / mux-go-api / Dockerfile.golang-alpine

FROM golang:

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
 && go install -v \
 && go build

EXPOSE 3000
CMD ["./api"]

Les seules différences entre + Dockerfile.golang-alpine + et + + Dockerfile.alpine + sont la commande + FROM + et la première commande + RUN +. Maintenant, la commande + FROM + spécifie une image + golang + avec la balise ++, et + RUN + ne dispose que d’une commande d’installation de Git. Vous avez besoin de Git pour que la commande + go get` fonctionne dans la deuxième commande` + RUN + au bas de + Dockerfile.golang-alpine`.

Construisez l’image avec la commande suivante:

docker build -f Dockerfile.golang-alpine -t golang-alpine .

Récupérez votre liste d’images:

docker images

Vous recevrez le résultat suivant:

OutputREPOSITORY      TAG     IMAGE ID        CREATED         SIZE
golang-alpine   latest  97103a8b912b    49 seconds ago

Maintenant, la taille de l’image est réduite à environ * 288 Mo *.

Bien que vous ayez réussi à réduire considérablement la taille, il vous reste une dernière chose à faire pour que l’image soit prête pour la production. C’est ce qu’on appelle une construction en plusieurs étapes. En utilisant des versions à plusieurs étapes, vous pouvez utiliser une image pour créer l’application, tout en utilisant une autre image plus claire pour conditionner l’application compilée à des fins de production, processus que vous allez exécuter à l’étape suivante.

Étape 5 - Exclure les outils de génération avec une construction en plusieurs étapes

Idéalement, les images que vous exécutez en production ne doivent pas comporter d’outil de construction ni de dépendances redondantes pour l’exécution de l’application de production. Vous pouvez les supprimer de l’image finale de Docker à l’aide de versions à plusieurs étapes. Cela fonctionne en construisant le binaire, ou en d’autres termes, l’application Go compilée, dans un conteneur intermédiaire, puis en le copiant dans un conteneur vide qui n’a pas de dépendances inutiles.

Commencez par créer un autre fichier appelé ++:

nano ~/mux-go-api/Dockerfile.multistage

Ce que vous allez ajouter ici vous sera familier. Commencez par ajouter exactement le même code qu’avec + Dockerfile.golang-alpine +. Mais cette fois-ci, ajoutez également une deuxième image sur laquelle vous allez copier le binaire de la première image.

~ / mux-go-api / Dockerfile.multistage

FROM golang:1.10-alpine3.8

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
 && go install -v \
 && go build

##

FROM alpine:3.8
COPY  /go/bin/api /go/bin/
EXPOSE 3000
CMD ["/go/bin/api"]

Enregistrez et fermez le fichier. Ici vous avez deux commandes + FROM +. Le premier est identique à + ​​Dockerfile.golang-alpine +, à l’exception de la présence d’un + AS à plusieurs étages + dans la commande + FROM +. Cela lui donnera le nom + multiétage +, que vous référencerez ensuite dans la partie inférieure du fichier + Dockerfile.multistage +. Dans la seconde commande + FROM, vous utiliserez une image` + alpine + de base et + + COPY + sur l’application Go compilée à partir de l’image + multiétage. Ce processus réduira davantage la taille de l’image finale, la préparant ainsi pour la production.

Exécutez la construction avec la commande suivante:

docker build -f Dockerfile.multistage -t prod .

Vérifiez la taille de l’image maintenant, après avoir utilisé une construction en plusieurs étapes.

docker images

Vous trouverez deux nouvelles images au lieu d’une seule:

OutputREPOSITORY      TAG     IMAGE ID        CREATED         SIZE
prod            latest  82fc005abc40    38 seconds ago
<none>          <none>  d7855c8f8280    38 seconds ago
. . .

L’image + <none> + est l’image + plusieurs étages + construite avec la commande + FROM golang: 1.10-alpine3.8 +. C’est seulement un intermédiaire utilisé pour construire et compiler l’application Go, tandis que l’image + prod + dans ce contexte est l’image finale qui ne contient que l’application Go compilée.

À partir de 744 Mo *, vous avez maintenant réduit la taille de l’image à environ * 11,3 Mo *. Garder une trace d’une image aussi petite que celle-ci et l’envoyer sur le réseau à vos serveurs de production sera beaucoup plus facile qu’avec une image de plus de 700 Mo et vous permettra d’économiser des ressources importantes à long terme.

Conclusion

Dans ce didacticiel, vous avez optimisé les images Docker pour la production en utilisant différentes images Docker de base et une image intermédiaire pour compiler et générer le code. De cette façon, vous avez empaqueté votre exemple d’API dans la plus petite taille possible. Vous pouvez utiliser ces techniques pour améliorer la vitesse de génération et de déploiement de vos applications Docker et de tout pipeline CI / CD que vous pourriez avoir.

Si vous souhaitez en savoir plus sur la création d’applications avec Docker, consultez notre How To Construire une application Node.js avec Docker. Pour plus d’informations conceptuelles sur l’optimisation des conteneurs, voir Construire des conteneurs optimisés pour Kubernetes.