導讀 | 我想我已經找到了一個很是不錯的Docker使用案例。你是否是會以爲這是一篇寫Docker有多好多好的文章,開始以前我想和你確認,這篇文章會介紹如何把文件系統做爲持久性的數據結構。 所以,這篇文章的看法一樣適用於其餘的 copy-on-write文件系統,如BTRFS和ZFS。 |
問題html
讓咱們從這個我試圖解決的問題開始。我開發了一個會運行很長時間的構建腳本,這個腳本中包含了不少的步驟。 這個腳本會運行1-2個小時。 它會從網絡下載比較大的文件(超過300M)。 後面的構建步驟依賴前期構建的庫。 但最最煩人的是,運行這個腳本真的須要花很長的時間。linux
文件系統是固有狀態docker
咱們通常是經過一種有狀態的方式與文件系統進行交互的。咱們能夠添加、刪除或移動文件。咱們能夠修改文件的 權限或者它的訪問時間。大部分獨立的操做均可以撤銷,例如將文件移動到其它地方後,你能夠將文件恢復到原來的位置。但咱們不會經過快照的方式來將它恢復到 原始狀態。這篇文章我將會介紹如何在耗時較長的腳本中充分利用快照這一特性。緩存
使用聯合文件系統的快照網絡
Docker使用的是聯合文件系統叫作AUFS(譯者注:簡單來講就是支持將不一樣目錄掛載到同一個虛擬文件系統下的文件系統)。聯合文件系統實現了Union mount。顧名思義,也就是說不一樣的文件系統的文件和目錄能夠分層疊加在單個連貫文件系統之上。這是經過分層的方式完成的。若是一個文件出如今兩個文件系統,那最高層級的文件纔會顯示(該文件其它版本也是存在於層級中的,不會改變,只是看不到的)。數據結構
在Docker中,每個在Union mount轉哦給你的文件系統都被稱爲layers(層)。使用這種技術能夠輕鬆實現快照,每一個快照都是全部層的一個Union mount。工具
生成腳本的快照post
使用快照能夠幫助構建一個長時運行的腳本。總的想法是,將一個大的腳本分解爲許多小的腳本(我喜歡稱之爲 scriptlets),並單獨運行這些小的腳本,腳本運行後爲其文件系統打一個快照 (Docker會自動執行此操做)。若是你發現一個scriptlet運行失敗,你能夠快速回退到上次的快照,而後再試一次。一旦你完成腳本的構建,而且 能夠保證腳本能正常工做,那你就能夠將它分配給其它主機。ui
回過頭來再對比下,若是你沒有使用快照功能了?當你辛辛苦苦等待了一個半小時後,腳本卻構建失敗了,我想除了少部分有耐心的人外,不少人是不想再來一次了,固然,你也會盡最大努力把系統恢復到失敗前的狀態,好比能夠刪除一個目錄或運行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 構建緩存更詳細的解釋能夠在這裏"https://docs.docker.com/articles/dockerfile_best-practices/#build-cache"找到。
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不只使寫這些構建腳本更加容易,有了合適的工具一樣能夠在任何提供快照的文件系統實現。
via: http://lambdalog.seanseefried.com
做者:Lambdalog 譯者:田浩本文由 Linux中國 榮譽推出