打造最小 Go Docker Image

簡短幾步驟輕鬆打造最小 Docker Image

ta-ching chen

2 minute read

Hello World

先寫個簡單不斷印出 Hello World 的小程式

// main.go
package main

import (
    "fmt"
    "time"
)

func main() {
    for {
        fmt.Println("Hello World!")
        time.Sleep(1 * time.Second)
    }
}

編譯

若是 native build 的話 Go 預設會打開 CGO_ENABLED 這個參數,主要是讓 Go 執行檔能夠直接連結到系統上的函式庫(libraries),而不必另外打包進執行檔。但由於 scratch 這個特別的 Docker image 本身空空如也,連基本的函式庫也沒有。因此我們需要關閉 CGO_ENABLED 讓編譯器能夠將程式所需的函式庫一起打包進執行檔內。

如果是 cross compilation 的話,記得要加入 GOOSGOARCH 這兩個參數。

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main

Dockerize

我們只需要三行便完成所需的 Dockerfile

FROM scratch
ADD main /
CMD ["/main"]

構建映像檔

$ docker build -t minimal_go_docker_img .
$ docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
minimal_go_docker_img             1.0                 8f836c8b219a        17 minutes ago      1.638 MB

嘗試執行

$ docker run --rm minimal_go_docker_img

加入 SSL 憑證

如果程式有需要存取 HTTPS 相關服務,記得要放入 CA 憑證。

通常你可以在任意 Linux distribution 的 /etc/ssl/certs/ 底下找到 ca-certificates.crt

FROM scratch
ADD ca-certificates.crt /etc/ssl/certs/
ADD main /
CMD ["/main"]

使用多階段構建改善持續交付流程 (2018/01/22)

經過前面的步驟可以知道產生最小的 docker 映像檔,我們必須分別先編譯好需要的 artifacts 然後放進 scratch 裡面,可以想見對於建構一個持續交付的流水線 (Continuous Delivery Pipeline) ,上述步驟會變得過於臃腫與繁雜。

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

首先,我們將前面 Dockerfile 稍作修改

# 將 golang:1.9.2 命名成 go-builder
# 可至 https://hub.docker.com/_/golang/ 找尋對應的 Go 版本替換
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 scratch
# 用 COPY --from=<base_image_name> 從改映像檔取得剛剛產生的 artifact
# 或者可以用 COPY --from=0,意味著從第一個映像檔複製檔案
COPY --from=go-builder /go/main /main
CMD ["/main"]

小技巧

  1. FROM <base_image> as <alias>

    • 為中間映像檔命名,能夠提高整體可讀性
    • 未來更換映像檔時可以不需跟著修改 Dockerfile
  2. COPY --from=<base_image_name>

    • 多利用映像檔名字取代數字,同樣可提高可讀性及維護性

再次構建映像檔

$ docker build -t multi_stage_minimal_go_docker_img .

可以看到類似下面的 log outputs

Sending build context to Docker daemon  7.168kB
Step 1/7 : FROM golang:onbuild
# Executing 3 build triggers...
Step 1/1 : COPY . /go/src/app
Step 1/1 : RUN go-wrapper download
 ---> Running in 5755303bcf11
+ exec go get -v -d
Step 1/1 : RUN go-wrapper install
 ---> Running in 0670ccd535d4
+ exec go install -v
app
 ---> b28583d2034e
Removing intermediate container 5755303bcf11
Removing intermediate container 0670ccd535d4
Step 2/7 : WORKDIR /go
 ---> 1d43ded752da
Removing intermediate container c1fa3dde7177
Step 3/7 : COPY *.go /go/
 ---> 523fa478842c
Step 4/7 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .
 ---> Running in 4a8f59c5439c
 ---> 4c54297c9846
Removing intermediate container 4a8f59c5439c
Step 5/7 : FROM scratch
 --->
Step 6/7 : COPY --from=0 /go/main /main
 ---> 6fd3433462a5
Step 7/7 : CMD /main
 ---> Running in adcad4c99620
 ---> 395ea1e62708
Removing intermediate container adcad4c99620
Successfully built 395ea1e62708
Successfully tagged multi_stage_minimal_go_docker_img:latest

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

參考連結

comments powered by Disqus