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