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