## 第三節
# Docker五種存儲驅動原理及應用場景和性能測試對比
Docker最開始采用AUFS作為文件系統,也得益于AUFS分層的概念,實現了多個Container可以共享同一個image。但由于AUFS未并入Linux內核,且只支持Ubuntu,考慮到兼容性問題,在Docker 0.7版本中引入了存儲驅動, 目前,Docker支持AUFS、Btrfs、Device mapper、OverlayFS、ZFS五種存儲驅動。就如Docker官網上說的,沒有單一的驅動適合所有的應用場景,要根據不同的場景選擇合適的存儲驅動,才能有效的提高Docker的性能。如何選擇適合的存儲驅動,要先了解存儲驅動原理才能更好的判斷,本文介紹一下Docker五種存儲驅動原理詳解及應用場景及IO性能測試的對比。在講原理前,先講一下寫時復制和寫時分配兩個技術。
## 原理說明
### 寫時復制(CoW)
所有驅動都用到的技術——寫時復制(CoW)。CoW就是copy-on-write,表示只在需要寫時才去復制,這個是針對已有文件的修改場景。比如基于一個image啟動多個Container,如果為每個Container都去分配一個image一樣的文件系統,那么將會占用大量的磁盤空間。而CoW技術可以讓所有的容器共享image的文件系統,所有數據都從image中讀取,只有當要對文件進行寫操作時,才從image里把要寫的文件復制到自己的文件系統進行修改。所以無論有多少個容器共享同一個image,所做的寫操作都是對從image中復制到自己的文件系統中的復本上進行,并不會修改image的源文件,且多個容器操作同一個文件,會在每個容器的文件系統里生成一個復本,每個容器修改的都是自己的復本,相互隔離,相互不影響。使用CoW可以有效的提高磁盤的利用率。
### 用時分配(allocate-on-demand)
而寫時分配是用在原本沒有這個文件的場景,只有在要新寫入一個文件時才分配空間,這樣可以提高存儲資源的利用率。比如啟動一個容器,并不會為這個容器預分配一些磁盤空間,而是當有新文件寫入時,才按需分配新空間。
### AUFS
AUFS(AnotherUnionFS)是一種Union FS,是文件級的存儲驅動。AUFS能透明覆蓋一或多個現有文件系統的層狀文件系統,把多層合并成文件系統的單層表示。簡單來說就是支持將不同目錄掛載到同一個虛擬文件系統下的文件系統。這種文件系統可以一層一層地疊加修改文件。無論底下有多少層都是只讀的,只有最上層的文件系統是可寫的。當需要修改一個文件時,AUFS創建該文件的一個副本,使用CoW將文件從只讀層復制到可寫層進行修改,結果也保存在可寫層。在Docker中,底下的只讀層就是image,可寫層就是Container。結構如下圖所示:

### 分析
- 雖然AUFS是Docker 第一版支持的存儲方式,但到現在還沒有加入內核主線( centos 無法直接使用)
- 從原理分析看,AUFS mount()方法很快,所以創建容器很快;讀寫訪問都具有本機效率;順序讀寫和隨機讀寫的性能大于kvm;并且Docker的AUFS可以有效的使用存儲和內存 。
- AUFS性能穩定,并且有大量生產部署及豐富的社區支持
- 不支持rename系統調用,執行“copy”和“unlink”時,會導致失敗。
- 當寫入大文件的時候(比如日志或者數據庫等)動態mount多目錄路徑的問題,導致branch越多,查找文件的性能也就越慢。(解決辦法:重要數據直接使用 -v 參數掛載。)
### OverlayFS
Overlay是Linux內核3.18后支持的,也是一種Union FS,和AUFS的多層不同的是Overlay只有兩層:一個upper文件系統和一個lower文件系統,分別代表Docker的鏡像層和容器層。當需要修改一個文件時,使用CoW將文件從只讀的lower復制到可寫的upper進行修改,結果也保存在upper層。在Docker中,底下的只讀層就是image,可寫層就是Container。結構如下圖所示:

### 分析
- 從kernel3.18進入主流Linux內核。設計簡單,速度快,比AUFS和Device mapper速度快。在某些情況下,也比Btrfs速度快。是Docker存儲方式選擇的未來。因為OverlayFS只有兩層,不是多層,所以OverlayFS “copy-up”操作快于AUFS。以此可以減少操作延時。
- OverlayFS支持頁緩存共享,多個容器訪問同一個文件能共享一個頁緩存,以此提高內存使用率。
- OverlayFS消耗inode,隨著鏡像和容器增加,inode會遇到瓶頸。Overlay2能解決這個問題。在Overlay下,為了解決inode問題,可以考慮將/var/lib/docker掛在單獨的文件系統上,或者增加系統inode設置。
- 有兼容性問題。open(2)只完成部分POSIX標準,OverlayFS的某些操作不符合POSIX標準。例如: 調用fd1=open("foo", O_RDONLY) ,然后調用fd2=open("foo", O_RDWR) 應用期望fd1 和fd2是同一個文件。然后由于復制操作發生在第一個open(2)操作后,所以認為是兩個不同的文件。
- 不支持rename系統調用,執行“copy”和“unlink”時,將導致失敗。
### Device mapper
Device mapper是Linux內核2.6.9后支持的,提供的一種從邏輯設備到物理設備的映射框架機制,在該機制下,用戶可以很方便的根據自己的需要制定實現存儲資源的管理策略。Docker的Device mapper利用 Thin provisioning snapshot管理鏡像和容器。
前面講的AUFS和OverlayFS都是文件級存儲,而Device mapper是塊級存儲,所有的操作都是直接對塊進行操作,而不是文件。Device mapper驅動會先在塊設備上創建一個資源池,然后在資源池上創建一個帶有文件系統的基本設備,所有鏡像都是這個基本設備的快照,而容器則是鏡像的快照。所以在容器里看到文件系統是資源池上基本設備的文件系統的快照,并不有為容器分配空間。當要寫入一個新文件時,在容器的鏡像內為其分配新的塊并寫入數據,這個叫用時分配。當要修改已有文件時,再使用CoW為容器快照分配塊空間,將要修改的數據復制到在容器快照中新的塊里再進行修改。Device mapper 驅動默認會創建一個100G的文件包含鏡像和容器。每一個容器被限制在10G大小的卷內,可以自己配置調整。結構如下圖所示:

#### Thin-provisioning Snapshot
Snapshot是Lvm提供的一種特性,它可以在不中斷服務運行的情況下為the origin(original device)創建一個虛擬快照(Snapshot)。Thin-Provisioning是一項利用虛擬化方法減少物理存儲部署的技術。Thin-provisioning Snapshot是結合Thin-Provisioning和Snapshoting兩種技術,允許多個虛擬設備同時掛載到一個數據卷以達到數據共享的目的。Thin-Provisioning Snapshot的特點如下:
- 可以將不同的snaptshot掛載到同一個the origin上,節省了磁盤空間。
- 當多個Snapshot掛載到了同一個the origin上,并在the origin上發生寫操作時,將會觸發COW操作。這樣不會降低效率。
- Thin-Provisioning Snapshot支持遞歸操作,即一個Snapshot可以作為另一個Snapshot的the origin,且沒有深度限制。
- 在Snapshot上可以創建一個邏輯卷,這個邏輯卷在實際寫操作(COW,Snapshot寫操作)發生之前是不占用磁盤空間的。

