隨着全面雲時代到來,不少公司都走上了容器化道路,老劉所在的公司也不例外。做爲一家初創型的互聯網公司,容器化的確帶來了不少便捷,也下降了公司成本,不過老劉卻有一個苦惱,之前天天和他一塊兒下班的小王自從公司上雲之後天天都比他早下班一個小時,你們手頭上的活都差很少,講道理不該該呀,通過多番試探、跟蹤、調查,終於讓老劉發現了祕密的所在。node
做爲一個開發,天天總少不了要出N個測試版本進行調試,容器化之後每次出版本都須要打成鏡像,老劉發現每次他作一個鏡像都要20分鐘,而小王只要10分鐘,對比來對比去只有這個東西不同!linux
Storage-Dirver究竟是何方神聖?爲何可以致使構建時間上的差別?如今讓咱們來一窺究竟。git
在回答這個問題以前咱們須要先回答三個問題——什麼是鏡像?什麼是鏡像構建?什麼是storage-driver?github
什麼是鏡像?docker
說到鏡像就繞不開容器,咱們先看一張來自官方對鏡像和容器解釋的圖片:數據庫
看完之後是否是更疑惑了,咱們能夠這樣簡單粗暴的去理解,鏡像就是一堆只讀層的堆疊。那隻讀層裏究竟是什麼呢,另一個簡單粗暴的解釋:裏邊就是放了一堆被改動的文件。這個解釋在不一樣的storage-driver下不必定準確可是咱們能夠先這樣簡單去理解。ubuntu
那不對呀,執行容器的時候明明是能夠去修改刪除容器裏的文件的,都是隻讀的話怎麼去修改呢?實際上咱們運行容器的時候是在那一堆只讀層的頂上再增長了一個讀寫層,全部的操做都是在這個讀寫層裏進行的,當須要修改一個文件的時候咱們會將須要修改的文件從底層拷貝到讀寫層再進行修改。那若是是刪除呢,咱們不是沒有辦法刪除底層的文件麼?沒錯,確實沒有辦法刪除,但只須要在上層把這個文件隱藏起來,就能夠達到刪除的效果。按照官方說法,這就是Docker的寫時複製策略。網絡
爲了加深你們對鏡像層的理解咱們來舉個栗子,用下面的Dockerfile構建一個etcd鏡像:併發
構建完成之後生成了以下的層文件:app
每次進入容器的時候都感受彷彿進入了一臺虛機,裏面包含linux的各個系統目錄。那是否是有一層目錄裏包含了全部的linux系統目錄呢?
bingo答對!在最底層的層目錄的確包含了linux的全部的系統目錄文件。
上述Dockerfile中有這樣一步操做
ADD . /go/src/github.com/coreos/etcd
將外面目錄的文件拷到了鏡像中,那這一層鏡像裏究竟保存了什麼呢?
打開發現裏面就只有
/go/src/github.com/coreos/etcd這個目錄,目錄下存放了拷貝進來的文件。
到這裏是否是有種管中窺豹的感受,接下來咱們再來了解什麼是鏡像構建,這樣基本上可以窺其全貌了。
什麼是鏡像構建?
經過第一節的內容咱們知道了鏡像是由一堆層目錄組成的,每一個層目錄裏放着這一層修改的文件,鏡像構建簡單的說就是製做和生成鏡像層的過程,那這一過程是如何實現的呢?如下圖流程爲例:
Docker Daemon首先利用基礎鏡像ubuntu:14.04建立了一個容器環境,經過第一節的內容咱們知道容器的最上層是一個讀寫層,在這一層咱們是能夠寫入修改的,Docker Daemon首先執行了RUN apt-update get命令,執行完成之後,經過Docker的commit操做將這個讀寫層的內容保存成一個只讀的鏡像層文件。接下來再在這一層的基礎上繼續執行 ADD run.sh命令,執行完成後繼續commit成一個鏡像層文件,如此反覆直到將全部的Dockerfile都命令都被提交後,鏡像也就作好了。
這裏咱們就能解釋爲何etcd的某個層目錄裏只有一個go目錄了,由於構建的過程是逐層提交的,每一層裏只會保存這一層操做所涉及改動的文件。
這樣看來鏡像構建就是一個反覆按照Dockerfile啓動容器執行命令並保存成只讀文件的過程,那爲何速度會不同呢?接下來就得說到storage-driver了。
什麼是storage-driver?
再來回顧一下這張圖:
以前咱們已經知道了,鏡像是由一個個的層目錄疊加起來的,容器運行時只是在上面再增長一個讀寫層,同時還有寫時複製策略保證在最頂層可以修改底層的文件內容,那這些原理是怎麼實現的呢?就是靠storage-driver!
簡單介紹三種經常使用的storage-driver:
1. AUFS
AUFS經過聯合掛載的方式將多個層文件堆疊起來,造成一個統一的總體提供統一視圖,當在讀寫層進行讀寫的時,先在本層查找文件是否存在,若是沒有則一層一層的往下找。aufs的操做都是基於文件的,須要修改一個文件時不管大小都會將整個文件從只讀層拷貝到讀寫層,所以若是須要修改的文件過大,會致使容器執行速度變慢,docker官方給出的建議是經過掛載的方式將大文件掛載進來而不是放在鏡像層中。
2. OverlayFS
OverlayFS能夠認爲是AUFS的升級版本,容器運行時鏡像層的文件是經過硬連接的方式組成一個下層目錄,而容器層則是工做在上層目錄,上層目錄是可讀寫的,下層目錄是隻讀的,因爲大量的採用了硬連接的方式,致使OverlayFS會可能會出現inode耗盡的狀況,後續Overlay2對這一問題進行了優化,且性能上獲得了很大的提高,不過Overlay2也有和AUFS有一樣的弊端——對大文件的操做速度比較慢。
3. DeviceMapper
DeviceMapper和前兩種Storage-driver在實現上存在很大的差別。首先DeviceMapper的每一層保存的是上一層的快照,其次DeviceMapper對數據的操做再也不是基於文件的而是基於數據塊的。
下圖是devicemapper在容器層讀取文件的過程:
首先在容器層的快照中找到該文件指向下層文件的指針。
再從下層0xf33位置指針指向的數據塊中讀取的數據到容器的存儲區
最後將數據返回app。
在寫入數據時還須要根據數據的大小先申請1~N個64K的容器快照,用於保存拷貝的塊數據。
DeviceMapper的塊操做看上去很美,實際上存在不少問題,好比頻繁操做較小文件時須要不停地從資源池中分配數據庫並映射到容器中,這樣效率會變得很低,且DeviceMapper每次鏡像運行時都須要拷貝全部的鏡像層信息到內存中,當啓動多個鏡像時會佔用很大的內存空間。
針對不一樣的storage-driver咱們用上述etcd的dockerfile進行了一組構建測試
文件存儲系統 |
單次構建時間 |
併發10次平均構建時間 |
DevivceMapper |
44s |
269.5s |
AUFS |
8s |
26s |
Overlay2 |
10s |
269.5s |
注:該數據因dockerfile以及操做系統、文件系統、網絡環境的不一樣測試結果可能會存在較大差別
咱們發如今該實驗場景下DevivceMapper在時間上明顯會遜於AUFS和Overlay2,而AUFS和Overlay2基本至關,固然該數據僅能做爲一個參考,實際構建還受到具體的Dockerfile內容以及操做系統、文件系統、網絡環境等多方面的影響,那要怎麼樣才能儘可能讓構建時間最短提高咱們的工做效率呢?
且看下回分解!