Docker 的口號是 Build, Ship, and Run Any App, Anywhere.但是我們在應用過程中會遇到一個問題,我們在 build 的時候,把原始碼也 build 進去了。然後就繼續把原始碼 Ship 出去嗎?這可不行。所有的編譯型語言都面臨這個困擾。 即使是指令碼型語言,build 的時候也會使用很多上線時用不到的構建工具,而我們希望減小生產映象的體積,這樣我們的小鯨魚才能多拉一點集裝箱嘛。
傳統做法
我們最終的目的是要將編譯好的可執行檔案複製到 alpine
這樣的迷你映象裡,那麼該怎麼弄到編譯好的檔案呢?基於 Docker 的思想,我們肯定需要在一個標準容器中編譯,這樣這個過程才是標準化的,再說,你在 Ubuntu 編譯出一個二進位制檔案在 alpine 也執行不了。
於是我們先需要準備一個編譯用的自定義映象。一般是用相應語言的 alpine 基礎映象,把編譯專案額外需要的各種工具打包進去,比如 golang 目前沒有官方的包管理,你就需要把你用的包管理工具裝進去。
然後我們需要在執行 container 時把主機的一個目錄通過 -v 掛載到 container上,讓它把編譯的結果輸出到這個掛載的目錄,這樣我們就在主機上拿到這個檔案了。
最後,我們用一個最小的 alpine
映象,把二進位制檔案複製進去。可能你還需要設定一下時區之類的。
持續整合
上面的流程,在用持續整合工具時又變成了一個問題。你會發現每一家 CI 提供商都不太一樣。你未必有許可權控制 CI 時的宿主機。
比如 Docker Cloud
,你需要定義 pre-build 的 hook 去完成這個工作,在 SEMAPHORE
,你發現你有了一臺宿主機,這下和我們在本地的做法可以一樣了。 在更多的提供商,你會發現他們只是能根據 git 倉庫和 Dockerfile 構建映象,你用他們的系統甚至沒辦法做出一個最小映象……中國的 DaoCloud
其實挺先進的,很早就推出了安全映象的概念,讓你的構建通過兩步完成。但是,那個配置的內容太多讓不太懂的人看了直接暈掉。
官方方案
在2017年5月3日即將發行的 Docker 17.05.0-ce
中,Docker 官方提供了簡便的多階段構建(multi-stage build) 方案。我用例子為大家介紹下:
FROM muninn/glide:alpine AS build-env
ADD . /go/src/app
WORKDIR /go/src/app
RUN glide install
RUN go build -v -o /go/src/app/app-server
FROM alpine
RUN apk add -U tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build-env /go/src/app/app-server /usr/local/bin/app-server
EXPOSE 80
CMD ["app-server"]複製程式碼
首先,第一個 FROM
後邊多了個 AS
關鍵字,可以給這個階段起個名字。我舉例子這個映象是官方 golang:alpine 加上構建工具 glide ,我們照舊安裝依賴, build 出一個二進位制程式。
然後,第二部分用了官方的 alpine
映象,改變時區到中國,新特性體現在 COPY
關鍵字,它現在可以接受 --from=
這樣的引數,從上個我們起名字的階段複製檔案過來。
就這麼簡單,現在你只需要一個 Dockerfile 就什麼都搞定了。
多專案構建
於是現在你可以把好幾個專案的二進位制檔案構建在一個迷你映象中釋出了,繼續舉個栗子:
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 ...複製程式碼
這個就是把兩個專案編譯出來的檔案最終合併到了一個映象裡。
好了,祝賀那些不支援多段構建的 CI 服務,Docker 幫你們追平了競爭對手。
我有機會會寫一個支援 Docker 的 CI 的主觀評論,也歡迎大家吐槽各路 CI 給我提供素材。