可以通過"docker info"或通過dmsetup ls獲取想要的更多信息。查看docker的Device mapper的信息:
### 分析
- Device mapper文件系統兼容性比較好,并且存儲為一個文件,減少了inode消耗。
- 每次一個容器寫數據都是一個新塊,塊必須從池中分配,真正寫的時候是稀松文件,雖然它的利用率很高,但性能不好,因為額外增加了vfs開銷。
- 每個容器都有自己的塊設備時,它們是真正的磁盤存儲,所以當啟動N個容器時,它都會從磁盤加載N次到內存中,消耗內存大。
- Docker的Device mapper默認模式是loop-lvm,性能達不到生產要求。在生產環境推薦direct-lvm模式直接寫原塊設備,性能好。 ==此處有坑==
### btfs
Btrfs被稱為下一代寫時復制文件系統,并入Linux內核,也是文件級級存儲,但可以像Device mapper一直接操作底層設備。Btrfs利用 subvolumes和snapshots管理鏡像容器分層。Btrfs把文件系統的一部分配置為一個完整的子文件系統,稱之為subvolume ,snapshot是subvolumn的實時讀寫拷貝,chunk是分配單位,通常是1GB。那么采用 subvolume,一個大的文件系統可以被劃分為多個子文件系統,這些子文件系統共享底層的設備空間,在需要磁盤空間時便從底層設備中分配,類似應用程序調用 malloc()分配內存一樣。為了靈活利用設備空間,Btrfs 將磁盤空間劃分為多個chunk 。每個chunk可以使用不同的磁盤空間分配策略。比如某些chunk只存放metadata,某些chunk只存放數據。這種模型有很多優點,比如Btrfs支持動態添加設備。用戶在系統中增加新的磁盤之后,可以使用Btrfs的命令將該設備添加到文件系統中。Btrfs把一個大的文件系統當成一個資源池,配置成多個完整的子文件系統,還可以往資源池里加新的子文件系統,而基礎鏡像則是子文件系統的快照,每個子鏡像和容器都有自己的快照,這些快照則都是subvolume的快照。
### 分析
- Btrfs是替換Device mapper的下一代文件系統, 很多功能還在開發階段,還沒有發布正式版本,相比EXT4或其它更成熟的文件系統,它在技術方面的優勢包括豐富的特征,如:支持子卷、快照、文件系統內置壓縮和內置RAID支持等。
- 不支持頁緩存共享,N個容器訪問相同的文件需要緩存N次。不適合高密度容器場景。
- 當前Btrfs版本使用“small writes”,導致性能問題。并且需要使用Btrfs原生命令btrfs filesys show替代df
- Btrfs使用“journaling”寫數據到磁盤,這將影響順序寫的性能。
- Btrfs文件系統會有碎片,導致性能問題。當前Btrfs版本,能通過mount時指定autodefrag 做檢測隨機寫和碎片整理。
-
### zfs
ZFS 文件系統是一個革命性的全新的文件系統,它從根本上改變了文件系統的管理方式,ZFS 完全拋棄了“卷管理”,不再創建虛擬的卷,而是把所有設備集中到一個存儲池中來進行管理,用“存儲池”的概念來管理物理存儲空間。過去,文件系統都是構建在物理設備之上的。為了管理這些物理設備,并為數據提供冗余,“卷管理”的概念提供了一個單設備的映像。而ZFS創建在虛擬的,被稱為“zpools”的存儲池之上。每個存儲池由若干虛擬設備(virtual devices,vdevs)組成。這些虛擬設備可以是原始磁盤,也可能是一個RAID1鏡像設備,或是非標準RAID等級的多磁盤組。于是zpool上的文件系統可以使用這些虛擬設備的總存儲容量。Docker的ZFS利用snapshots和clones,它們是ZFS的實時拷貝,snapshots是只讀的,clones是讀寫的,clones從snapshot創建。
下面看一下在Docker里ZFS的使用。首先從zpool里分配一個ZFS文件系統給鏡像的基礎層,而其他鏡像層則是這個ZFS文件系統快照的克隆,快照是只讀的,而克隆是可寫的,當容器啟動時則在鏡像的最頂層生成一個可寫層。如下圖所示:
### 分析
- ZFS同 Btrfs類似是下一代文件系統。ZFS在Linux(ZoL)port是成熟的,但不推薦在生產環境上使用Docker的 ZFS存儲方式,除非你有ZFS文件系統的經驗。
- 警惕ZFS內存問題,因為,ZFS最初是為了有大量內存的Sun Solaris服務器而設計 。
- ZFS的“deduplication”特性,因為占用大量內存,推薦關掉。但如果使用SAN,NAS或者其他硬盤RAID技術,可以繼續使用此特性。
- ZFS caching特性適合高密度場景。
- ZFS的128K塊寫,intent log及延遲寫可以減少碎片產生。
- 和ZFS FUSE實現對比,推薦使用Linux原生ZFS驅動。

### 總結

以上是五種Docker存儲方式的介紹及分析,以此為理論依據,選擇自己的Docker存儲方式。同時可以做一些驗證測試:如IO性能測試,以此確定適合自己應用場景的存儲方式。同時,有兩點值得提出:
- 使用SSD(Solid State Devices)存儲,提高性能。
- 考慮使用數據卷掛載提高性能
- 第一章 Docker核心技術(一)
- 第一章 Docker核心技術(二)
- 第一章 Docker核心技術(三)
- 第一章 Docker核心技術(四)
- 第二章 Docker 入門
- 第三章 使用docker鏡像
- 第四章 實戰應用使用Docker構建LNMP環境 (一)
- 第四章 實戰應用使用Docker構建LNMP環境 (二)
- 第四章 實戰應用使用Docker構建LNMP環境 (三)
- 第五章 構建企業級Harbor-Registry
- 第六章 構建Docker應用發布系統(一)
- 第六章 構建Docker應用發布系統(二)
- 第六章 構建Docker應用發布系統(三)
- 第六章 構建Docker應用發布系統(四)