由淺入深 docker 系列: (6) 映象分層

lixiang9194發表於2019-06-26

這篇文章我們簡單聊聊 docker 的映象分層。

思考題

在第四篇文章容器與虛擬機器中,我們說到 docker 其實就是把可執行程式及其所有的依賴打包成映象檔案,然後呼叫宿主機核心介面執行容器。可以想象,像 ubuntu等基礎映象,體積必然不小。那麼,思考以下幾個問題:

  • 我們基於同一個映象(ubuntu 18.4)啟動了兩個容器,會佔用兩倍磁碟空間嗎?
  • 我們在容器內修改或者新建了某個檔案,要修改原映象嗎?
  • 我們基於某映象(ubuntu 18.04)新建一個映象(myubuntu),需要將原映象檔案全部拷貝到新映象中嗎?

首先,讓我們嘗試思考下,如果我們去做,該如何高效的解決這些問題?

  • 問題 1,只要將同一個映象檔案載入到記憶體不同位置就行了,沒必要在磁碟上儲存多份,可以節省大量儲存空間。
  • 問題 2,我們可以參考 Linux 核心管理記憶體的 *Copy-On-Write 策略,也即讀時大家共用一份檔案,如果需要修改再複製一份進行修改,而大部分檔案是其實不會修改的,這樣可以最大限度節省空間,提升效能。
  • 問題 3,我們可以將映象檔案分為多個獨立的層,然後新映象檔案只要引用基礎映象檔案就可以了,這樣可以節省大量空間。至於修改基礎映象檔案的情況,參考問題 2 。

如果你能想到以上思路,那麼恭喜你,因為 Docker 就是這麼做的,你已經具備為寫docker 寫一套檔案系統的實力了(哈哈哈哈,不要飄,還有大量技術細節需要思考)。

Docker的映象分層

接下來,我們來看看 Docker的映象分層機制。

Docker映象是分層構建的,Dockerfile 中每條指令都會新建一層。例如以下 Dockerfile:

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

以上四條指令會建立四層,分別對應基礎映象、複製檔案、編譯檔案以及入口檔案,每層只記錄本層所做的更改,而這些層都是隻讀層。當你啟動一個容器,Docker 會在最頂部新增讀寫層,你在容器內做的所有更改,如寫日誌、修改、刪除檔案等,都儲存到了讀寫層內,一般稱該層為容器層,如下圖所示:

docker layer

事實上,容器(container)和映象(image)的最主要區別就是容器加上了頂層的讀寫層。所有對容器的修改都發生在此層,映象並不會被修改,也即前面說的 COW(copy-on-write)技術。容器需要讀取某個檔案時,直接從底部只讀層去讀即可,而如果需要修改某檔案,則將該檔案拷貝到頂部讀寫層進行修改,只讀層保持不變。

每個容器都有自己的讀寫層,因此多個容器可以使用同一個映象,另外容器被刪除時,其對應的讀寫層也會被刪除(如果你希望多個容器共享或者持久化資料,可以使用 Docker volume)。

最後,執行命令 docker ps -s,可以看到最後有兩列 size 和 virtual size。其中 size就是容器讀寫層佔用的磁碟空間,而 virtual size 就是讀寫層加上對應只讀層所佔用的磁碟空間。如果兩個容器是從同一個映象建立,那麼只讀層就是 100%共享,即使不是從同一映象建立,其映象仍然可能共享部分只讀層(如一個映象是基於另一個建立)。因此,docker 實際佔用的磁碟空間遠遠小於 virtual size 的總和。

以上就是 Docker 映象分層的主要內容,至於這些層的互動、管理就需要儲存驅動程式,也即聯合檔案系統(UnionFS)。Docker 可使用多種驅動,如目前已經合併入 Linux 核心、官方推薦的overlay, 曾在 Ubuntu、Debian等發行版中得到廣泛使用的 AUFS,以及devicemapper、zfs等等,需要根據 Docker以及宿主機系統的版本,進行合適的選擇。接下來以 AUFS 為例,簡單介紹 UnionFS 的使用。


AUFS

AUFS 是一種 UnionFS,所謂 UnionFS 就是把不同物理位置的目錄合併到同一個目錄中。例如把 CD和硬碟 mount 到一起,就可以對 CD上的檔案進行修改,再比如上面所講 docker 的使用場景。

AUFS是Another UnionFS的首字母縮略字,2006年由岡島順治郎開發,是之前的 UnionFS 的完全重寫,其穩定性和效能上確實好很多,但從第 2 版開始它代表advanced multi-layered unification filesystem。

這裡插播一個悲情的小故事,AUFS被 Linus 拒絕合併到主線 Linux,其程式碼被批評為“稠密,不可讀,無註釋”。然後作者不斷改進程式碼,不斷提交,不斷被 Linus 拒掉,最終放棄。而2014年,OverlayFS被合併到 Linux 核心 3.18版本,岡島順治郎再無希望。

言歸正傳,下面我們簡單試驗下 AUFS 的使用。(Ubuntu 18.04環境)

首先,建立兩個資料夾代表只讀層以及讀寫層。

mkdir readLayer
echo "original read file content" >> readLayer/readFile
mkdir writeLayer
echo "original write file content" >> writeLayer/writeFile

然後,將兩個資料夾進行聯合掛載。命令中預設dirs後第一個資料夾為讀寫許可權,之後的資料夾為只讀許可權。

mkdir unionFs
mount -t aufs -o dirs=./writeLayer:./readLayer none ./unionFs

接下來,我們可以檢視下 unionFs資料夾下的內容,發現已經有了readFile、writeFile兩個檔案,說明掛載成功。

然後,我們再來測試下對檔案的修改操作,我們分別修改讀寫層以及只讀層的檔案。

cd unionFs
echo "edit writeable file" >> writeFile
echo "edit readonly file" >> readFile

根據我們之前的分析,針對讀寫層writeLayer的 writeFile 的修改,應該是直接在原始檔上生效;而針對只讀層readLayer的 readFile 的修改,應該是原始檔保持不變,將檔案拷貝到 writeLayer,然後再修改,我們來檢查下。

首先,檢視下只讀層readLayer,檔案內容並沒有改變。

然後檢視下讀寫層,可以看到新增的檔案 readFile,且檔案內容為修改後內容。

可以看到,我們成功模擬了只讀層、讀寫層的聯合使用,Docker的映象分層也是此原理,只是實現更加複雜。

參考資料

以上即是 docker 映象分層技術的簡單介紹,通過這項技術,節省了大量的儲存空間,也提升了容器的下載、構建、啟動速度,推動了 Docker 技術的廣泛使用。

如果你想更進一步瞭解相關細節,可以參考以下文章:

DOCKER基礎技術:AUFS

About storage drivers

閒言

前後拖了 7 個月,終於把六篇文章更完了,把自己學習時的困惑、自己的理解都儘量簡潔的寫出來,希望能幫大家更快的理解學習。在整理的過程中,我對 Docker 技術的認知也更加清晰完整,收穫很大。

每寫一篇文章都很痛苦,文采、技術都不好,糾結怎麼能以更清晰的方式,說更多的技術點。每寫完一篇也很充實,又前近了一步。看著專欄的讀者一個個增加,很開心,也很有成就感,接下來還會更新,不保證頻繁,但每一篇都會盡量寫好。千里之行,始於足下,加油!

相關文章