歡迎您光臨本站 註冊首頁

構建Golang應用最小Docker鏡像的實現

←手機掃碼閱讀     madbeef @ 2020-06-08 , reply:0

我通常使用docker運行我的 golang 程序,在這裡分享一下我構建 docker 鏡像的經驗。我構建 docker 鏡像不僅優化構建後的體積,還要優化構建速度。
 

示例應用
 

首先貼出代碼例子,我們假設要構建一個 http 服務
 

  package main    import (   "fmt"   "net/http"   "time"     "github.com/gin-gonic/gin"  )    func main() {   fmt.Println("Server Ready")   router := gin.Default()   router.GET("/", func(c *gin.Context) {   c.String(200, "hello world, this time is: "+time.Now().Format(time.RFC1123Z))   })   router.GET("/github", func(c *gin.Context) {   _, err := http.Get("https://api.github.com/")   if err != nil {    c.String(500, err.Error())    return   }   c.String(200, "access github api ok")   })     if err := router.Run(":9900"); err != nil {   panic(err)   }  }

 

說明:

  • 這裡選擇 Gin 作為例子,是為了演示我們有第三方包條件下要優化構建速度

  • main函數第一行打印了一行字,為了演示後面啟動時遇到的一個坑

  • 跟路由打印了時間,為了演示後面遇到的關於時區的坑

  • 路由 github 嘗試訪問 https://api.github.com,為了演示後面遇到的證書坑

這裡我們可以先試一試構建後包的體積
 

  $ go build -o server  $ ls -alh | grep server  -rwxrwxrwx 1 eyas eyas 14.6M May 29 10:26 server

 

14.6MB,這是一個http服務的 hello world,當然這是因為使用了 gin ,所以有些大,如果用標準包 net/http 寫的 hello world,體積大概是接近 7 MB
 

Dockerfile 的進化
 

版本一,初步優化
 

先看看第一個版本
 

  FROM golang:1.14-alpine as builder  WORKDIR /usr/src/app  ENV GOPROXY=https://goproxy.cn  COPY ./go.mod ./  COPY ./go.sum ./  RUN go mod download  COPY . .  RUN go build -ldflags "-s -w" -o server    FROM scratch as runner  COPY --from=builder /usr/src/app/server /opt/app/  CMD ["/opt/app/server"]

 

說明:

  • 選擇 golang:1.14-alpine 作為編譯環境,是因為這是體積最小的golang編譯環境

  • 設置 GOPROXY 是為了提升構建速度

  • 先複製 go.mod 和 go.sum ,然後 go mod download,是為了防止每次構建都會重新下載依賴包,利用docker構建緩存提升構建速度

  • go build 時加上 -ldflags "-s -w" 去除構建包的調試信息,減小go構建後程序體積,大概能減小 1/4 吧

  • 使用了多階段構建,也就是 FROM XXX as xxx ,在構建程序包的時候,使用帶編譯環境的鏡像去構建,運行的時候其實完全不需要go的編譯環境,所以在運行階段使用docker的空鏡像 scratch 去運行。這部是減小鏡像體積最有效的方法了。

好了,下面開始構建鏡像
 

  $ docker build -t server .  ...  Successfully built 8d3b91210721  Successfully tagged server:latest

 

到了這一步,構建成功,看看鏡像大小
 

  $ docker images  server     latest     8d3b91210721   1 minutes ago    11MB

 

11MB,還行,現在運行一下
 

  $ docker run -p 9900:9900 server  standard_init_linux.go:211: exec user process caused "no such file or directory"

 

發現啟動報錯了,而且main函數的第一行打印語句都沒有出現,所以整個程序完全沒有運行。錯誤原因是缺少庫依賴文件。這其實是構建的 go 程序還依賴底層的 so 庫文件,不信可以在物理機編譯後看看它的依賴
 

  $ go build -o server  $ ldd server      linux-vdso.so.1 (0x00007ffcfb775000)      libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9a8dc47000)      libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9a8d856000)      /lib64/ld-linux-x86-64.so.2 (0x00007f9a8de66000)

 

這是不是跟我們的認知有點出入呢,說好無依賴的呢,結果還是有幾個依賴庫文件呢,雖然這幾個依賴都是最底層的,一般操作系統都會有,可誰叫我們選了 scratch,這個鏡像裡面除了linux內核以外真的什麼都沒了。
 

這是因為go build 是默認啟用 CGO 的,不信你可以試試這個命令 go env CGO_ENABLED,在 CGO 開啟情況下,無論代碼有沒有用CGO,都會有庫依賴文件,解決方法也很簡單,手動指定關閉CGO就行,而且包體積並不會增加哦,還會減少呢
 

  $ CGO_ENABLED=0 go build -o server  $ ldd server      not a dynamic executable

 

版本二,解決運行時報錯
 

  FROM golang:1.14-alpine as builder  WORKDIR /usr/src/app  ENV GOPROXY=https://goproxy.cn  COPY ./go.mod ./  COPY ./go.sum ./  RUN go mod download  COPY . .  -RUN go build -ldflags "-s -w" -o server  +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server    FROM scratch as runner  COPY --from=builder /usr/src/app/server /opt/app/  CMD ["/opt/app/server"]

 

改動點: go build 前加了 CGO_ENABLED=0
 

  $ docker build -t server .  ...  Successfully built a81385160e25  Successfully tagged server:latest  $ docker run -p 9900:9900 server  [GIN-debug] GET  /             --> main.main.func1 (3 handlers)  [GIN-debug] GET  /github          --> main.main.func2 (3 handlers)  [GIN-debug] Listening and serving HTTP on :9900

 

