使用 Docker 開發 - 使用多階段構建鏡像

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

在多階段構建以前

構建鏡像時最具挑戰性的事情之一就是縮小鏡像大小。Dockerfile 中的每一條指令都會在鏡像中添加一個層,在進入下一層以前,您須要記住清除全部不須要的工件。要編寫一個真正高效的 Dockerfile,傳統上須要使用 shell 技巧和其餘邏輯來保持層儘量小,並確保每一層都有它須要的來自前一層的工件,而沒有其餘東西。linux

實際上,有一個 Dockerfile 用於開發環境(包含構建應用程序所需的全部內容),同時有一個精簡的 Dockerfile 用於生產環境(僅包含應用程序和運行應用程序所需的內容)是很是常見的。這被稱爲「建造者模式」。維護兩個 Dockerfiles 並不理想。nginx

這裏有一個例子 Dockerfile.build 文件以及符合上述建造者模式的 Dockerfilegit

Dockerfile.buildgithub

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 命使人爲壓縮在一塊兒,以免在鏡像中建立額外的層。這很容易發生故障,也很難維護。例如,很容易插入另外一個命令而忘記使用 \ 字符繼續行。golang

Dockerfiledocker

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

build.shshell

#!/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 工件。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 官網
譯者 : 技術譯民
出品 : 技術譯站
連接 : 英文原文

相關文章
相關標籤/搜索