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

如何為你的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)

分享創造快樂