歡迎光臨
每天分享高質量文章

如何為你的Go應用創建輕量級Docker鏡像?

恭喜!你已經創建了一個出色的go應用程式,現在你想創建一個Docker容器來分發你的應用。
但是,如何盡可能為你的Golang應用程式打造一個最小的鏡像呢?提示:我們將會使用多階段構建(自從Docker 17.05版本提供的方法)來完成這個標的。
介紹

多什麼?
簡單來講,多階段。
多階段允許在創建Dockerfile時使用多個from,它非常有用,因為它使我們能夠使用所有必需的工具構建應用程式。舉個例子,首先我們使用Golang的基礎鏡像,然後在第二階段的時候使用構建好的鏡像的二進制檔案,最後階段構建出來的鏡像用於發佈到我們自己的倉庫或者是用於上線發佈。
在上述的案例中,我們總共有三個階段:
  • build編譯階段

  • certs(可選,可有可無)證書認證階段

  • prod生產階段

在build階段主要是編譯我們的應用程式,證書認證階段將會安裝我們所需要的CA證書,最後的生產發佈階段會將我們構建好的鏡像推到鏡像倉庫中。而且發佈階段將會使用build階段編譯完畢的二進制檔案和certs階段安裝的證書。
專案發佈的多個build階段
示例工程
對於這個方法,我們將使用一個非常簡單的專案。它只是一個運行在8080端口的HTTP服務,並且傳回結果為傳遞過去的URL的內容結果。
舉例
GET http://localhost:8080?url=https://google.com 傳回結果為goole頁面內容展示。
你也可以在這裡[1]找到代碼倉庫。
在master分支上只包含了應用程式,final分支上還包含本篇教程中使用的Dockerfile檔案。
如果你想跟著本教程來做,只需要拉下master上的代碼並且跟著我來創建Dockerfile。
步驟1 – 編譯階段

第一階段主要是使用Golang基礎鏡像來將我們的應用程式打包為二進制檔案。這個基礎鏡像包含了將我們的應用程式編譯成可執行二進制檔案的所有工具。
下麵是我們最原始的Dockerfile:
1 #
2 # BUILD 階段
3 
4 FROM golang:1.10 AS build
5
6 # 設置我們應用程式的工作目錄
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加所有需要編譯的應用代碼
10 ADD . .
11
12 # 編譯一個靜態的go應用(在二進制構建中包含C語言依賴庫)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 設置我們應用程式的啟動命令
16 CMD ["./blog-multistage-go"]

  • 第4行:使用的基礎鏡像(golang:1.10)並且我們使用as給當前階段一個別名,也可以使用階段索引來取用前一階段,但這使得它更清晰。

  • 第7行:我們將工作目錄設置為Golang基礎鏡像的預設$GOPATH中的應用程式目錄。

  • 第10行:添加我們的應用程式源檔案。

  • 第13行:編譯二進制檔案。使用不同的引數來創建一個完整的靜態庫,因為在生產環境拉取鏡像時可能不一定需要所有的Golang VM以及C語言庫。

  • 第16行:使用設定的命令來啟動應用程式。

現在我們進行編譯並使用Docker容器,我們的應用程式如我們預期正常運行:
docker build -t scboffspring/blog-multistage-go .
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

我們可以使用curl命令來請求,並且它會傳回http://google.com頁面內容。
在終端運行curl localhost:8080。
<html itemscope="" itemtype="http://schema.org/WebPage" lang="de-CH">
2  <head>
3  <meta content="text/html; charset=UTF-8" 
4      http-equiv="Content-Type">

5  <meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" 
6  itemprop="image">
<title>Googletitle>


7 ….

讓我們使用docker images,來看看鏡像的大小:
REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 818MB

荒唐,太荒唐了,一個這麼小的應用居然占了磁盤818M記憶體空間。
推送到鏡像倉庫後,鏡像大小被壓縮到309M。
docker hub 占用309M
接下來我們來改善這種情況,把鏡像的大小降低到10M!
步驟2 – 生產階段

上面提供的鏡像是完全可以進行部署使用的,但是它真的是太大了。每次在Kubernetes上啟動你的容器時需要拉取309M的鏡像?真的是太浪費時間和帶寬。
讓我們來為我們的鏡像構建一個生產階段,正如上面解釋的,這個階段只是從build階段拷貝二進制檔案到容器中。
我們新的Dockerfile將會如下所示:
1 #
2 # BUILD 階段
3 
4 FROM golang:1.10 AS build
5
6 # 設置我們應用程式的工作目錄
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加所有需要編譯的應用代碼
10 ADD . .
11
12 # 編譯一個靜態的go應用(在二進制構建中包含C語言依賴庫)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 設置我們應用程式的啟動命令
16 CMD ["./blog-multistage-go"]
17
18
19
20 #
21 # 生產階段
22 
23 FROM scratch AS prod
24 
25 # 從buil階段拷貝二進制檔案
26 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .
27 CMD ["./blog-multistage-go"]

如你所見,同一個Dockerfile檔案中我們添加了第二個FROM陳述句。這次,我們直接拉取二進制檔案,不需要添加任何其他依賴。
  • 第23行:拉取基礎鏡像

  • 第26行:從/go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go拷貝build階段編譯的檔案

  • 第27行:使用設定的命令來啟動應用程式

簡單吧。
讓我們像之前一樣編譯並使用Docker容器:
docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

我們可以看到服務正常啟動,也就是意味著它正確的啟動了!我們完成了!
讓我們使用docker images,來看看鏡像的大小:
REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 6.65MB

如我們之前所說,鏡像的大小變為10MB以下。而且鏡像被推送到鏡像倉庫後,它只有2MB。當你啟動容器時,只需下載2MB即可,相比於之前節省了大量的時間和帶寬呢。
使用prod階段編譯的容器僅2MB
但是,它在我們的例子中不起作用。 如果運行curl localhost:8080,你看到的傳回的結果為500。
curl localhost:8080
500 - Something bad happened

如果你查看容器的日誌,你可以找到如下錯誤:
發生了一個錯誤:Get http://google.com:X509:加載系統根目錄失敗並且沒有根目錄可以使用。
我們嘗試使用https來連接Goole服務器,但是我們沒有用於驗證Google的SSL證書的CA(證書頒發機構)證書或是其他網站的CA證書。如果你的應用不需要使用SSL的話,可以選擇跳到下一節,否則,讓我們來改善我們的軟體使得其可以進行訪問。
階段3 – (可選)認證階段

1 #
2 # BUILD 階段
3 
4 FROM golang:1.10 AS build
5
6 # 設置我們應用程式的工作目錄
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加所有需要編譯的應用代碼
10 ADD . .
11
12 # 編譯一個靜態的go應用(在二進制構建中包含C語言依賴庫)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 設置我們應用程式的啟動命令
16 CMD ["./blog-multistage-go"]
17
18
19 
20 # CERTS Stage
21 #
22 FROM alpine:latest as certs
23 
24 # Install the CA certificates
25 RUN apk --update add ca-certificates
26 
27 #
28 # PRODUCTION STAGE
29 
30 FROM scratch AS prod
31 
32 # 從certs階段拷貝CA證書
33 COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
34 # 從buil階段拷貝二進制檔案
35 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .
36 CMD ["./blog-multistage-go"]


  • 第23行:我們新的certs階段,使用alpine鏡像

  • 第25行:安裝最新版的CA證書

  • 第33行:從certs層拷貝證書,並儲存為/etc/ssl/certs/ca-certificates.crt

讓我們再次編譯並使用Docker容器:
docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

現在,curl localhost:8080將會傳回真實的頁面!它真的奏效了!
使用docker images查看,鏡像依然還是非常小的:

REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 6.89MB

額外福利:在指定的階段為鏡像添加tag

有時候我們可能會在各個階段為鏡像創建一個tag,在我們的示例中,我們可能也會將build階段產生的結果發佈到Docker,因為它對開發真的十分有用。
要想這樣做的話,只需要在build鏡像的時候簡單的使用–target=NAMEOFTHESTAGE。
舉個例子:
docker build -t scboffspring/blog-multistage-go:build . --target=build

總結

現在你已經能夠為你的Golang應用程式創建一個非常輕量級的應用程式。階段構建的概念對其他許多案例也是非常有用的。
我在NodeJS世界中的一個用法是第一階段編譯TypeScript專案。然後第一個階段編譯以便使得該鏡像可以運行測試。此鏡像也能夠用於開發環境,因為它包含了所有開發環境所需的依賴。
當第一階段測試通過後,第二階段只是簡單的安裝專案中的package.json中的依賴(並不是測試環境依賴)。它只將編譯和縮小的代碼複製到鏡像中,然後將該鏡像推送並部署到生產中。
相關鏈接:
  1. https://github.com/scboffspring/blog-multistage-go

原文鏈接:https://blog.zysset.me/lightweight-docker-go-images/

Kubernetes實戰培訓

Kubernetes應用實戰培訓將於2018年10月12日在深圳開課,3天時間帶你系統學習Kubernetes本次培訓包括:容器基礎、Docker基礎、Docker進階、Kubernetes架構及部署、Kubernetes常用物件、Kubernetes網絡、儲存、服務發現、Kubernetes的調度和服務質量保證、監控和日誌、Helm、專案實踐等,點擊下方圖片查看詳情。

赞(0)

分享創造快樂