Docker如何使耗時運行的構建腳本更容易
我想我已經找到了一個相當引人注目的docker使用案例。但在此之前,如果你還是認為這又是一個人云亦云docker美德的博客帖子的話,我想明確指出,這個帖子確實是關于把文件系統作為持久性數據結構的贊美帖。
因此,這篇文章的見解同樣適用于其他的 copy-on-write文件系統,如BTRFS和ZFS。
問題
讓我們從這個我試圖解決的問題開始。我開發了包括了眾多的步驟的長時運行的構建腳本。
花費1-2個小時運行。
它從互聯網下載了很多相當大的文件。(超過300M)。
后期嚴重依賴早期構建的庫。
但最顯著的特點是,它需要花很長的時間來運行。
文件系統是固有狀態
我們通常是以一種狀態的方式與文件系統進行交互的。我們可以添加,刪除或移動文件。我們可以改變文件的權限或者它的訪問時間。
隔離下的大部分操作都可以撤銷。例如你可以移動文件到其其他的地方后,將文件恢復到原來的位置。通常我們不會做的是采取一個快照,并恢復到那個狀態。這篇文章建議更多地利用這一特性對開發長時運行腳本有巨大好處。
使用聯合文件系統的快照
Docker采用的是所謂的聯合文件系統叫做AUFS。聯合文件系統實現了被稱為聯合掛載的文件系統。顧名思義,這意味著文件和獨立的文件系統的目錄被分層于互相形成的單個連貫文件系統之上。
這是以分層方式完成的。如果一個文件出現在兩個文件系統,后來添加的文件將會呈現 (該文件其他版本是存在于層級中的,不改變,只是看不到的)。
Docker稱呼在聯合掛載文件系統里的每個文件系統為layers(層)。使用這種技術的結果是,它的副作用可以實現快照。每個快照對于所有層是一個簡單的聯合掛載文件系統掛載到某個層次結構中。
生成腳本的快照
快 照使開發一個長時運行的構建腳本成為夢想。總的想法是,分解大腳本為更小的腳本(我喜歡稱之為scriptlets)并且單獨地運行每一個,每一個運行后 快照其文件系統。 (Docker會自動執行此操作。)如果你發現一個scriptlet運行失敗,簡單的可以回到最后的快照(仍處于原始狀態!),然后再試一次。
一旦你完成你的構建腳本,你可以保證,腳本正常工作,現在可以分配給其他主機。
相對于如果你沒有使用快照會發生什么。除了在我們中間那些有和尚般的耐心的人,當它在1個半小時后失敗了,沒有人會去從頭開始運行他們的構建腳本。當然,我們會盡最大努力把系統恢復到失敗前的狀態。例如我們可以刪除一個目錄或運行 make clean。
但是,我們可能沒有真正地理解我們正在構建的組件。它可能復雜的Makefile:把文件放到文件系統中我們不知道的地方。唯一真正確定的途徑是恢復到快照。
使用快照構建腳本的docker
在 本節中,我將介紹我是如何使用Docker實現GHC7.8.3 ARM交叉編譯器的構建腳本。對于這個任務Docker相當不錯的,但并不是完美的。我做了一些事情,可能看起來浪費的或不雅的,但都是必要的,以保持開 發腳本的總時間到最低限度。構建腳本可以在這里找到。
用Dockerfile構建
Docker 讀取一個名為Dockerfile來構建鏡像。Dockerfile包含一些命令詞匯來具體指定哪些行動應該被執行。一個完整的參考可以在這里找到。其中 在我的腳本主要用了WORKDIR,ADD和RUN。ADD命令非常有用因為它可以讓你在運行之前將外部文件添加到當前Docker鏡像中然后轉換成鏡像 的文件系統。你可以在這里看到很多scriptlets構成的構建腳本。
設計
1.在RUN之前ADD scriptlets
如 果你太早ADD所有的scriptlets在Dockerfile,您可能會遇到以下問題:你的腳本失敗,你回去修改scriptlet并再次運行 docker build .。但是你發現,Docker開始在首次加入scriptlets的地方構建!這會浪費了大量的時間和違背了使用快照的目的。
出 現這種情況的原因是因為Docker如何追蹤它的中間鏡像(快照)。當Docker通過Dockerfile構建鏡像時它會與中間鏡像比較當前命令是否一 致。然而,在ADD命令的情況下被裝進鏡像的文件里的內容也會被檢查。這是有道理的。如果文件已改變就現有的中間鏡像那么Docker將別無選擇,只能從 從這點開始建立一個新的鏡像。只是沒有辦法可以知道這些變化不會影響到構建。這是必須要保守的即使他們沒有。
此外,使用RUN命令 要注意,每次運行時它將導致文件系統有不同的更改。在這種情況下,Docker會發現中間鏡像并使用它,但是這將是錯誤的。RUN命令每次運行時必須造成 文件系統相同的改變。舉個例子,我確保在我的scriptlets我總是下載了一個已知版本的文件與一個特定MD5校驗。
對Docker 構建緩存更詳細的解釋可以在這里找到。
2.不要使用ENV命令來設置環境變量。使用scriptlet。
它似乎看起來很有誘惑力:使用ENV命令來設置所有構建腳本需要的環境變量。但是,它不支持變量替換的方式,例如 ENV BASE=$HOME/base 將設置BASE的值為$HOME/base著很可能不是你想要的。
相反,我用ADD命令添加一個名為set-env.sh文件。此文件被包含在每個后續的scriptlet中:
如果你沒有在第一時間獲取set-env.sh會怎么樣呢?自從它很早就被加入Dockerfile并不意味著修改它將會使隨后的快照無效?
是的,這將導致一些不雅。在開發腳本時,我發現,我已經錯過了在set-env.sh添加一個有用的環境變量。解決方案是創建一個新的文件set-env-1.sh包含:
缺點
一個主要缺點是這種方法是,所構建的鏡像尺寸是大于它實際需求的尺寸。在我的情況下尤其如此,因為我在最后刪除了大量文件的。然而,這些文件都仍然存在于聯合掛載文件系統的底層文件系統內,所以整個鏡像是大于它實際需要的大小至少多余的是刪除文件的大小。
然而,有一個變通。我沒有公布此鏡像到Docker Hub Registry。相反,我:
·使用docker export導出內容到tar文件。
·創建一個新的Dockerfile簡單地添加了這個tar文件的內容。
來產生尺寸盡可能小的鏡像。
結論
這種方法的優點是雙重的:
·它使開發時間降至最低。不再做那些已經構建成功的子組件。你可以專注于那些失敗的組件。
·這是偉大對于維護構建腳本。有一個機會 古怪的RUN命令在一段時間(即使它不應該)會改變其行為。構建可能會失敗,但至少你不必再回到開頭,一旦你解決了Dockerfile
此外,正如我前面提到的Docker不僅使寫這些構建腳本更加容易。有了合適的工具同樣可以在任何提供快照的文件系統實現。
構建快樂!
文章出自:http://dockerone.com/article/100
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!