Aller au contenu principal

Constructions en plusieurs étapes

Les constructions en plusieurs étapes sont utiles pour tous ceux qui ont eu du mal à optimiser les Dockerfiles tout en les gardant faciles à lire et à maintenir.

Sans constructions en plusieurs étapes

Avant Docker 17.05, il y avait généralement deux façons de construire des images Docker :

Mettre tout dans un seul Dockerfile

Une façon était d'inclure toutes les étapes de construction dans un seul Dockerfile, y compris la compilation, les tests et l'empaquetage du projet et de ses dépendances. Cela pourrait entraîner certains problèmes :

  • Plusieurs couches d'image, taille d'image plus importante et temps de déploiement plus longs

  • Risque de fuite du code source

Par exemple, écrire un fichier app.go qui imprime Hello World!

package main

import "fmt"

func main(){
fmt.Printf("Hello World!");
}

Écrire un fichier Dockerfile.one

FROM golang:alpine

RUN apk --no-cache add git ca-certificates

WORKDIR /go/src/github.com/go/helloworld/

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root

WORKDIR /root/

CMD ["./app"]

Construire l'image

$ docker build -t go/helloworld:1 -f Dockerfile.one .

Diviser en plusieurs Dockerfiles

L'autre façon était de compiler, tester et empaqueter le projet et ses dépendances dans un Dockerfile, puis de copier les artefacts dans l'environnement d'exécution dans un autre Dockerfile. Cette approche nécessitait l'écriture de deux Dockerfiles et de quelques scripts de construction pour automatiser l'intégration des deux étapes, ce qui rendait le processus de déploiement plus complexe, bien qu'elle évitait les risques de la première approche.

Par exemple, écrire un fichier Dockerfile.build

FROM golang:alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

Écrire un fichier Dockerfile.copy

FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]

Créer un script build.sh

#!/bin/sh
echo Building go/helloworld:build

docker build -t go/helloworld:build . -f Dockerfile.build

docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract

echo Building go/helloworld:2

docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app

Maintenant, exécuter le script pour construire l'image

$ chmod +x build.sh

$ ./build.sh

Comparer les tailles d'image générées par les deux approches

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB

Utiliser les constructions en plusieurs étapes

Pour résoudre les problèmes ci-dessus, Docker v17.05 a introduit la prise en charge des constructions en plusieurs étapes. En utilisant les constructions en plusieurs étapes, nous pouvons facilement résoudre les problèmes mentionnés précédemment, et nous n'avons besoin que d'écrire un seul Dockerfile :

Par exemple, écrire un fichier Dockerfile

FROM golang:alpine as builder

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld/

RUN go get -d -v github.com/go-sql-driver/mysql

COPY app.go .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest as prod

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=0 /go/src/github.com/go/helloworld/app .

CMD ["./app"]

Construire l'image

$ docker build -t go/helloworld:3 .

Comparer les tailles des trois images

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 d6911ed9c846 7 seconds ago 6.47MB
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB

Il est clair que l'image construite à l'aide de constructions en plusieurs étapes est plus petite en taille, tout en résolvant parfaitement les problèmes mentionnés précédemment.

Utiliser une étape précédente comme une nouvelle étape

Lorsque vous utilisez des constructions en plusieurs étapes, vous n'êtes pas limité à la copie des étapes que vous avez créées précédemment dans votre Dockerfile. Vous pouvez utiliser l'instruction COPY --from pour copier à partir d'une image distincte, en utilisant soit le nom de l'image locale, soit une balise disponible localement ou sur un registre Docker, soit un ID de balise. Le client Docker récupère l'image si nécessaire et copie l'artéfact à partir de là. La syntaxe est :

FROM golang:alpine as builder

Par exemple, si nous voulons seulement construire l'image pour l'étape builder, ajoutez le paramètre --target=builder :

$ docker build --target builder -t username/imagename:tag .

Utiliser une image externe comme étape

Vous pouvez reprendre là où une étape précédente s'est arrêtée en vous y référant lors de l'utilisation de la directive FROM.

Dans l'exemple ci-dessus, nous avons utilisé COPY --from=0 /go/src/github.com/go/helloworld/app . pour copier les fichiers de l'image de l'étape précédente. Nous pouvons également copier des fichiers depuis n'importe quelle autre image.

$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf