如何使用Docker構建執行時間較長的指令碼

Lambdalog發表於2014-12-30

如何使用Docker構建執行時間較長的指令碼如何使用Docker構建執行時間較長的指令碼

問題

讓我們從這個我試圖解決的問題開始。我開發了一個會執行很長時間的構建中包含了很多的步驟。 這個指令碼會執行1-2個小時。 它會從網路下載比較大的檔案(超過300M)。 後面的構建步驟依賴前期構建的庫。 但最最煩人的是,執行這個指令碼真的需要花很長的時間。

檔案系統是固有狀態

我們一般是透過一種有狀態的方式與檔案系統進行互動的。我們可以新增、刪除或移動檔案。我們可以修改檔案的 許可權或者它的訪問時間。大部分獨立的操作都可以撤銷,例如將檔案移動到其它地方後,你可以將檔案恢復到原來的位置。但我們不會透過快照的方式來將它恢復到 原始狀態。這篇文章我將會介紹如何在耗時較長的指令碼中充分利用快照這一特性。

使用聯合檔案系統的快照

Docker使用的是聯合檔案系統叫做AUFS(譯者注:簡單來說就是支援將不同目錄掛載到同一個虛擬檔案系統下的檔案系統)。聯合檔案系統實現了Union mount。顧名思義,也就是說不同的檔案系統的檔案和目錄可以分層疊加在單個連貫檔案系統之上。這是透過分層的方式完成的。如果一個檔案出現在兩個檔案系統,那最高層級的檔案才會顯示(該檔案其它版本也是存在於層級中的,不會改變,只是看不到的)。

在Docker中,每一個在Union mount轉哦給你的檔案系統都被稱為layers(層)。使用這種技術可以輕鬆實現快照,每個快照都是所有層的一個Union mount。

生成指令碼的快照

使用快照可以幫助構建一個長時執行的指令碼。總的想法是,將一個大的指令碼分解為許多小的指令碼(我喜歡稱之為 scriptlets),並單獨執行這些小的指令碼,指令碼執行後為其檔案系統打一個快照 (Docker會自動執行此操作)。如果你發現一個scriptlet執行失敗,你可以快速回退到上次的快照,然後再試一次。一旦你完成指令碼的構建,並且 可以保證指令碼能正常工作,那你就可以將它分配給其它主機。

回過頭來再對比下,如果你沒有使用快照功能了?當你辛辛苦苦等待了一個半小時後,指令碼卻構建失敗了,我想除了少部分有耐心的人外,很多人是不想再來一次了,當然,你也會盡最大努力把系統恢復到失敗前的狀態,比如可以刪除一個目錄或執行make clean。

但是,我們可能沒有真正地理解我們正在構建的元件。它可能有複雜的Makefile,它會把把檔案放到檔案系統中我們不知道的地方,真正確定的途徑是恢復到快照。

使用快照構建指令碼的Docker

在本節中,我將介紹我是如何使用Docker實現GHC7.8.3 ARM交叉編譯器的構建指令碼。Docker非常適合做這件事,但並非完美。我做了很多看起來沒用的或者不雅的事情,但都是必要的,這都是為了保證將開發指令碼的總時間降到最低限度。構建指令碼可以在這裡找到。

用Dockerfile構建

Docker透過讀取Dockerfile來構建映象。Dockerfile會透過一些工具來具體指定應該執行哪些動作。具體使用說明可以參考這篇文章。在我的指令碼中主要用到WORKDIR、ADD和RUN。ADD工具非常有用因為它可以讓你在執行之前將外部檔案新增到當前Docker映象中然後轉換成映象的檔案系統。你可以在這裡看到很多scriptlets構成的構建指令碼。

設計
1. 在RUN之前ADD scriptlets

如果你很早就將所有的scriptletsADD在Dockerfile,您可能會遇到以下問題:如果你的指令碼構建失敗,你回去修改scriptlet並再次執行docker build。但是你發現,Docker開始在加入scriptlets的地方構建!這樣做會浪費了大量的時間並且違背了使用快照的目的。

出現這種情況的原因是由於Docker處理它的中間映象(快照)的方式。當Docker透過Dockerfile構建映象時,它會與中間映象比較當前命令是否一致。然而,在ADD命令的情況下被裝進映象的檔案裡的內容也會被檢查。如果相對於現有的中間映象,檔案已經改變,那麼Docker也別無選擇,只能從這點開始建立一個新的映象。因為Docker不知道這些變化會不會影響到構建。

此外,使用RUN命令要注意,每次執行時它都會導致檔案系統有不同的更改。在這種情況下,Docker會發現中間映象並使用它,但是這將是錯誤的。RUN命令每次執行時會造成檔案系統相同的改變。舉個例子,我確保在我的scriptlets我總是下載了一個已知版本的檔案與一個特定MD5校驗。

對Docker 構建快取更詳細的解釋可以在這裡"找到。

2.不要使用ENV命令來設定環境變數,請使用scriptlet。

它似乎看起來很有誘惑力:使用ENV命令來設定所有構建指令碼需要的環境變數。但是,它不支援變數替換的方式,例如 ENV BASE=$HOME/base 將設定BASE的值為$HOME/base著很可能不是你想要的。

相反,我用ADD命令新增一個名為set-env.sh檔案。此檔案會包含在後續的scriptlet中:

1.THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
2.source $THIS_DIR/set-env-1.sh

如果你沒有在第一時間獲取set-env.sh會怎麼樣呢?它很早就被加入Dockerfile並不意味著修改它將會使隨後的快照無效?

是的,這會有問題。在開發指令碼時,我發現,我已經錯過了在set-env.sh新增一個有用的環境變數。解決方案是建立一個新的檔案set-env-1.sh包含:

1.THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
2.source $THIS_DIR/set-env.sh
3.if ! [ -e "$CONFIG_SUB_SRC/config.sub" ] ; then
4.CONFIG_SUB_SRC=${CONFIG_SUB_SRC:-$NCURSES_SRC}
5.fi

然後,在所有後續的scriptlets檔案中包含了此檔案。現在,我已經完成了構建指令碼,我可以回去解決這個問題了,但是,在某種意義上,它會破壞最初的目標。我將不得不從頭開始執行構建指令碼看看這種變化是否能成功。

缺點

一個主要缺點是這種方法是,所構建的映象尺寸是大於它實際需求的尺寸。在我的情況下尤其如此,因為我在最後刪除了大量檔案的。然而,這些檔案都仍然存在於聯合掛載檔案系統的底層檔案系統內,所以整個映象是大於它實際需要的大小至少多餘的是刪除檔案的大小。

然而,有一個變通。我沒有公佈此映象到Docker Hub Registry。相反,我:
•使用docker export匯出內容為tar檔案。
•建立一個新的Dockerfile簡單地新增了這個tar檔案的內容。
產生尺寸儘可能小的映象。

結論

這種方法的優點是雙重的:
•它使開發時間降至最低,不再做那些已經構建成功的子元件。你可以專注於那些失敗的元件。
•這非常便於維護構建指令碼。構建可能會失敗,但只要你搞定Dockerfiel,至少你不必再從頭開始。

此外,正如我前面提到的Docker不僅使寫這些構建指令碼更加容易,有了合適的工具同樣可以在任何提供快照的檔案系統實現。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2951893/,如需轉載,請註明出處,否則將追究法律責任。

相關文章