Dockerfile 多階段構建實踐

iqsing發表於2021-08-25

寫在前面

在Docker Engine 17.05 中引入了多階段構建,以此降低構建複雜度,同時使縮小映象尺寸更為簡單。這篇小作文我們來學習一下如何編寫實現多階段構建的Dockerfile

關於dockerfile基礎編寫可參考之前docker容器dockerfile詳解


一 、不使用多階段構建

我們知道在Dockerfile中每新增一個指令都會在映象中生產新的層,一個高效的Dockerfile應該在繼續下一層之前清除之前所有不需要的資源。

不使用多階段構建時,我們通常會建立兩dockerfile檔案,一個用於開發及編譯應用,另一個用於構建精簡的生產映象。這樣能比較大限度的減小生產映象的大小。

我們以一個go應用來看看。我首先會建立一個dockerfile,構建這個映象的主要目的就是編譯我們的應用。

FROM golang:1.16
WORKDIR /go/src
COPY app.go ./
#go編譯
RUN go build  -o myapp app.go

構建映象

[root@localhost dockerfiles]# docker build -t builder_app:v1 .
Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM golang:1.16
 ---> 019c7b2e3cb8
Step 2/4 : WORKDIR /go/src
 ---> Using cache
 ---> 15362720e897
Step 3/4 : COPY app.go ./
 ---> Using cache
 ---> 8f14ac97a68a
Step 4/4 : RUN go build  -o myapp app.go
 ---> Running in 4368cc4617a7
Removing intermediate container 4368cc4617a7
 ---> 631f67587803
Successfully built 631f67587803
Successfully tagged builder_app:v1

這樣在這個映象中就包含了我們編譯後的應用myapp,現在我們可以建立容器將myapp拷貝到宿主機等待後續使用。

# docker create --name builder builder_app:v1
fafc1cf7ffa42e06d19430b807d24eafe0bf731fc45ff0ecf31ada5a6075f1d5
# docker cp builder:/go/src/myapp ./

我們有了應用,下一步就是構建生產映象

FROM scratch
WORKDIR /server
COPY myapp ./
CMD ["./myapp"]

由於此時我們不需要其他依賴環境,所以我們採用scratch這個空映象,不僅可以減小容器尺寸,還可以提高安全性。

構建映象

#docker build  --no-cache -t server_app:v1 .

我們看一次構建的兩個映象大小

# docker images 
REPOSITORY            TAG       IMAGE ID       CREATED          SIZE
server_app            v1        6ebc0833cad0   6 minutes ago    1.94MB
builder_app           v1        801f0b615004   23 minutes ago   921MB

顯然在不使用多階段構建時,我們也可以構建出生產映象,但是我們需要維護兩個dockerfile,需要將app遺留到本地,並且帶來了更多儲存空間開銷。在使用多階段構建時能比較好的解決以上問題。


二、使用多階段構建

在一個Dockerfile中使用多個FROM指令,每個FROM都可以使用不同的基映象,並且每條指令都將開始新階段構建。在多階段構建中,我們可以將資源從一個階段複製到另一個階段,在最終映象中只保留我們所需要的內容。

我們將上面例項的兩個Dockerfile合併為如下:

#階段1
FROM golang:1.16
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#階段2
FROM scratch
WORKDIR /server
COPY --from=0 /go/src/myapp ./
CMD ["./myapp"]

構建映象

# docker build --no-cache  -t server_app:v2 .

檢視構建好的映象

# docker images
REPOSITORY            TAG       IMAGE ID       CREATED              SIZE
server_app            v2        20225cb1ea6b   12 seconds ago       1.94MB

這樣我們無需建立額外映象,以更簡單的方式構建出了同樣微小的目標映象。可以看到在多階段構建dockerfile中最關鍵的是COPY --from=0 /go/src/myapp ./ 通過 --from=0指定我們資源來源,這裡的0即是指第一階段。

命令構建階段

預設情況下構建階段沒有名稱,我們可以通過整數0~N來引用,即第一個from從0開始。其實我們還可以在FROM指令中新增AS <NAME> 來命名構建階段,接著在COPY指令中通過<NAME>引用。我們對上面dockerfile修改如下:

#階段1命名為builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#階段2
FROM scratch
WORKDIR /server
#通過名稱引用
COPY --from=builder /go/src/myapp ./
CMD ["./myapp"]
只構建某個階段

構建映象時,您不一定需要構建整個 Dockerfile,我們可以通過--target引數指定某個目標階段構建,比如我們開發階段我們只構建builder階段進行測試。

#docker build --target builder -t builder_app:v2 .
使用外部映象

使用多階段構建時,我們侷限於從之前在 Dockerfile 中建立的階段進行復制。還可以使用COPY --from指令從單獨的映象複製,如本地映象名稱、本地或 Dockerhub上可用的標籤或標籤 ID。Docker 客戶端在必要時會拉取需要的映象到本地。

COPY --from  httpd:latest /usr/local/apache2/conf/httpd.conf ./httpd.conf
從上一階段建立新的階段

我們可以通過FROM指令來引用上一階段作為新階段的開始

#階段1命名為builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#階段2
FROM builder as builder_ex
ADD dest.tar ./
...

通過上面我們對dockerfile多階段構建有了一個整體的瞭解。


NEXT

  • Dockerfile 與Docker容器安全實踐

希望小作文對你有些許幫助,如果內容有誤請指正。

您可以隨意轉載、修改、釋出本文章,無需經過本人同意。 個人blog:iqsing.github.io

相關文章