一步步了解 Docker 存儲(chǔ)驅(qū)動(dòng)
鏡像的分層特性
在說(shuō)docker的文件系統(tǒng)之前,我們需要先想清楚一個(gè)問(wèn)題。我們知道docker的啟動(dòng)是依賴(lài)于image,docker在啟動(dòng)之前,需要先拉取image,然后啟動(dòng)。多個(gè)容器可以使用同一個(gè)image啟動(dòng)。那么問(wèn)題來(lái)了:這些個(gè)容器是共用一個(gè)image,還是各自將這個(gè)image復(fù)制了一份,然后各自獨(dú)立運(yùn)行呢?
我們假設(shè)每個(gè)容器都復(fù)制了一份這個(gè)image,然后各自獨(dú)立運(yùn)行,那么就意味著,啟動(dòng)多少個(gè)容器,就需要復(fù)制多少個(gè)image,毫無(wú)疑問(wèn)這是對(duì)空間的一種巨大浪費(fèi)。事實(shí)上,在容器的設(shè)計(jì)當(dāng)中,通過(guò)同一個(gè)Image啟動(dòng)的容器,全部都共享這個(gè)image,而并不復(fù)制。那么問(wèn)題又隨之而來(lái):既然所有的容器都共用這一個(gè)image,那么豈不是我在任意一個(gè)容器中所做的修改,在其他容器中都可見(jiàn)?如果我一個(gè)容器要將一個(gè)配置文件修改成A,而另一個(gè)容器同樣要將這個(gè)文件修改成B,兩個(gè)容器豈不是會(huì)產(chǎn)生沖突?
我們把上面的問(wèn)題放一放,先來(lái)看下面一個(gè)拉取鏡像的示例:
- root@ubuntu:~# docker pull nginx
- Using default tag: latest
- latest: Pulling from library/nginx
- be8881be8156: Pull complete
- 32d9726baeef: Pull complete
- 87e5e6f71297: Pull complete
- Digest: sha256:6ae5dd1664d46b98257382fd91b50e332da989059482e2944aaa41ae6cf8043a
- Status: Downloaded newer image for nginx:latest
上面的示例是從docker官方鏡像倉(cāng)庫(kù)拉取一個(gè)nginx:latest鏡像,可以看到在拉取鏡像時(shí),是一層一層的拉取的。事實(shí)上鏡像也是這么一層一層的存儲(chǔ)在磁盤(pán)上的。通常一個(gè)應(yīng)用鏡像包含多層,如下:
我們首先需要明確一點(diǎn),鏡像是只讀的。每一層都只讀。在上圖上,我們可以看到,在內(nèi)核之上,最底層首先是一個(gè)基礎(chǔ)鏡像層,這里是一個(gè)ubuntu的基礎(chǔ)鏡像,因?yàn)殓R像的只讀特性,如果我們想要在這個(gè)ubuntu的基礎(chǔ)鏡像上安裝一個(gè)emacs編輯器,則只能在基礎(chǔ)鏡像之上,在構(gòu)建一層新的鏡像層。同樣的道理,如果想要在當(dāng)前的emacs鏡像層之上添加一個(gè)apache,則只能在其上再構(gòu)建一個(gè)新的鏡像層。而這即是鏡像的分層特性。
容器讀寫(xiě)層的工作原理
我們剛剛在說(shuō)鏡像的分層特性的時(shí)候說(shuō)到鏡像是只讀的。而事實(shí)上當(dāng)我們使用鏡像啟動(dòng)一個(gè)容器的時(shí)候,我們其實(shí)是可以在容器里隨意讀寫(xiě)的,從結(jié)果上看,似乎與鏡像的只讀特性相悖。
我們繼續(xù)看上面的圖,其實(shí)可以看到在鏡像的最上層,還有一個(gè)讀寫(xiě)層。而這個(gè)讀寫(xiě)層,即在容器啟動(dòng)時(shí)為當(dāng)前容器單獨(dú)掛載。每一個(gè)容器在運(yùn)行時(shí),都會(huì)基于當(dāng)前鏡像在其最上層掛載一個(gè)讀寫(xiě)層。而用戶(hù)針對(duì)容器的所有操作都在讀寫(xiě)層中完成。一旦容器銷(xiāo)毀,這個(gè)讀寫(xiě)層也隨之銷(xiāo)毀。
知識(shí)點(diǎn): 容器=鏡像+讀寫(xiě)層而我們針對(duì)這個(gè)讀寫(xiě)層的操作,主要基于兩種方式:寫(xiě)時(shí)復(fù)制和用時(shí)分配。
寫(xiě)時(shí)復(fù)制
所有驅(qū)動(dòng)都用到的技術(shù)——寫(xiě)時(shí)復(fù)制(CoW)。CoW就是copy-on-write,表示只在需要寫(xiě)時(shí)才去復(fù)制,這個(gè)是針對(duì)已有文件的修改場(chǎng)景。比如基于一個(gè)image啟動(dòng)多個(gè)Container,如果為每個(gè)Container都去分配一個(gè)image一樣的文件系統(tǒng),那么將會(huì)占用大量的磁盤(pán)空間。而CoW技術(shù)可以讓所有的容器共享image的文件系統(tǒng),所有數(shù)據(jù)都從image中讀取,只有當(dāng)要對(duì)文件進(jìn)行寫(xiě)操作時(shí),才從image里把要寫(xiě)的文件復(fù)制到自己的文件系統(tǒng)進(jìn)行修改。所以無(wú)論有多少個(gè)容器共享同一個(gè)image,所做的寫(xiě)操作都是對(duì)從image中復(fù)制到自己的文件系統(tǒng)中的復(fù)本上進(jìn)行,并不會(huì)修改image的源文件,且多個(gè)容器操作同一個(gè)文件,會(huì)在每個(gè)容器的文件系統(tǒng)里生成一個(gè)復(fù)本,每個(gè)容器修改的都是自己的復(fù)本,相互隔離,相互不影響。使用CoW可以有效的提高磁盤(pán)的利用率。
用時(shí)配置
用時(shí)分配是用在原本沒(méi)有這個(gè)文件的場(chǎng)景,只有在要新寫(xiě)入一個(gè)文件時(shí)才分配空間,這樣可以提高存儲(chǔ)資源的利用率。比如啟動(dòng)一個(gè)容器,并不會(huì)為這個(gè)容器預(yù)分配一些磁盤(pán)空間,而是當(dāng)有新文件寫(xiě)入時(shí),才按需分配新空間。
Docker存儲(chǔ)驅(qū)動(dòng)
接下來(lái)我們說(shuō)一說(shuō),這些分層的鏡像是如何在磁盤(pán)中存儲(chǔ)的。
docker提供了多種存儲(chǔ)驅(qū)動(dòng)來(lái)實(shí)現(xiàn)不同的方式存儲(chǔ)鏡像,下面是常用的幾種存儲(chǔ)驅(qū)動(dòng):
- AUFS
- OverlayFS
- Devicemapper
- Btrfs
- ZFS
下面說(shuō)一說(shuō)AUFS、OverlayFS及Devicemapper:
AUFS
AUFS(AnotherUnionFS)是一種Union FS,是文件級(jí)的存儲(chǔ)驅(qū)動(dòng)。AUFS是一個(gè)能透明覆蓋一個(gè)或多個(gè)現(xiàn)有文件系統(tǒng)的層狀文件系統(tǒng),把多層合并成文件系統(tǒng)的單層表示。簡(jiǎn)單來(lái)說(shuō)就是支持將不同目錄掛載到同一個(gè)虛擬文件系統(tǒng)下的文件系統(tǒng)。這種文件系統(tǒng)可以一層一層地疊加修改文件。無(wú)論底下有多少層都是只讀的,只有最上層的文件系統(tǒng)是可寫(xiě)的。當(dāng)需要修改一個(gè)文件時(shí),AUFS創(chuàng)建該文件的一個(gè)副本,使用CoW將文件從只讀層復(fù)制到可寫(xiě)層進(jìn)行修改,結(jié)果也保存在可寫(xiě)層。在Docker中,底下的只讀層就是image,可寫(xiě)層就是Container。結(jié)構(gòu)如下圖所示:
OverlayFS
Overlay是Linux內(nèi)核3.18后支持的,也是一種Union FS,和AUFS的多層不同的是Overlay只有兩層:一個(gè)upper文件系統(tǒng)和一個(gè)lower文件系統(tǒng),分別代表Docker的鏡像層和容器層。當(dāng)需要修改一個(gè)文件時(shí),使用CoW將文件從只讀的lower復(fù)制到可寫(xiě)的upper進(jìn)行修改,結(jié)果也保存在upper層。在Docker中,底下的只讀層就是image,可寫(xiě)層就是Container。目前最新的OverlayFS為Overlay2。結(jié)構(gòu)如下圖所示:
Devicemapper
Device mapper是Linux內(nèi)核2.6.9后支持的,提供的一種從邏輯設(shè)備到物理設(shè)備的映射框架機(jī)制,在該機(jī)制下,用戶(hù)可以很方便的根據(jù)自己的需要制定實(shí)現(xiàn)存儲(chǔ)資源的管理策略。前面講的AUFS和OverlayFS都是文件級(jí)存儲(chǔ),而Device mapper是塊級(jí)存儲(chǔ),所有的操作都是直接對(duì)塊進(jìn)行操作,而不是文件。Device mapper驅(qū)動(dòng)會(huì)先在塊設(shè)備上創(chuàng)建一個(gè)資源池,然后在資源池上創(chuàng)建一個(gè)帶有文件系統(tǒng)的基本設(shè)備,所有鏡像都是這個(gè)基本設(shè)備的快照,而容器則是鏡像的快照。所以在容器里看到文件系統(tǒng)是資源池上基本設(shè)備的文件系統(tǒng)的快照,并沒(méi)有為容器分配空間。當(dāng)要寫(xiě)入一個(gè)新文件時(shí),在容器的鏡像內(nèi)為其分配新的塊并寫(xiě)入數(shù)據(jù),這個(gè)叫用時(shí)分配。當(dāng)要修改已有文件時(shí),再使用CoW為容器快照分配塊空間,將要修改的數(shù)據(jù)復(fù)制到在容器快照中新的塊里再進(jìn)行修改。Device mapper 驅(qū)動(dòng)默認(rèn)會(huì)創(chuàng)建一個(gè)100G的文件包含鏡像和容器。每一個(gè)容器被限制在10G大小的卷內(nèi),可以自己配置調(diào)整。結(jié)構(gòu)如下圖所示:
常用存儲(chǔ)驅(qū)動(dòng)對(duì)比
AUFS VS OverlayFS
AUFS和Overlay都是聯(lián)合文件系統(tǒng),但AUFS有多層,而Overlay只有兩層,所以在做寫(xiě)時(shí)復(fù)制操作時(shí),如果文件比較大且存在比較低的層,則AUSF可能會(huì)慢一些。而且Overlay并入了linux kernel mainline,AUFS沒(méi)有。目前AUFS已基本被淘汰。
OverlayFS VS Device mapper
OverlayFS是文件級(jí)存儲(chǔ),Device mapper是塊級(jí)存儲(chǔ),當(dāng)文件特別大而修改的內(nèi)容很小,Overlay不管修改的內(nèi)容大小都會(huì)復(fù)制整個(gè)文件,對(duì)大文件進(jìn)行修改顯示要比小文件要消耗更多的時(shí)間,而塊級(jí)無(wú)論是大文件還是小文件都只復(fù)制需要修改的塊,并不是整個(gè)文件,在這種場(chǎng)景下,顯然device mapper要快一些。因?yàn)閴K級(jí)的是直接訪(fǎng)問(wèn)邏輯盤(pán),適合IO密集的場(chǎng)景。而對(duì)于程序內(nèi)部復(fù)雜,大并發(fā)但少I(mǎi)O的場(chǎng)景,Overlay的性能相對(duì)要強(qiáng)一些。