一、概述
Docker中的鏡像采用分層構建設計,每個層可以稱之為“layer”,這些layer被存放在了/var/lib/docker/<storage-driver>/目錄下,這里的storage-driver可以有很多種如:AUFS、OverlayFS、VFS、Brtfs等。可以通過docker info命令查看存儲驅動,(筆者系統是centos7.4):
通常ubuntu類的系統默認采用的是AUFS,centos7.1+系列采用的是OverlayFS。而本文將介紹以OverlayFS作為存儲驅動的鏡像存儲原理以及存儲結構。
二、OverlayFS
介紹
OverlayFS是一種堆疊文件系統,它依賴並建立在其它的文件系統之上(例如ext4fs和xfs等等),並不直接參與磁盤空間結構的划分,僅僅將原來底層文件系統中不同的目錄進行“合並”,然后向用戶呈現,這也就是聯合掛載技術,對比於AUFS,OverlayFS速度更快,實現更簡單。 而Linux 內核為Docker提供的OverlayFS驅動有兩種:overlay和overlay2。而overlay2是相對於overlay的一種改進,在inode利用率方面比overlay更有效。但是overlay有環境需求:docker版本17.06.02+,宿主機文件系統需要是ext4或xfs格式。
聯合掛載
overlayfs通過三個目錄:lower目錄、upper目錄、以及work目錄實現,其中lower目錄可以是多個,work目錄為工作基礎目錄,掛載后內容會被清空,且在使用過程中其內容用戶不可見,最后聯合掛載完成給用戶呈現的統一視圖稱為為merged目錄。以下使用mount將演示其如何工作的。
使用mount命令掛載overlayfs語法如下:
mount -t overlay overlay -o lowerdir=lower1:lower2:lower3,upperdir=upper,workdir=work merged_dir
創建三個目錄A、B、C,以及worker目錄:
然后使用mount聯合掛載到/tmp/test 下:
然后我們再去查看/tmp/test目錄,你會發現目錄A、B、C被合並到了一起,並且相同文件名的文件會進行“覆蓋”,這里覆蓋並不是真正的覆蓋,而是當合並時候目錄中兩個文件名稱都相同時,merged層目錄會顯示離它最近層的文件:
同時我們還可以通過mount命令查看其掛載的選項:
以上這樣的方式也就是聯合掛載技術。
Docker中的overlay驅動
介紹了overlay驅動原理以后再來看Docker中的overlay存儲驅動,以下是來自docker官網關於overlay的工作原理圖:
在上述圖中可以看到三個層結構,即:lowerdir、uperdir、merged,其中lowerdir是只讀的image layer,其實就是rootfs,對比我們上述演示的目錄A和B,我們知道image layer可以分很多層,所以對應的lowerdir是可以有多個目錄。而upperdir則是在lowerdir之上的一層,這層是讀寫層,在啟動一個容器時候會進行創建,所有的對容器數據更改都發生在這里層,對比示例中的C。最后merged目錄是容器的掛載點,也就是給用戶暴露的統一視角,對比示例中的/tmp/test。而這些目錄層都保存在了/var/lib/docker/overlay2/或者/var/lib/docker/overlay/(如果使用overlay)。
演示
啟動一個容器
查看其overlay掛載點,可以發現其掛載的merged目錄、lowerdir、upperdir以及workdir:
overlay2的lowerdir可以有多個,並且是軟連接方式掛載,后續我們會進行說明。
如何工作
- 如果文件在容器層(upperdir),直接讀取文件;
- 如果文件不在容器層(upperdir),則從鏡像層(lowerdir)讀取;
- 首次寫入: 如果在upperdir中不存在,overlay和overlay2執行copy_up操作,把文件從lowdir拷貝到upperdir,由於overlayfs是文件級別的(即使文件只有很少的一點修改,也會產生的copy_up的行為),后續對同一文件的在此寫入操作將對已經復制到容器的文件的副本進行操作。這也就是常常說的寫時復制(copy-on-write)
- 刪除文件和目錄: 當文件在容器被刪除時,在容器層(upperdir)創建whiteout文件,鏡像層(lowerdir)的文件是不會被刪除的,因為他們是只讀的,但without文件會阻止他們顯示,當目錄在容器內被刪除時,在容器層(upperdir)一個不透明的目錄,這個和上面whiteout原理一樣,阻止用戶繼續訪問,即便鏡像層仍然存在。
- copy_up操作只發生在文件首次寫入,以后都是只修改副本,
- overlayfs只適用兩層目錄,,相比於比AUFS,查找搜索都更快。
- 容器層的文件刪除只是一個“障眼法”,是靠whiteout文件將其遮擋,image層並沒有刪除,這也就是為什么使用docker commit 提交保存的鏡像會越來越大,無論在容器層怎么刪除數據,image層都不會改變。
三、overlay2鏡像存儲結構
從倉庫pull一個ubuntu鏡像,結果顯示總共拉取了4層鏡像如下:
此時4層被存儲在了/var/lib/docker/overlay2/目錄下:
這里面多了一個l目錄包含了所有層的軟連接,短鏈接使用短名稱,避免mount時候參數達到頁面大小限制(演示中mount命令查看時候的短目錄):
處於底層的鏡像目錄包含了一個diff和一個link文件,diff目錄存放了當前層的鏡像內容,而link文件則是與之對應的短名稱:
在這之上的鏡像還多了work目錄和lower文件,lower文件用於記錄父層的短名稱,work目錄用於聯合掛載指定的工作目錄。而這些目錄和鏡像的關系是怎么組織在的一起呢?答案是通過元數據關聯。元數據分為image元數據和layer元數據。
image元數據
鏡像元數據存儲在了/var/lib/docker/image/<storage_driver>/imagedb/content/sha256/目錄下,名稱是以鏡像ID命名的文件,鏡像ID可通過docker images查看,這些文件以json的形式保存了該鏡像的rootfs信息、鏡像創建時間、構建歷史信息、所用容器、包括啟動的Entrypoint和CMD等等。例如ubuntu鏡像的id為47b19964fb50:
查看其對應的元數據(使用vim :%!python -m json.tool格式化成json) 截取了其rootfs的構成:
上面的 diff_id 對應的的是一個鏡像層,其排列也是有順序的,從上到下依次表示鏡像層的最低層到最頂層:
diff_id如何關聯進行層?具體說來,docker 利用 rootfs 中的每個diff_id 和歷史信息計算出與之對應的內容尋址的索引(chainID) ,而chaiID則關聯了layer層,進而關聯到每一個鏡像層的鏡像文件。
layer元數據

每個chainID目錄下會存在三個文件cache-id、diff、zize:
cache-id文件:
docker隨機生成的uuid,內容是保存鏡像層的目錄索引,也就是/var/lib/docker/overlay2/中的目錄,這就是為什么通過chainID能找到對應的layer目錄。以chainID為d801a12f6af7beff367268f99607376584d8b2da656dcd8656973b7ad9779ab4 對應的目錄為 130ea10d6f0ebfafc8ca260992c8d0bef63a1b5ca3a7d51a5cd1b1031d23efd5,也就保存在/var/lib/docker/overlay2/130ea10d6f0ebfafc8ca260992c8d0bef63a1b5ca3a7d51a5cd1b1031d23efd5
diff文件:
保存了鏡像元數據中的diff_id(與元數據中的diff_ids中的uuid對應)
size文件:
保存了鏡像層的大小
- 如果該鏡像層是最底層(沒有父鏡像層),該層的 diffID 便是 chainID。
- 該鏡像層的 chainID 計算公式為 chainID(n)=SHA256(chain(n-1) diffID(n)),也就是根據父鏡像層的 chainID 加上一個空格和當前層的 diffID,再計算 SHA256 校驗碼。
查看其對應的mountedLayer三個文件:
可以看到initID是在mountID后加了一個-init,同時initID就是存儲在/var/lib/docker/overlay2/的目錄名稱:
查看mountID還可以直接通過mount命令查看對應掛載的mountID,對應着/var/lib/docker/overlay2/目錄,這也是overlayfs呈現的merged目錄:
在容器中創建了一文件:
此時到宿主的merged目錄就能看到對應的文件:
關於init層
小結
- 鏡像層:也稱為rootfs,提供容器啟動的文件系統
- init層: 用於修改容器中一些文件如/etc/hostname、/etc/resolv.conf等
- 容器層:使用聯合掛載統一給用戶提供的可讀寫目錄。

四、總結
本文介紹了以overlayfs作為存儲驅動的的鏡像存儲原理其中每層的鏡像數據保存在/var/lib/docker/overlay2/<uuid>/diff目錄下,init層數據保存了在 /var/lib/docker/overlay2/<init-id>/diff目錄下,最后統一視圖(容器層)數據在 /var/lib/docker/overlay2/<mount_id>/diff目錄下,docker通過image元數據和layer元數據利用內容尋址(chainID)將這些目錄組織起來構成容器所運行的文件系統。
參考: