1. 程式人生 > >Docker:使用多階段構建映象

Docker:使用多階段構建映象

多階段構建是 Docker 17.05 及更高版本提供的新功能。這對致力於優化 Dockerfile 的人來說,使得 Dockerfile 易於閱讀和維護。

致謝: 特別感謝 Alex Ellis 授權使用他的關於 Docker 多階段構建的部落格文章 Builder pattern vs. Multi-stage builds in Docker 作為以下示例的基礎。

在多階段構建之前

關於構建映象最具挑戰性的事情之一是保持映象體積小巧。 Dockerfile 中的每條指令都會在映象中增加一層,並且在移動到下一層之前,需要記住清除不需要的構件。要編寫一個非常高效的 Dockerfile,你通常需要使用 shell 技巧和其它方式來儘可能地減少層數,並確保每一層都具有上一層所需的構件,而其它任何東西都不需要。

實際上最常見的是,有一個 Dockerfile 用於開發(其中包含構建應用程式所需的所有內容),而另一個裁剪過的用於生產環境,它只包含您的應用程式以及執行它所需的內容。這被稱為“構建器模式”。但是維護兩個 Dockerfile 並不理想。

下面分別是一個 Dockerfile.build 和遵循上面的構建器模式的 Dockerfile 的例子:

Dockerfile.build:

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

注意這個例子還使用 Bash 的 && 運算子人為地將兩個 RUN 命令壓縮在一起,以避免在映象中建立額外的層。這很容易失敗,難以維護。例如,插入另一個命令時,很容易忘記繼續使用 \ 字元。

Dockerfile:

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]

build.sh:

#!/bin/sh
echo Building alexellis2/href-counter:build
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
    -t alexellis2/href-counter:build . -f Dockerfile.build
docker create --name extract alexellis2/href-counter:build
docker cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app

當您執行 build.sh 指令碼時,它會構建第一個映象,從中建立一個容器,以便將該構件複製出來,然後構建第二個映象。 這兩個映象會佔用您的系統的空間,而你仍然會一個 app 構件存放在你的本地磁碟上。

多階段構建大大簡化了這種情況!

使用多階段構建

在多階段構建中,您需要在 Dockerfile 中多次使用 FROM 宣告。每次 FROM 指令可以使用不同的基礎映象,並且每次 FROM 指令都會開始新階段的構建。您可以選擇將構件從一個階段複製到另一個階段,在最終映象中,不會留下您不需要的所有內容。為了演示這是如何工作的,讓我們調整前一節中的 Dockerfile 以使用多階段構建。

Dockerfile:

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

您只需要單一個 Dockerfile。 不需要另外的構建指令碼。只需執行 docker build 即可。

$ docker build -t alexellis2/href-counter:latest .

最終的結果是和以前體積一樣小的生產映象,複雜性顯著降低。您不需要建立任何中間映象,也不需要將任何構件提取到本地系統。

它是如何工作的呢?第二條 FROM 指令以 alpine:latest 映象作為基礎開始新的建造階段。COPY --from=0 這一行將剛才前一個階段產生的構件複製到這個新階段。Go SDK和任何中間構件都被留在那裡,而不會儲存到最終的映象中。

命名您的構建階段

預設情況下,這些階段沒有命名,您可以通過它們的整數來引用它們,從第一個 FROM 指令的 0 開始。但是,你可以通過在 FROM 指令中使用 as來為階段命名。以下示例通過命名階段並在 COPY 指令中使用名稱來改進前一個示例。這意味著,即使您的 Dockerfile 中的指令稍後重新排序,COPY 也不會出問題。

FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]