透過 Multi-Stage Builds 改善持續交付流程

學習利用 Docker multi-stage builds

ta-ching chen

2 minute read

 文章目錄

介紹 

製作容器映像檔時有個常見的原則: 映像檔越小越好

但針對編譯式語言 (e.g. go, java),由於映像檔本身包含編譯環境、相依套件導致最終的映像檔十分肥大。在文章「打造最小 Go Docker Image」,我們必須先編譯好所需的 artifacts 再放進 scratch 裡面。可以想見對於一個建構持續交付的流水線 (Continuous Delivery Pipeline) 的開發人員而言,上述步驟會變得過於臃腫與繁雜。

為解決此問題在 Docker 17.05+ 以後提供名為「多階段構建 (multi-stage builds)」的新功能,透過將原先流水線中的多個映像檔整合進同個 Dockerfile 內,而後續的映像檔可透過指令取得中間映像檔 (intermediate image) 所產生的檔案 (artifacts),如此便能讓整個過程更為簡單,也確保流水線簡潔易懂及提高維護性。

Hands-On 

多階段構建 (multi-stage builds) 範例 dockerfile

FROM alpine AS base
RUN apk add --no-cache curl wget

FROM golang:1.9.2 AS go-builder
WORKDIR /go
COPY *.go /go/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .

FROM base
COPY --from=go-builder /go/main /main
CMD ["/main"]

我們用示意圖解釋整個建構流程中間到底發生什麼事

mulit-stage-builds

Stages 

Stage 0 (Stage base) 

FROM alpine AS base
RUN apk add --no-cache curl wget
  • 利用 as <NAME> 將 stage 0 命名為 base
  • alpine 為 parent image 建立中間映像檔,並在裡面安裝所需的套件 (curl, wget)

Stage 1 (Stage go-builder) 

FROM golang:1.9.2 AS go-builder
WORKDIR /go
COPY *.go /go/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .
  • 將 stage 1 命名為 go-builder
  • 編譯 packages & dependencies

Stage 2 

FROM base
COPY --from=go-builder /go/main /main
CMD ["/main"]
  • 利用 stage 0 (stage base) 建立好的中間映像檔作為 parent image
  • 利用 COPY 取得剛剛在 stage 1 (stage go-builder) 產生的 artifacts
    • COPY --from=1
    • COPY --from=<intermediate_image_name>

小技巧 

  1. FROM <base_image> as <stage_name>

    • 記得為每個 stage 命名,提高整體可讀性
    • 未來若有更多 stage 加入時,可以不需跟著修改現存的 COPY 指令
  2. COPY --from=<stage_name>

    • 多利用 stage 名字取代數字以提高可讀性及維護性

構建映像檔

$ docker build -t multi_stage_minimal_go_docker_img .

可以看到類似下面的 log outputs

Sending build context to Docker daemon  3.072kB
Step 1/9 : FROM alpine AS base
 ---> 3fd9065eaf02
Step 2/9 : RUN apk add --no-cache curl wget
 ---> Running in c1c75ffbfef4
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
(1/5) Installing ca-certificates (20171114-r0)
(2/5) Installing libssh2 (1.8.0-r2)
(3/5) Installing libcurl (7.59.0-r0)
(4/5) Installing curl (7.59.0-r0)
(5/5) Installing wget (1.19.2-r1)
Executing busybox-1.27.2-r7.trigger
Executing ca-certificates-20171114-r0.trigger
OK: 6 MiB in 16 packages
Removing intermediate container c1c75ffbfef4
 ---> 6061a54c31c9
Step 3/9 : FROM golang:1.9.2 AS go-builder
 ---> 138bd936fa29
Step 4/9 : WORKDIR /go
Removing intermediate container e7a1f8df0451
 ---> 8f440d314727
Step 5/9 : COPY *.go /go/
 ---> 95f1803ce6b3
Step 6/9 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .
 ---> Running in 72cba3033d5c
Removing intermediate container 72cba3033d5c
 ---> 9a8875b6eec1
Step 7/9 : FROM base
 ---> 6061a54c31c9
Step 8/9 : COPY --from=go-builder /go/main /main
 ---> 99e564fa87b8
Step 9/9 : CMD ["/main"]
 ---> Running in 7751604e5d0c
Removing intermediate container 7751604e5d0c
 ---> 50aa7d144269
Successfully built 50aa7d144269
Successfully tagged multi_stage_minimal_go_docker_img:latest

透過整合多個 stage 來解決原本繁複的步驟,是不是讓建構持續交付流水線時的負擔降低不少了呢?(笑)

參考連結 

comments powered by Disqus