用 Docker Multi-Stage 編譯出 Go 語言最小 Image

docker

之前應該沒寫過用 Docker 結合 Go 語言編譯出最小 Image 的文章,剛好趁這機會來介紹。其實網路上可以直接找到文章,像是這篇『Building Minimal Docker Containers for Go Applications』,那本文來介紹 Docker 新功能 multi-stage builds,此功能只有在 17.05.0-ce 才支援,看起來是 2017/05/03 號會 release 出來。我們拿 Go 語言的 Hello World 來介紹 Single build 及 Multiple build。

Single build

底下是 Go 語言 Hello World 範例:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

接著用 alpine 的 Go 語言 Image 來編譯出執行檔。

1
2
3
4
5
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o app
ENTRYPOINT ./app

接著執行底下編譯指令:

1
2
$ docker build -t appleboy/go-app .
$ docker run --rm appleboy/go-app

最後檢查看看編譯出來的 Image 大小,使用 docker images | grep go-app,會發現 Image 大小為 258 MB

Multiple build

Multiple build 則是可以在 Dockerfile 使用多個不同的 Image 來源,請看看底下範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# build stage
FROM golang:alpine AS build-env
ADD . /src
RUN cd /src && go build -o app

# final stage
FROM alpine
WORKDIR /app
COPY --from=build-env /src/app /app/
ENTRYPOINT ./app

從上面可以看到透過 AS--from 互相溝通,以前需要寫兩個 Dockerfile,現在只要一個就可以搞定。最後一樣執行編譯指令:

1
2
$ docker build -t appleboy/go-app .
$ docker run --rm appleboy/go-app

會發現最後大小為 6.35 MB,比上面是不是小了很多。

最小 Image?

6.35 MB 是最小的 Image 了嗎?才單單一個 Hello World 執行檔,用 Docker 包起來竟然要 6.35,其實不用這麼大,我們可以透過 Dokcer 所提供的最小 Image: scratch,將執行檔直接包進去即可,在編譯執行檔需加入特定參數才可以:

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

再透過 Docker 包起來

1
2
3
4
5
FROM centurylink/ca-certs

ADD app /

ENTRYPOINT ["/app"]

編譯出來大小為: 1.81MB,相信這是最小的 Image 了。最後用 Docker 來包

1
2
3
4
5
6
7
8
9
# build stage
FROM golang:alpine AS build-env
ADD . /src
RUN cd /src && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app

# final stage
FROM centurylink/ca-certs
COPY --from=build-env /src/app /
ENTRYPOINT ["/app"]

結論

Multiple build 非常方便,這樣就可以將多個步驟全部合併在一個 Dockerfile 處理掉,像是底下例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from debian as build-essential
arg APT_MIRROR
run apt-get update
run apt-get install -y make gcc
workdir /src

from build-essential as foo
copy src1 .
run make

from build-essential as bar
copy src2 .
run make

from alpine
copy --from=foo bin1 .
copy --from=bar bin2 .
cmd ...

用一個 Dockerfile 產生多個執行檔,最後再用 alpine 打包成 Image。

附上本篇程式碼範例


See also