原理:Docker如何使耗時運(yùn)行的構(gòu)建腳本更容易
我想我已經(jīng)找到了一個相當(dāng)引人注目的docker使用案例。但在此之前,如果你還是認(rèn)為這又是一個人云亦云docker美德的博客帖子的話,我想明確指出,這個帖子確實(shí)是關(guān)于把文件系統(tǒng)作為持久性數(shù)據(jù)結(jié)構(gòu)的贊美帖。
因此,這篇文章的見解同樣適用于其他的 copy-on-write文件系統(tǒng),如BTRFS和ZFS。
問題
讓我們從這個我試圖解決的問題開始。我開發(fā)了包括了眾多的步驟的長時運(yùn)行的構(gòu)建腳本。
花費(fèi)1-2個小時運(yùn)行。
它從互聯(lián)網(wǎng)下載了很多相當(dāng)大的文件。(超過300M)。
后期嚴(yán)重依賴早期構(gòu)建的庫。
但最顯著的特點(diǎn)是,它需要花很長的時間來運(yùn)行。
文件系統(tǒng)是固有狀態(tài)
我們通常是以一種狀態(tài)的方式與文件系統(tǒng)進(jìn)行交互的。我們可以添加,刪除或移動文件。我們可以改變文件的權(quán)限或者它的訪問時間。
隔離下的大部分操作都可以撤銷。例如你可以移動文件到其其他的地方后,將文件恢復(fù)到原來的位置。通常我們不會做的是采取一個快照,并恢復(fù)到那個狀態(tài)。這篇文章建議更多地利用這一特性對開發(fā)長時運(yùn)行腳本有巨大好處。
使用聯(lián)合文件系統(tǒng)的快照
Docker采用的是所謂的聯(lián)合文件系統(tǒng)叫做AUFS。聯(lián)合文件系統(tǒng)實(shí)現(xiàn)了被稱為聯(lián)合掛載的文件系統(tǒng)。顧名思義,這意味著文件和獨(dú)立的文件系統(tǒng)的目錄被分層于互相形成的單個連貫文件系統(tǒng)之上。
這是以分層方式完成的。如果一個文件出現(xiàn)在兩個文件系統(tǒng),后來添加的文件將會呈現(xiàn) (該文件其他版本是存在于層級中的,不改變,只是看不到的)。
Docker稱呼在聯(lián)合掛載文件系統(tǒng)里的每個文件系統(tǒng)為layers(層)。使用這種技術(shù)的結(jié)果是,它的副作用可以實(shí)現(xiàn)快照。每個快照對于所有層是一個簡單的聯(lián)合掛載文件系統(tǒng)掛載到某個層次結(jié)構(gòu)中。
生成腳本的快照
快 照使開發(fā)一個長時運(yùn)行的構(gòu)建腳本成為夢想??偟南敕ㄊ牵纸獯竽_本為更小的腳本(我喜歡稱之為scriptlets)并且單獨(dú)地運(yùn)行每一個,每一個運(yùn)行后 快照其文件系統(tǒng)。 (Docker會自動執(zhí)行此操作。)如果你發(fā)現(xiàn)一個scriptlet運(yùn)行失敗,簡單的可以回到最后的快照(仍處于原始狀態(tài)!),然后再試一次。
一旦你完成你的構(gòu)建腳本,你可以保證,腳本正常工作,現(xiàn)在可以分配給其他主機(jī)。
相對于如果你沒有使用快照會發(fā)生什么。除了在我們中間那些有和尚般的耐心的人,當(dāng)它在1個半小時后失敗了,沒有人會去從頭開始運(yùn)行他們的構(gòu)建腳本。當(dāng)然,我們會盡最大努力把系統(tǒng)恢復(fù)到失敗前的狀態(tài)。例如我們可以刪除一個目錄或運(yùn)行 make clean。
但是,我們可能沒有真正地理解我們正在構(gòu)建的組件。它可能復(fù)雜的Makefile:把文件放到文件系統(tǒng)中我們不知道的地方。唯一真正確定的途徑是恢復(fù)到快照。
使用快照構(gòu)建腳本的docker
在 本節(jié)中,我將介紹我是如何使用Docker實(shí)現(xiàn)GHC7.8.3 ARM交叉編譯器的構(gòu)建腳本。對于這個任務(wù)Docker相當(dāng)不錯的,但并不是完美的。我做了一些事情,可能看起來浪費(fèi)的或不雅的,但都是必要的,以保持開 發(fā)腳本的總時間到最低限度。構(gòu)建腳本可以在這里找到。
用Dockerfile構(gòu)建
Docker 讀取一個名為Dockerfile來構(gòu)建鏡像。Dockerfile包含一些命令詞匯來具體指定哪些行動應(yīng)該被執(zhí)行。一個完整的參考可以在這里找到。其中 在我的腳本主要用了WORKDIR,ADD和RUN。ADD命令非常有用因?yàn)樗梢宰屇阍谶\(yùn)行之前將外部文件添加到當(dāng)前Docker鏡像中然后轉(zhuǎn)換成鏡像 的文件系統(tǒng)。你可以在這里看到很多scriptlets構(gòu)成的構(gòu)建腳本。
#p#
設(shè)計
1.在RUN之前ADD scriptlets
如 果你太早ADD所有的scriptlets在Dockerfile,您可能會遇到以下問題:你的腳本失敗,你回去修改scriptlet并再次運(yùn)行 docker build .。但是你發(fā)現(xiàn),Docker開始在首次加入scriptlets的地方構(gòu)建!這會浪費(fèi)了大量的時間和違背了使用快照的目的。
出 現(xiàn)這種情況的原因是因?yàn)镈ocker如何追蹤它的中間鏡像(快照)。當(dāng)Docker通過Dockerfile構(gòu)建鏡像時它會與中間鏡像比較當(dāng)前命令是否一 致。然而,在ADD命令的情況下被裝進(jìn)鏡像的文件里的內(nèi)容也會被檢查。這是有道理的。如果文件已改變就現(xiàn)有的中間鏡像那么Docker將別無選擇,只能從 從這點(diǎn)開始建立一個新的鏡像。只是沒有辦法可以知道這些變化不會影響到構(gòu)建。這是必須要保守的即使他們沒有。
此外,使用RUN命令 要注意,每次運(yùn)行時它將導(dǎo)致文件系統(tǒng)有不同的更改。在這種情況下,Docker會發(fā)現(xiàn)中間鏡像并使用它,但是這將是錯誤的。RUN命令每次運(yùn)行時必須造成 文件系統(tǒng)相同的改變。舉個例子,我確保在我的scriptlets我總是下載了一個已知版本的文件與一個特定MD5校驗(yàn)。
對Docker 構(gòu)建緩存更詳細(xì)的解釋可以在這里找到。
2.不要使用ENV命令來設(shè)置環(huán)境變量。使用scriptlet。
它似乎看起來很有誘惑力:使用ENV命令來設(shè)置所有構(gòu)建腳本需要的環(huán)境變量。但是,它不支持變量替換的方式,例如 ENV BASE=$HOME/base 將設(shè)置BASE的值為$HOME/base著很可能不是你想要的。
相反,我用ADD命令添加一個名為set-env.sh文件。此文件被包含在每個后續(xù)的scriptlet中:
- THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source $THIS_DIR/set-env-1.sh
如果你沒有在第一時間獲取set-env.sh會怎么樣呢?自從它很早就被加入Dockerfile并不意味著修改它將會使隨后的快照無效?
是的,這將導(dǎo)致一些不雅。在開發(fā)腳本時,我發(fā)現(xiàn),我已經(jīng)錯過了在set-env.sh添加一個有用的環(huán)境變量。解決方案是創(chuàng)建一個新的文件set-env-1.sh包含:
- THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source $THIS_DIR/set-env.sh
- if ! [ -e "$CONFIG_SUB_SRC/config.sub" ] ; then
- CONFIG_SUB_SRC=${CONFIG_SUB_SRC:-$NCURSES_SRC}
- fi
然后,在所有后續(xù)的scriptlets文件中包含了此文件?,F(xiàn)在,我已經(jīng)完成了構(gòu)建腳本,我可以回去解決這個問題了,但是,在某種意義上,它會破壞最初的目標(biāo)。我將不得不從頭開始運(yùn)行構(gòu)建腳本看看這種變化是否能成功。
缺點(diǎn)
一個主要缺點(diǎn)是這種方法是,所構(gòu)建的鏡像尺寸是大于它實(shí)際需求的尺寸。在我的情況下尤其如此,因?yàn)槲以谧詈髣h除了大量文件的。然而,這些文件都仍然存在于聯(lián)合掛載文件系統(tǒng)的底層文件系統(tǒng)內(nèi),所以整個鏡像是大于它實(shí)際需要的大小至少多余的是刪除文件的大小。
然而,有一個變通。我沒有公布此鏡像到Docker Hub Registry。相反,我:
- 使用docker export導(dǎo)出內(nèi)容到tar文件。
- 創(chuàng)建一個新的Dockerfile簡單地添加了這個tar文件的內(nèi)容。
來產(chǎn)生尺寸盡可能小的鏡像。
結(jié)論
這種方法的優(yōu)點(diǎn)是雙重的:
- 它使開發(fā)時間降至最低。不再做那些已經(jīng)構(gòu)建成功的子組件。你可以專注于那些失敗的組件。
- 這是偉大對于維護(hù)構(gòu)建腳本。有一個機(jī)會 古怪的RUN命令在一段時間(即使它不應(yīng)該)會改變其行為。構(gòu)建可能會失敗,但至少你不必再回到開頭,一旦你解決了Dockerfile
此外,正如我前面提到的Docker不僅使寫這些構(gòu)建腳本更加容易。有了合適的工具同樣可以在任何提供快照的文件系統(tǒng)實(shí)現(xiàn)。
構(gòu)建快樂!