Docker 多階段構建最佳實踐

Docker 目前在容器市場可以說是佔領了大部分市場,Docker 掀起了容器革命,同時也改變了現代化雲平臺的構建方式。儘管 Docker 很強大,但使用過程當中也遇到了一些問題。比如說我想要構建一個編譯型語言鏡像,需要先在一個 Dockerfile 中編譯,然後再使用另外一個 Dockerfile 把編譯好的文件放到鏡像中。這樣無形當中就增大了 CI/CD 的複雜度。

Docker 多階段構建是 17.05 以後引入的新特性,旨在解決編譯和構建複雜的問題。減小鏡像大小。因此要使用多階段構建特性必須使用高於或等於 17.05 的 Docker。

多階段構建出現之前

構建鏡像最具挑戰性的一點是使鏡像大小盡可能的小。Dockerfile 中的每條指令都爲圖像添加了一個圖層,您需要記住在移動到下一層之前清理任何不需要的工件。

爲了編寫一個真正高效的 Dockerfile,傳統上需要使用 shell 技巧和其他邏輯來保持層儘可能小,並確保每個層都具有前一層所需的工件而不是其他任何東西。

實際上,有一個 Dockerfile 用於開發(包含構建應用程序所需的所有內容),以及用於生產環境的精簡版 Dockerfile,它只包含您的應用程序以及運行它所需的內容。這被稱爲 “建造者模式”。維護兩個 Dockerfiles 並不理想。

這是一個 Dockerfile.build 和 Dockerfile 的例子,它遵循上面的模式:

Dockerfile.build:

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

請注意,此示例使用 Bash && 運算符人爲壓縮兩個 RUN 命令,以避免在 image 中創建其他層。這很容易出錯並且難以維護。

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 container create --name extract alexellis2/href-counter:build  
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app  
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app

當您運行 build.sh 腳本時,它需要構建第一個 image,從中創建容器以複製工件,然後構建第二個 image。
多階段構建大大簡化了這種情況!

使用多階段構建

對於多階段構建,您可以在 Dockerfile 中使用多個 FROM 語句。每個 FROM 指令可以使用不同的基礎,並且每個指令都開始一個新的構建。您可以選擇性地將工件從一個階段複製到另一個階段,從而在最終 image 中只留下您想要的內容。

爲了說明這是如何工作的,讓我們調整上述示例的 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=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 app:latest .

最終結果是產生與之前相同大小的 image,複雜性顯著降低。您不需要創建任何中間 image,也不需要將任何 artifacts 提取到本地系統。

它是如何工作的?第二個 FROM 指令以 alpine:latest image 爲基礎開始一個新的構建階段。

COPY –from = 0 行僅將前一階段的構建文件複製到此新階段。Go SDK 和任何中間層都被遺忘,而不是保存在最終 image 中。

爲多構建階段命名

默認情況下,階段未命名,您可以通過整數來引用它們,從第 0 個 FROM 指令開始。

但是,您可以通過向 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=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"]

停在特定的構建階段

構建鏡像時,不一定需要構建整個 Dockerfile 每個階段。

您可以指定目標構建階段。以下命令假定您使用的是以前的 Dockerfile,但在名爲 builder 的階段停止:

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

使用此功能可能的一些非常適合的場景是:

使用外部鏡像作爲 stage

使用多階段構建時,您不僅可以從 Dockerfile 中創建的鏡像中進行復制。

您還可以使用 COPY –from 指令從單獨的 image 中複製,使用本地 image 名稱,本地或 Docker 註冊表中可用的標記或標記 ID。
如有必要,Docker 會提取 image 並從那裏開始複製。

語法是:

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

鏈接: http://dockone.io/article/8179

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/RKMZRxjwtkCvE4WBDyv9fQ