正常啟動了,我們訪問一下試試,訪問之前看看當前時間
 

  $ date  Fri May 29 13:11:28 CST 2020    $ curl http://localhost:9900      hello world, this time is: Fri, 29 May 2020 05:18:28 +0000    $ curl http://localhost:9900/github  Get "https://api.github.com/": x509: certificate signed by unknown authority

 

發現有問題

  • 當前系統時間是 13:11:28 ,但是根據由顯示的時間是 05:11:53,其實是docker 容器內的時區不對,默認是 0 時區,可是我們國家是 東8區

  • 嘗試訪問 https://api.github.com/ 這是 https 站點,報證書錯誤

解決問題

  • 在容器放置根證書

  • 設置容器時區

版本三,解決運行環境時區與證書問題
 

  FROM golang:1.14-alpine as builder  WORKDIR /usr/src/app  ENV GOPROXY=https://goproxy.cn  +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories &&   + apk add --no-cache ca-certificates tzdata  COPY ./go.mod ./  COPY ./go.sum ./  RUN go mod download  COPY . .  RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server    FROM scratch as runner  +COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/  COPY --from=builder /usr/src/app/server /opt/app/  CMD ["/opt/app/server"]

 

在 builder 階段,安裝了 ca-certificates tzdata 兩個庫,在runner階段,將時區配置和根證書複製了一份
 

  $ docker build -t server .  ...  Successfully built e0825838043d  Successfully tagged server:latest  $ docker run -p 9900:9900 server  [GIN-debug] GET  /             --> main.main.func1 (3 handlers)  [GIN-debug] GET  /github          --> main.main.func2 (3 handlers)  [GIN-debug] Listening and serving HTTP on :9900

 

訪問一下試試
 

  $ date  Fri May 29 13:27:16 CST 2020    $ curl http://localhost:9900      hello world, this time is: Fri, 29 May 2020 13:27:16 +0800    $ curl http://localhost:9900/github  access github api ok

 

一切正常了,看看當前鏡像大小
 

  $ docker images  server     latest     e0825838043d   9 minutes ago    11.3MB

 

才 11.3MB,已經很小了,但是,還可以更小,就是把構建後的包再壓縮一次
 

版本四,進一步減小體積
 

  FROM golang:1.14-alpine as builder  WORKDIR /usr/src/app  ENV GOPROXY=https://goproxy.cn  RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories &&   - apk add --no-cache ca-certificates tzdata  + apk add --no-cache upx ca-certificates tzdata  COPY ./go.mod ./  COPY ./go.sum ./  RUN go mod download  COPY . .  -RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server  +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server &&  + upx --best server -o _upx_server &&   + mv -f _upx_server server    FROM scratch as runner  COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/  COPY --from=builder /usr/src/app/server /opt/app/  CMD ["/opt/app/server"]

 

在 builder 階段,安裝了 upx ,並且go build 完成後,使用 upx 壓縮了一下,執行一下構建,你會發現這個構建時間變長了,這是因為我給 upx 設置的參數是 --best ,也就是最大壓縮級別,這樣壓縮出來的後會儘可能的小,如果嫌慢,可以降低壓縮級別從 -1 到 -9 ,數字越大壓縮級別越高,也越慢。我使用 --best 構建完成後看看鏡像體積。
 

  $ docker build -t server .  ...  Successfully built 80c3f3cde1f7  Successfully tagged server:latest  $ docker images  server     latest     80c3f3cde1f7   1 minutes ago    4.26MB

 

這下子可小了,才 4.26MB,再去試試那兩個接口,一切正常。優化到此結束。
 

最終的Dockerfile
 

  FROM golang:1.14-alpine as builder  WORKDIR /usr/src/app  ENV GOPROXY=https://goproxy.cn  RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories &&    apk add --no-cache upx ca-certificates tzdata  COPY ./go.mod ./  COPY ./go.sum ./  RUN go mod download  COPY . .  RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server &&   upx --best server -o _upx_server &&    mv -f _upx_server server    FROM scratch as runner  COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/  COPY --from=builder /usr/src/app/server /opt/app/  CMD ["/opt/app/server"]

 

總結
 

要減小鏡像體積,首先多階段構建這很重要,這樣就可以把編譯環境和運行環境分開。
 

另外,選擇 scratch 這個鏡像其實很不明智,它雖然很小,但是它太原始了,裡面什麼工具都沒有,程序啟動後,連容器都進不去,就算進去了什麼都做不了。所以就算一昧的追求儘可能小的鏡像體積,也不建議選擇 scratch 作為運行環境,我暫時只踩到小部分的坑,後面還有更多坑沒踩,我也沒有興趣繼續踩 scratch 的坑。
 

建議選擇 alpine ,alpine 的鏡像大小是 5.61MB 這個大小其實還是鏡像解壓後的大小,實際上下載鏡像的時候,只需要下載 2.68 MB 。還有,上文所有我說的鏡像體積,全都是指解壓後的鏡像體積,和實際上傳下載時的體積是不一樣的,docker自己會壓縮一次再傳輸鏡像
 

還有個很小的鏡像是 busybox,它的體積是 1.22MB,下載 705.6 KB ,有大部分的linux命令可用,但是運行環境還是很原始,有興趣可以去嘗試
 

無論是 alpine 還是 busybox ,他們都會上述時區和證書問題,同樣按照上面方法就能解決,切換到 alpine 或者 busybox 也很簡單,只需要修改 runner 基礎鏡像就行
 

  -FROM scratch as runner  +FROM alpine as runner

 

或者
 

  -FROM scratch as runner  +FROM busybox as runne


                                                     

   


[madbeef ] 構建Golang應用最小Docker鏡像的實現已經有300次圍觀

http://coctec.com/docs/docker/show-post-237507.html