大家好,我是費益洲。UnionFS 作為 Docker 的技術核心之一,實現了 Docker 鏡像的分層輕量化構建、容器資源的隔離復用等目的。本文將從核心原理、主流技術實現簡單介紹 UnionFS。
核心原理
Linux 的聯合文件系統(Union File System,簡稱 UnionFS)是一種將多個目錄層(Layer)“透明地”疊加為單一視圖的文件系統的技術。
UnionFS 疊加的過程其實是 UnionFS 將多個目錄層(Layer)聯合掛載到了同一個掛載點,并進行目錄層內容的覆蓋。用戶最終看到的是一個合并后的文件系統,但實際上文件內容可能分布在不同的存儲位置。當用戶在最終合并完成后的文件系統寫入內容時,系統是真正將內容寫入了新的文件,但是這個過程并不會改變原來的文件,這是因為 UnionFS 還用到了另外一個重要的資源管理技術,寫時復制。
分層存儲
層(Layer):層是 UnionFS 的基本組成單元,每一層都包含文件系統的一部分內容,多個層聯合疊加后,共同組成一個完整的文件系統。層一般分為只讀層和讀寫層:
只讀層(Read-Only Layer):
- 可能有多個只讀層,每個只讀層的內容都是固定不變的
- 每個只讀層都包含一部分系統需要的庫或者二進制文件等基礎文件
- 相同的只讀層只會在物理存儲中存在一個,但是可以被多個容器共享
- 多個只讀層之間有上下層關系,上層可以覆蓋下層的同名文件夾和文件
讀寫層(Read-Write Layer):
- 在容器技術中,讀寫層都是記錄容器在運行過程中的內容修改
- 每個容器在運行時都會有獨立的讀寫層,這種設計解決了容器運行時的文件內容隔離
- 在容器被刪除時,該容器的讀寫層也會被刪除
寫時復制
寫時復制機制(Copy-on-Write),即只有在需要寫入(修改)時,才會進行復制操作,然后操作復制出來的副本。這種機制可以節省大量的存儲空間和時間,而且不會修改原始文件。整體的工作流程如下所示:
-
讀操作流程:系統從上層到下層依次查找文件,找到文件后直接讀取,無額外開銷
-
首次寫入(修改)流程:
-
后續寫入(修改)流程:無需再次復制,直接修改讀寫層中已經存在的副本
主流實現技術
UnionFS 的主流實現技術有: ??AUFS?、??OverlayFS、??Device Mapper??、Btrfs/ZFS 等。AUFS 是一個用戶空間的聯合文件系統實現,設計目標是提供最大的靈活性和功能完整性,AUFS 是早期 Docker 的默認存儲驅動。Device Mapper 不是傳統意義上的文件系統,而是 Linux 內核的一個框架,用于將物理塊設備映射為虛擬塊設備。Btrfs 和 ZFS 都是現代文件系統,原生支持快照和子卷功能。
Docker 目前默認使用 OverlayFS 作為默認的存儲系統,Docker 從早期的 AUFS 轉向 OverlayFS,是綜合了內核兼容性、性能、資源利用率及社區生態等多方面因素的結果:
💻 1.內核兼容性
-
AUFS 未被內核主線接納
AUFS 作為第三方文件系統,?? 從未被集成到 Linux 內核主線 ??,需用戶手動為內核打補丁并重新編譯,導致兼容性差。尤其在 RedHat/CentOS 等強調穩定性的發行版中無法直接使用 -
內核原生支持 OverlayFS
自 Linux 內核版本 3.18 起,OverlayFS 直接集成到內核主線 ??,無需額外補丁,到 4.0 版本以后,就支持了所有功能。
? 2.性能
- OverlayFS?? 的 CoW 操作是塊級(Block-level)??,復制單位是文件的數據塊。AUFS?? 的 CoW 操作是文件級(File-level)??,復制單位是整個文件,對大文件操作延遲顯著
📦 3.資源利用率
-
OverlayFS(overlay2) 通過符號鏈接管理層間依賴,避免 inode 耗盡問題。AUFS 則依賴硬鏈接,易導致 inode 枯竭(尤其在頻繁創建/銷毀容器的場景)
-
OverlayFS 支持跨容器共享頁緩存:多個容器訪問同一鏡像文件時,內核僅緩存一份數據,降低內存占用。AUFS 無此優化,每個容器需獨立緩存相同文件
📈 4.社區生態
-
自 Docker 版本 1.12 起,OverlayFS 成為默認存儲驅動,官方持續優化其穩定性與功能
-
Linux 內核社區放棄 AUFS 維護,新特性(如 ID 映射、安全增強)僅適配 OverlayFS
OverlayFS
OverlayFS(Overlay File System)是 Linux 內核中的一種聯合掛載文件系統,通過將多個目錄(層)透明疊加為單一視圖,實現高效的分層存儲管理。OverlayFS 使用四個目錄來實現分層聯合:
-
lower(只讀層)
- 可包含多個目錄(如 Docker 鏡像層)
- 文件不可修改,多個容器可共享同一組 lower
-
upper(讀寫層)
- 存儲用戶修改后的內容(如容器運行時的文件內容變更)
- 文件修改或刪除時,通過寫時復制(CoW)將數據從 lower 復制到 upper 再操作
-
workdir(工作目錄)
- ?? 內部臨時目錄,用于原子化操作(如文件復制),掛載后內容自動清空且用戶不可見
-
merged(合并目錄)
- 用戶訪問的最終目錄,動態合并 lower 和 upper 內容
整體架構如下圖所示:
文件操作
🌳 掛載文件
1?? 創建文件夾
mkdir -p /tmp/overlay-demo/{lower1,lower2,upper,work,merged}# 目錄結構圖如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
├── lower2
├── merged
├── upper
└── work5 directories, 0 files
2?? 創建一些初始文件
echo "Config template: lower1" > /tmp/overlay-demo/lower1/config.conf
echo "Config template: lower2" > /tmp/overlay-demo/lower2/config.conf
echo "Env file content: lower1" > /tmp/overlay-demo/lower1/env
echo "Base file content: lower1" > /tmp/overlay-demo/lower1/base1.txt
echo "Base file content: lower2" > /tmp/overlay-demo/lower2/base2.txt
echo "Personal content" > /tmp/overlay-demo/upper/tom.txt
echo "Env file content: upper" > /tmp/overlay-demo/upper/env
創建初始文件后目錄結構如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
├── upper
│ ├── env
│ └── tom.txt
└── work└── work6 directories, 7 files
3?? 掛載目錄
sudo mount -t overlay overlay \
-o lowerdir=/tmp/overlay-demo/lower1:/tmp/overlay-demo/lower2,\
upperdir=/tmp/overlay-demo/upper,\
workdir=/tmp/overlay-demo/work \
/tmp/overlay-demo/merged
掛載目錄需要 root 權限。具體參數如下:
-
-t overlay
表示文件系統為 overlay
-
-o lowerdir=/tmp/overlay-demo/lower1:/tmp/overlay-demo/lower2,upperdir=/tmp/overlay-demo/upper,workdir=/tmp/overlay-demo/work
指定 lowerdir,支持多個 lower,使用
:
隔離,優先級從左往右依次降低;指定 upper;指定 work -
/tmp/overlay-demo/merged
指定 merged,即最終的掛載點、用戶看到的最終的、合并后的統一視圖
掛載后,目錄結構如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
│ ├── base1.txt
│ ├── base2.txt
│ ├── config.conf
│ ├── env
│ └── tom.txt
├── upper
│ ├── env
│ └── tom.txt
└── work└── work6 directories, 12 files
查看 merged 下的 config.conf、env 內容
# config.conf
[root@master01 ~]# cat /tmp/overlay-demo/merged/config.conf
Config template: lower1
# env
[root@master01 ~]# cat /tmp/overlay-demo/merged/env
Env file content: upper
合并后的 config.conf 內容為Config template: lower1
,證明了多個 lower 的優先級在掛載參數中是從左往右依次降低;合并后的 env 的內容為Env file content: upper
,證明了 upper 的優先級高于 lower。整體的文件結構如下圖所示
? 新增文件
echo "test" > /tmp/overlay-demo/merged/test
新增文件后,整體目錄結構如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
│ ├── base1.txt
│ ├── base2.txt
│ ├── config.conf
│ ├── env
│ ├── test
│ └── tom.txt
├── upper
│ ├── env
│ ├── test
│ └── tom.txt
└── work└── work
從上面的目錄結構可以看出,在 OverlayFS 中添加文件其實就是在 upper 中添加文件,然后合并覆蓋后的結果。
文件新增的過程如下所示
📑 修改文件
echo "Modified base content: merged" > /tmp/overlay-demo/merged/base1.txt
修改文件后,整體目錄結構如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
│ ├── base1.txt
│ ├── base2.txt
│ ├── config.conf
│ ├── env
│ └── tom.txt
├── upper
│ ├── base1.txt
│ ├── env
│ └── tom.txt
└── work└── work6 directories, 13 files
# lowerdir內容
[root@master01 ~]# cat /tmp/overlay-demo/lower1/base1.txt
Base file content: lower2# upperdir內容
[root@master01 ~]# cat /tmp/overlay-demo/upper/base1.txt
Modified base content: merged# merged內容
[root@master01 ~]# cat /tmp/overlay-demo/merged/base1.txt
Modified base content: merged
由上面的文件內容變化可以得出試驗結果:
- 原始文件(/tmp/overlay-demo/lower1/base1.txt)保持不變
- 修改文件后 upper 層新增了一個 base1.txt 文件
- 用戶看到的內容其實是來自 upper 層
文件修改的過程如下所示
OverlayFS 中,lower 的文件是不允許修改的,在 Cow 的技術機制下,對 base1.txt 進行修改時,文件系統發現 uppder 中不存在 base1.txt,就會從 lower 中復制一份副本到 upper 中,再進行修改。修改后的內容,在合并覆蓋后,用戶看到的就是對 base1.txt 修改后的內容。
從 lower copy 到 upper,叫做 copy_up
🧺 刪除文件
在上一步的基礎上,刪除/tmp/overlay-demo/merged/base1.txt
rm -f /tmp/overlay-demo/merged/base1.txt
刪除后的文件目錄結構如下
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
│ ├── base2.txt
│ ├── config.conf
│ ├── env
│ └── tom.txt
├── upper
│ ├── base1.txt
│ ├── env
│ └── tom.txt
└── work└── work└── #7eb6 directories, 13 files
查看 upperdir 中的文件變化
[root@master01 ~]# ls -al /tmp/overlay-demo/upper/
total 8
drwxr-xr-x 2 root root 100 Aug 22 14:30 .
drwxr-xr-x 7 root root 140 Aug 22 10:28 ..
c--------- 2 root root 0, 0 Aug 22 14:30 base1.txt
-rw-r--r-- 1 root root 24 Aug 22 11:36 env
-rw-r--r-- 1 root root 17 Aug 22 11:36 tom.txt
可以發現,lower 中的文件并不會被刪除,而是會在 upper 中創建一個標記,表示這個文件已經被刪除了。再次刪除文件c--------- 2 root root 0, 0 Aug 22 14:30 base1.txt
,發現 merged 中又可以看到 base1.txt 文件了
[root@master01 ~]# rm -f /tmp/overlay-demo/upper/base1.txt
[root@master01 ~]# tree /tmp/overlay-demo/
/tmp/overlay-demo/
├── lower1
│ ├── base1.txt
│ ├── config.conf
│ └── env
├── lower2
│ ├── base2.txt
│ └── config.conf
├── merged
│ ├── base1.txt
│ ├── base2.txt
│ ├── config.conf
│ ├── env
│ └── tom.txt
├── upper
│ ├── env
│ └── tom.txt
└── work└── work└── #7eb6 directories, 13 files
由上面的文件內容變化可以得出試驗結果:
- 原始文件(/tmp/overlay-demo/lower1/base1.txt)保持不變
- 刪除文件后 upper 層會添加一個與文件同名的刪除標記
- 刪除標記后,merged 又能夠看到該文件了
OverlayFS 中,刪除從 lower 層映射來的文件或文件夾時,會在 upper 層添加一個與文件或文件夾同名的 c 標識文件,這個文件叫 whiteout 文件。這個標識文件意味著該文件或文件夾已經被刪除,而且合并覆蓋過程中,當掃描到這個文件或文件夾后,會忽略此文件或文件夾。導致 merged 層中不會看到該文件或文件夾,從用戶側來說,即達到了刪除的效果。
文件刪除的過程如下所示