Docker鏡像
1 前言
回首過 去的2014年,大家可以看到Docker在全球刮起了一陣又一陣的“容器風”,工業界對Docker的探索與實踐更是一波高過一波。在如今的2015年 以及未來,Docker似乎并不會像其他曇花一現的技術一樣,在歷史的舞臺上熱潮褪去,反而在工業界實踐與評估之后,顯現了前所未有的發展潛力。
究 其本質,“Docker提供容器服務”這句話,相信很少有人會有異議。那么,既然Docker提供的服務屬于“容器”技術,那么反觀“容器”技術的本質與 歷史,我們又可以發現什么呢?正如前文所提到的,Docker使用的“容器”技術,主要是以Linux的cgroup、namespace等內核特性為基 礎,保障進程或者進程組處于一個隔離、安全的環境。Docker發行第一個版本是在2013年的3月,而cgroup的正式亮相可以追溯到2007年下半 年,當時cgroup被合并至Linux內核2.6.24版本。期間6年時間,并不是“容器”技術發展的真空期,2008年LXC(Linux Container)誕生,其簡化了容器的創建與管理;之后業界一些PaaS平臺也初步嘗試采用容器技術作為其云應用的運行環境;而與Docker發布同 年,Google也發布了開源容器管理工具lmctfy。除此之外,若拋開Linux操作系統,其他操作系統如FreeBSD、Solaris等,同樣誕 生了作用相類似的“容器”技術,其發展歷史更是需要追溯至千禧年初期。
可見,“容器”技術的發展不可謂短暫,然而論同時代的影響力,卻鮮有 Docker的媲美者。不論是云計算大潮催生了Docker技術,抑或是Docker技術趕上了云計算的大時代,毋庸置疑的是,Docker作為領域內的 新寵兒,必然會繼續受到業界的廣泛青睞。云計算時代,分布式應用逐漸流行,并對其自身的構建、交付與運行有著與傳統不一樣的要求。借助Linux內核的 cgroup與namespace特性,自然可以做到應用運行環境的資源隔離與應用部署的快速等;然而,cgroup和namespace等內核特性卻無 法為容器的運行環境做全盤打包。而Docker的設計則很好得考慮到了這一點,除cgroup和namespace之外,另外采用了神奇的“鏡像”技術作 為Docker管理文件系統以及運行環境的強有力補充。Docker靈活的“鏡像”技術,在筆者看來,也是其大紅大紫最重要的因素之一。
2.Docker鏡像介紹
大家看到這,第一個問題肯定是“什么是Docker鏡像”?
據Docker官網的技術文檔描述,Image(鏡像)是Docker術語的一種,代表一個只讀的layer。而layer則具體代表Docker Container文件系統中可疊加的一部分。
筆者如此介紹Docker鏡像,相信眾多Docker愛好者理解起來依舊是云里霧里。那么理解之前,先讓我們來認識一下與Docker鏡像相關的4個概念:rootfs、Union mount、image以及layer。
2.1 rootfs
Rootfs:代表一個Docker Container在啟動時(而非運行后)其內部進程可見的文件系統視角,或者是Docker Container的根目錄。當然,該目錄下含有Docker Container所需要的系統文件、工具、容器文件等。
傳 統來說,Linux操作系統內核啟動時,內核首先會掛載一個只讀(read-only)的rootfs,當系統檢測其完整性之后,決定是否將其切換為讀寫 (read-write)模式,或者最后在rootfs之上另行掛載一種文件系統并忽略rootfs。Docker架構下,依然沿用Linux中 rootfs的思想。當Docker Daemon為Docker Container掛載rootfs的時候,與傳統Linux內核類似,將其設定為只讀(read-only)模式。在rootfs掛載完畢之后,和 Linux內核不一樣的是,Docker Daemon沒有將Docker Container的文件系統設為讀寫(read-write)模式,而是利用Union mount的技術,在這個只讀的rootfs之上再掛載一個讀寫(read-write)的文件系統,掛載時該讀寫(read-write)文件系統內空 無一物。
舉一個Ubuntu容器啟動的例子。假設用戶已經通過Docker Registry下拉了Ubuntu:14.04的鏡像,并通過命令docker run –it ubuntu:14.04 /bin/bash將其啟動運行。則Docker Daemon為其創建的rootfs以及容器可讀寫的文件系統可參見圖2.1:
圖2.1 Ubuntu 14.04容器rootfs示意圖
正 如read-only和read-write的含義那樣,該容器中的進程對rootfs中的內容只擁有讀權限,對于read-write讀寫文件系統中的 內容既擁有讀權限也擁有寫權限。通過觀察圖2.1可以發現:容器雖然只有一個文件系統,但該文件系統由“兩層”組成,分別為讀寫文件系統和只讀文件系統。 這樣的理解已然有些層級(layer)的意味。
簡單來講,可以將Docker Container的文件系統分為兩部分,而上文提到是Docker Daemon利用Union Mount的技術,將兩者掛載。那么Union mount又是一種怎樣的技術?
2.2 Union mount
Union mount:代表一種文件系統掛載的方式,允許同一時刻多種文件系統掛載在一起,并以一種文件系統的形式,呈現多種文件系統內容合并后的目錄。
一 般情況下,通過某種文件系統掛載內容至掛載點的話,掛載點目錄中原先的內容將會被隱藏。而Union mount則不會將掛載點目錄中的內容隱藏,反而是將掛載點目錄中的內容和被掛載的內容合并,并為合并后的內容提供一個統一獨立的文件系統視角。通常來 講,被合并的文件系統中只有一個會以讀寫(read-write)模式掛載,而其他的文件系統的掛載模式均為只讀(read-only)。實現這種 Union mount技術的文件系統一般被稱為Union Filesystem,較為常見的有UnionFS、AUFS、OverlayFS等。
Docker實現容器文件系統Union mount時,提供多種具體的文件系統解決方案,如Docker早版本沿用至今的的AUFS,還有在docker 1.4.0版本中開始支持的OverlayFS等。
更深入的了解Union mount,可以使用AUFS文件系統來進一步闡述上文中ubuntu:14.04容器文件系統的例子。如圖2.2:
圖2.2 AUFS掛載Ubuntu 14.04文件系統示意圖
使 用鏡像ubuntu:14.04創建的容器中,可以暫且將該容器整個rootfs當成是一個文件系統。上文也提到,掛載時讀寫(read-write)文 件系統中空無一物。既然如此,從用戶視角來看,容器內文件系統和rootfs完全一樣,用戶完全可以按照往常習慣,無差別的使用自身視角下文件系統中的所 有內容;然而,從內核的角度來看,兩者在有著非常大的區別。追溯區別存在的根本原因,那就不得不提及AUFS等文件系統的COW(copy-on- write)特性。
COW文件系統和其他文件系統最大的區別就是:從不覆寫已有文件系統中已有的內容。由于通過COW文件系統將兩個文件系 統(rootfs和read-write filesystem)合并,最終用戶視角為合并后的含有所有內容的文件系統,然而在Linux內核邏輯上依然可以區別兩者,那就是用戶對原先 rootfs中的內容擁有只讀權限,而對read-write filesystem中的內容擁有讀寫權限。
既然對用戶而言,全然不知哪些 內容只讀,哪些內容可讀寫,這些信息只有內核在接管,那么假設用戶需要更新其視角下的文件/etc/hosts,而該文件又恰巧是rootfs只讀文件系 統中的內容,內核是否會拋出異常或者駁回用戶請求呢?答案是否定的。當此情形發生時,COW文件系統首先不會覆寫read-only文件系統中的文件,即 不會覆寫rootfs中/etc/hosts,其次反而會將該文件拷貝至讀寫文件系統中,即拷貝至讀寫文件系統中的/etc/hosts,最后再對后者進 行更新操作。如此一來,縱使rootfs與read-write filesystem中均由/etc/ hosts,諸如AUFS類型的COW文件系統也能保證用戶視角中只能看到read-write filesystem中的/etc/hosts,即更新后的內容。
當然,這樣的特性同樣支持rootfs中文件的刪除等其他操作。例如:用 戶通過apt-get軟件包管理工具安裝Golang,所有與Golang相關的內容都會被安裝在讀寫文件系統中,而不會安裝在rootfs。此時用戶又 希望通過apt-get軟件包管理工具刪除所有關于MySQL的內容,恰巧這部分內容又都存在于rootfs中時,刪除操作執行時同樣不會刪除 rootfs實際存在的MySQL,而是在read-write filesystem中刪除該部分內容,導致最終rootfs中的MySQL對容器用戶不可見,也不可訪。
掌握Docker中rootfs以及Union mount的概念之后,再來理解Docker鏡像,就會變得水到渠成。
2.3 image
Docker中rootfs的概念,起到容器文件系統中基石的作用。對于容器而言,其只讀的特性,也是不難理解。神奇的是,實際情況下Docker的rootfs設計與實現比上文的描述還要精妙不少。
繼 續以ubuntu 14.04為例,雖然通過AUFS可以實現rootfs與read-write filesystem的合并,但是考慮到rootfs自身接近200MB的磁盤大小,如果以這個rootfs的粒度來實現容器的創建與遷移等,是否會稍顯 笨重,同時也會大大降低鏡像的靈活性。而且,若用戶希望擁有一個ubuntu 14.10的rootfs,那么是否有必要創建一個全新的rootfs,畢竟ubuntu 14.10和ubuntu 14.04的rootfs中有很多一致的內容。
Docker中image的概念,非常巧妙的解決了以上的問題。最為簡單的解釋image, 就是 Docker容器中只讀文件系統rootfs的一部分。換言之,實際上Docker容器的rootfs可以由多個image來構成。多個image構成 rootfs的方式依然沿用Union mount技術。
多個Image構成rootfs的示意圖如圖2.3(圖中,rootfs中每一層image中的內容劃分只為了闡述清楚rootfs由多個image構成,并不代表實際情況中rootfs中的內容劃分):
圖2.3容器rootfs多image構成圖
從 上圖可以看出,舉例的容器rootfs包含4個image,其中每個image中都有一些用戶視角文件系統中的一部分內容。4個image處于層疊的關 系,除了最底層的image,每一層的image都疊加在另一個image之上。另外,每一個image均含有一個image ID,用以唯一的標記該image。
基于以上的概念,Docker Image中又抽象出兩種概念:Parent Image以及Base Image。除了容器rootfs最底層的image,其余image都依賴于其底下的一個或多個image,而Docker中將下一層的image稱為 上一層image的Parent Image。以圖2.3為例,imageID_0是imageID_1的Parent Image,imageID_2是imageID_3的Parent Image,而imageID_0沒有Parent Image。對于最下層的image,即沒有Parent Image的鏡像,在Docker中習慣稱之為Base Image。
通過image的形式,原先較為臃腫的rootfs被逐漸打散成輕便的多層。Image除了輕便的特性,同時還有上文提到的只讀特性,如此一來,在不同的容器、不同的rootfs中image完全可以用來復用。
多image組織關系與復用關系如圖2.4(圖中鏡像名稱的舉例只為將image之間的關系闡述清楚,并不代表實際情況中相應名稱image之間的關系):
圖2.4 多image組織關系示意圖
圖 2.4中,共羅列了11個image,這11個image之間的關系呈現一副森林圖。森林中含有兩棵樹,左邊樹中包含5個節點,即含有5個image;右 邊樹中包含6個節點,即含有6個image。圖中,有些image標記了紅色字段,意味該image代表某一種容器鏡像rootfs的最上層image。 如圖中的ubuntu:14.04,代表imageID_3為該類型容器rootfs的最上層,沿著該節點找到樹的根節點,可以發現路徑上還有 imageID_2,imageID_1和imageID_0。特殊的是,imageID_2作為imageID_3的Parent Image,同時又是容器鏡像ubuntu:12.04的rootfs中的最上層,可見鏡像ubuntu:14.04只是在鏡像ubuntu:12.04 之上,再另行疊加了一層。因此,在下載鏡像ubuntu:12.04以及ubuntu:14.04時,只會下載一份imageID_2、 imageID_1和imageID_0,實現image的復用。同時,右邊樹中mysql:5.6、mongo:2.2、debian:wheezy和 debian:jessie也呈現同樣的關系。
2.4 layer
Docker術語中,layer是一個與image含義較為相近的詞。容器鏡像的rootfs是容器只讀的文件系統,rootfs又是由多個只讀的image構成。于是,rootfs中每個只讀的image都可以稱為一層layer。
除了只讀的image之外,Docker Daemon在創建容器時會在容器的rootfs之上,再mount一層read-write filesystem,而這一層文件系統,也稱為容器的一層layer,常被稱為top layer。
因此,總結而言,Docker容器中的每一層只讀的image,以及最上層可讀寫的文件系統,均被稱為layer。如此一來,layer的范疇比image多了一層,即多包含了最上層的read-write filesystem。
有了layer的概念,大家可以思考這樣一個問題:容器文件系統分為只讀的rootfs,以及可讀寫的top layer,那么容器運行時若在top layer中寫入了內容,那這些內容是否可以持久化,并且也被其它容器復用?
上文對于image的分析中,提到了image有復用的特性,既然如此,再提一個更為大膽的假設:容器的top layer是否可以轉變為image?
答 案是肯定的。Docker的設計理念中,top layer轉變為image的行為(Docker中稱為commit操作),大大釋放了容器rootfs的靈活性。Docker的開發者完全可以基于某個 鏡像創建容器做開發工作,并且無論在開發周期的哪個時間點,都可以對容器進行commit,將所有top layer中的內容打包為一個image,構成一個新的鏡像。Commit完畢之后,用戶完全可以基于新的鏡像,進行開發、分發、測試、部署等。不僅 docker commit的原理如此,基于Dockerfile的docker build,其追核心的思想,也是不斷將容器的top layer轉化為image。
3.總結
Docker 風暴席卷全球,并非偶然。如今的云計算時代下,輕量級容器技術與靈活的鏡像技術相結合,似乎顛覆了以往的軟件交付模式,為持續集成(Continuous Integration, CI)與持續交付(Continuous Delivery, CD)的發展帶來了全新的契機。
理解 Docker的“鏡像”技術,有助于Docker愛好者更好的使用、創建以及交付Docker鏡像。基于此,本文從Docker鏡像的4個重要概念入手, 介紹了Docker鏡像中包含的內容,涉及到的技術,還有重要的特性。Docker引入優秀的“鏡像”技術時,著實使容器的使用變得更為便利,也拓寬了 Docker的使用范疇。然而,于此同時,我們也應該理性地看待鏡像技術引入時,是否會帶來其它的副作用。