使用 Docker 開發 - 使用多階段構建映象

技術譯民發表於2020-07-04

多階段構建是一個新特性,需要 Docker 17.05 或更高版本的守護程式和客戶端。對於那些努力優化 Dockerfiles 並使其易於閱讀和維護的人來說,多階段構建非常有用。

在多階段構建之前

構建映象時最具挑戰性的事情之一就是縮小映象大小。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=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 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 指令碼,它需要構建第一個映象,從中建立一個容器來複制工件,然後構建第二個映象。這兩個映象在您的系統上佔用空間,並且您的本地磁碟上仍然有 app 工件。

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

使用多階段構建

對於多階段構建,可以在 Dockerfile 中使用多個 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 開始。但是,您可以通過新增一個 AS <NAME>FROM 指令來命名階段。下面示例通過命名階段並在 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"]  

在特定的構建階段停止

在構建映像時,不必構建包括每個階段的整個 Dockerfile。你可以指定目標構建階段。以下命令假設你正在使用之前的 Dockerfile,但是在名為 builder 的階段停止:

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

這可能非常強有力的幾個場景是:

  • 除錯一個特定的構建階段
  • 使用一個啟用了所有除錯符號或工具的 除錯(debug) 階段和一個精益的 生產(production) 階段
  • 使用一個測試(testing)階段,在這個階段你的應用會被測試資料填充,但是在構建產品時,使用一個使用真實資料的不同階段。

docker-multi-stage-medium

使用外部映象作為“階段”

當使用多階段構建時,您不受限於從 Dockerfile 中先前建立的階段進行復制。您可以使用 COPY --from 指令從單獨的映象中進行復制,可以使用本地映象名稱、本地或 Docker 登錄檔上可用的標籤或標籤 ID。Docker 客戶端會在必要時拉取映象並從中複製工件。語法是:

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

把以前的階段作為新的階段

在使用 FROM 指令時,您可以引用前一階段的內容。例如:

FROM alpine:latest as builder
RUN apk --no-cache add build-base

FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp

作者 : Docker 官網
譯者 : 技術譯民
出品 : 技術譯站
連結 : 英文原文

相關文章