如何爲你的Go應用建立輕量級Docker鏡像?

介紹

多什麼?

簡單來說,多階段。

多階段容許在建立Dockerfile時使用多個from,它很是有用,由於它使咱們可以使用全部必需的工具構建應用程序。舉個例子,首先咱們使用Golang的基礎鏡像,而後在第二階段的時候使用構建好的鏡像的二進制文件,最後階段構建出來的鏡像用於發佈到咱們本身的倉庫或者是用於上線發佈。

在上述的案例中,咱們總共有三個階段:html

  • build編譯階段
  • certs(可選,無關緊要)證書認證階段
  • prod生產階段


在build階段主要是編譯咱們的應用程序,證書認證階段將會安裝咱們所須要的CA證書,最後的生產發佈階段會將咱們構建好的鏡像推到鏡像倉庫中。並且發佈階段將會使用build階段編譯完畢的二進制文件和certs階段安裝的證書。linux

1.png


項目發佈的多個build階段git

示例工程

對於這個方法,咱們將使用一個很是簡單的項目。它只是一個運行在8080端口的HTTP服務,而且返回結果爲傳遞過去的URL的內容結果。github

舉例

GET http://localhost:8080?url=https://google.com 返回結果爲goole頁面內容展現。

你也能夠在這裏找到代碼倉庫。

在master分支上只包含了應用程序,final分支上還包含本篇教程中使用的Dockerfile文件

若是你想跟着本教程來作,只須要拉下master上的代碼而且跟着我來建立Dockerfile。golang

步驟1 - 編譯階段

第一階段主要是使用Golang基礎鏡像來將咱們的應用程序打包爲二進制文件。這個基礎鏡像包含了將咱們的應用程序編譯成可執行二進制文件的全部工具。

下面是咱們最原始的Dockerfile:spring

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

docker build -t scboffspring/blog-multistage-go .
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go


咱們可使用curl命令來請求,而且它會返回http://google.com頁面內容。

在終端運行curl localhost:8080json

1 <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>Google</title>
7 ....


讓咱們使用docker images,來看看鏡像的大小:服務器

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


荒唐,太荒唐了,一個這麼小的應用竟然佔了磁盤818M內存空間。

推送到鏡像倉庫後,鏡像大小被壓縮到309M。app

2.png


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便可,相比於以前節省了大量的時間和帶寬呢。

3.png


使用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中的依賴(並非測試環境依賴)。它只將編譯和縮小的代碼複製到鏡像中,而後將該鏡像推送並部署到生產中。

相關文章
相關標籤/搜索