DevOps and Infrastructure

Débloquer l'efficacité : Une plongée approfondie dans les builds multi-étapes Docker

Dans le monde des applications conteneurisées, la taille de l'image n'est pas qu'un chiffre : c'est une métrique qui impacte directement la vitesse de déploiement, les coûts de stockage et la surface d'attaque. Pour les développeurs intermédiaires à avancés, le passage des builds mono-étape aux builds multi-étapes est l'une des optimisations les plus critiques dans la boîte à outils d'un ingénieur DevOps. Cet article de blog explore les mécanismes, les avantages et la mise en œuvre pratique des builds multi-étapes Docker.

Le problème des builds mono-étape

Traditionnellement, la construction d'une image Docker consistait à combiner l'environnement de build et l'environnement d'exécution en une seule couche. Prenons l'exemple d'une application Go ou Java typique. Pour compiler le code source, vous avez besoin de compilateurs (comme gcc ou javac), d'outils de build (make, gradle) et potentiellement de bibliothèques de développement. Ces outils ajoutent un poids significatif à l'image finale.

Même si vous supprimez ces dépendances avant d'exécuter l'application, les couches restent dans l'historique de l'image. Cela résulte en des images gonflées qui prennent plus de temps à être poussées et tirées depuis les registries, des temps de démarrage plus lents et une surface d'attaque potentielle plus grande pour les vulnérabilités. Les builds multi-étapes résolvent ce problème en vous permettant d'utiliser plusieurs instructions FROM dans votre Dockerfile, en copiant les artefacts d'une étape à l'autre et en éliminant le gonflement inutile.

Comment fonctionnent les builds multi-étapes

Les builds multi-étapes divisent le processus de construction en étapes distinctes. Chaque instruction FROM commence une nouvelle étape. Vous pouvez donner des noms aux étapes pour faciliter leur référencement. Le concept clé est que seule la dernière étape devient l'image réelle qui est poussée vers votre registry.

Regardons un exemple pratique utilisant une application Go simple. L'objectif est de produire un binaire statique minuscule qui s'exécute dans une image de base Alpine Linux minimale.

Exemple : Optimisation d'une application Go


# Étape 1 : L'environnement de build
FROM golang:1.21-alpine AS builder

# Installer les outils de build nécessaires
RUN apk add --no-cache git

# Définir le répertoire de travail
WORKDIR /app

# Copier les fichiers go.mod et go.sum pour la mise en cache des dépendances
COPY go.mod go.sum ./
RUN go mod download

# Copier le code source
COPY . .

# Construire l'application
RUN go build -o myapp .

# Étape 2 : L'environnement d'exécution
FROM alpine:3.18 AS final

# Installer les certificats CA pour les requêtes HTTPS
RUN apk --no-cache add ca-certificates

# Créer un utilisateur non-root pour la sécurité
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Copier le binaire depuis l'étape builder
COPY --from=builder /app/myapp /usr/local/bin/myapp

# Définir la propriété pour l'utilisateur non-root
RUN chown appuser:appgroup /usr/local/bin/myapp

# Basculer vers l'utilisateur non-root
USER appuser

# Exposer le port (si applicable)
EXPOSE 8080

# Exécuter l'application
CMD ["myapp"]

Dans cet exemple, l'étape builder utilise une image Go complète pour compiler le code. L'étape final utilise une image Alpine minimale, ne copie que le binaire compilé et élimine tous les outils de build et le code source. L'image résultante est considérablement plus petite — souvent moins de 10 Mo par rapport à plusieurs centaines de mégaoctets.

Meilleures pratiques et techniques avancées

Étiquetage des étapes

Toujours étiqueter vos étapes en utilisant AS name. Cela rend votre Dockerfile plus lisible et vous permet de référencer des étapes spécifiques lors du processus de copie en utilisant --from=name.

Optimisation du cache

Placez les commandes qui changent rarement (comme l'installation des dépendances) plus tôt dans le processus de build. Cela exploite la mise en cache par couches de Docker, accélérant les reconstructions lorsque vous modifiez le code source mais pas les dépendances.

Considérations de sécurité

En utilisant une image finale minimale (comme Alpine ou Distroless), vous réduisez le nombre de packages installés et donc le nombre de vulnérabilités potentielles. De plus, exécutez toujours votre application en tant qu'utilisateur non-root pour limiter l'impact des échappements de conteneur.

Conclusion

Les builds multi-étapes ne sont pas seulement une commodité ; ils sont une nécessité pour les conteneurs de qualité production. Ils permettent aux développeurs de découpler l'environnement de build de l'environnement d'exécution, résultant en des images plus petites, plus rapides et plus sécurisées. En adoptant ce modèle, vous rationalisez vos pipelines CI/CD, réduisez les coûts de stockage cloud et améliorez la fiabilité globale de votre infrastructure. Commencez à refactoriser vos Dockerfiles dès aujourd'hui pour profiter des avantages de cette fonctionnalité puissante.

Share: