Zum Hauptinhalt springen

Mehrstufige Builds

Mehrstufige Builds sind nützlich für alle, denen es schwer fiel, Dockerfiles zu optimieren und gleichzeitig gut lesbar und wartbar zu halten.

Ohne mehrstufige Builds

Vor Docker 17.05 gab es typischerweise zwei Möglichkeiten, Docker-Images zu bauen:

Alles in ein Dockerfile

Eine Möglichkeit war, alle Build-Schritte in einem einzigen Dockerfile zu kombinieren, einschließlich der Kompilierung, des Testens und des Paketierens des Projekts und seiner Abhängigkeiten. Dies konnte zu einigen Problemen führen:

  • Mehrere Image-Schichten, größere Image-Größe und längere Deploymentzeiten

  • Risiko der Offenlegung von Quellcode

Zum Beispiel schreiben wir eine app.go-Datei, die Hello World! ausgibt

package main

import "fmt"

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

Schreiben einer Dockerfile.one-Datei

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"]

Bauen des Images

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

Aufteilung in mehrere Dockerfiles

Die andere Möglichkeit war, zunächst in einem Dockerfile das Projekt und seine Abhängigkeiten zu kompilieren, zu testen und zu paketieren und dann die Artefakte in einem anderen Dockerfile in die Laufzeitumgebung zu kopieren. Dieser Ansatz erforderte das Schreiben von zwei Dockerfiles und einiger Build-Skripte, um die Integration der beiden Stufen zu automatisieren, was den Bereitstellungsprozess komplexer machte, obwohl er die Risiken des ersten Ansatzes vermied.

Zum Beispiel schreiben wir eine Dockerfile.build-Datei

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 .

Schreiben einer Dockerfile.copy-Datei

FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]

Erstellen eines build.sh-Skripts

#!/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

Nun führen wir das Skript aus, um das Image zu bauen

$ chmod +x build.sh

$ ./build.sh

Vergleichen der Imagegrößen, die von den beiden Ansätzen erzeugt wurden

$ 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

Verwendung von mehrstufigen Builds

Um die oben genannten Probleme zu lösen, führte Docker in v17.05 die Unterstützung für mehrstufige Builds ein. Mit mehrstufigen Builds können wir die zuvor erwähnten Probleme leicht lösen und brauchen nur ein einziges Dockerfile zu schreiben:

Zum Beispiel schreiben wir eine Dockerfile-Datei

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"]

Bauen des Images

$ docker build -t go/helloworld:3 .

Vergleichen der Größen der drei 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

Es ist klar, dass das mit mehrstufigen Builds erstellte Image kleiner ist und gleichzeitig die zuvor genannten Probleme perfekt löst.

Verwendung einer vorherigen Stufe als neue Stufe

Bei der Verwendung von mehrstufigen Builds sind Sie nicht darauf beschränkt, nur von Stufen zu kopieren, die Sie früher in Ihrem Dockerfile erstellt haben. Sie können die Anweisung COPY --from verwenden, um von einem separaten Image zu kopieren, entweder unter Verwendung des lokalen Imagenamens, eines lokal oder in einem Docker-Registry verfügbaren Tags oder einer Tag-ID. Der Docker-Client lädt das Image bei Bedarf herunter und kopiert das Artefakt von dort. Die Syntax lautet:

FROM golang:alpine as builder

Wenn wir beispielsweise nur das Image für die builder-Stufe bauen möchten, fügen wir den Parameter --target=builder hinzu:

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

Verwendung eines externen Images als Stufe

Sie können dort weitermachen, wo eine vorherige Stufe aufgehört hat, indem Sie sich bei der Verwendung der FROM-Anweisung darauf beziehen.

In dem oben genannten Beispiel haben wir COPY --from=0 /go/src/github.com/go/helloworld/app . verwendet, um Dateien aus dem Image der vorherigen Stufe zu kopieren. Wir können auch Dateien aus einem beliebigen anderen Image kopieren.

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