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 <NAME>
來為階段命名。以下示例通過命名階段並在 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"]
via: https://docs.docker.com/engine/userguide/eng-image/multistage-build/
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive