從 GraphDriver 開始看#
當 container 啟動後,可以用 docker inspect 看 runtime 配給它的儲存目錄結構:
docker run -d --name demo alpine:3.19 sleep 3600
docker inspect -f '{{json .GraphDriver.Data}}' demo預期可以看到四個 key:
LowerDir:image 的所有 read-only layer,以冒號串接(最頂層在最前)UpperDir:container 啟動後新增的可寫層,所有寫入都會落在這裡MergedDir:上面兩者透過 OverlayFS 聯合掛載出來的「container 看到的根檔案系統」WorkDir:OverlayFS 自己用的暫存目錄(atomic rename 等需要)
container 內部的 / 對應的就是 MergedDir。從 host 直接 ls 那個目錄,會看到完整的檔案樹。
在大多數 Linux 安裝中,這些目錄位於
/var/lib/docker/overlay2/<id>/之下。Docker Desktop(Mac/Windows)由於跑在 VM 內,host 上看不到,需要進到 VM 才能觀察。
UpperDir 與 LowerDir 的對比#
幾個值得記住的對應關係:
- 在 container 內讀檔:OverlayFS 從 MergedDir 讀,命中 LowerDir 或 UpperDir 都行
- 在 container 內寫檔:先把檔案從 LowerDir 拷貝一份到 UpperDir(Copy-On-Write),再對 UpperDir 的副本寫入
- 在 container 內刪檔:UpperDir 會新增一個 whiteout 標記,告訴 OverlayFS「這個檔案在合併視圖中要視為不存在」
- LowerDir 永遠不會被改寫,這是 image 可以被多 container 共用的前提
修改 UpperDir 觀察行為#
來做一個小實驗(需要 root 權限觀察 host 上的目錄):
docker run -d --name demo alpine:3.19 sleep 3600
docker exec demo sh -c 'echo "from container" > /a.txt'
# 取得 UpperDir
UPPER=$(docker inspect -f '{{.GraphDriver.Data.UpperDir}}' demo)
ls "$UPPER"
cat "$UPPER/a.txt"預期觀察:
- UpperDir 下會出現
a.txt,內容就是 container 寫入的字串 - 在 host 上修改 UpperDir 內這個檔案,container 內
cat /a.txt也會跟著變(因為它們其實是同一個 inode,OverlayFS 只是把它「呈現」在合併檔案系統的/a.txt) - 如果在 host 上把 UpperDir 的某個檔案刪除,container 內也會看不到
直接動 UpperDir 是「打開引擎蓋」的觀察手段,正式環境不要這樣操作;OverlayFS 對某些操作有原子性要求,繞過 runtime 直接寫可能造成不一致。
把 UpperDir 凍結成新 image:commit#
實驗到一個段落,可以把目前的可寫層固化成新 image:
docker commit demo demo:after-experiment
docker history demo:after-experiment
docker inspect -f '{{json .RootFS.Layers}}' demo:after-experiment預期可觀察:
RootFS.Layers會比原本 alpine 多一層- 多出來的那層 SHA256,對應的就是「UpperDir 在 commit 當下的內容」
- 拿這個新 image 起 container,會是一個全新的 UpperDir(空的),原本 UpperDir 的內容已經沉到 LowerDir 裡,變成 read-only
這個流程把抽象的 layer 概念變得具體:commit = 把 UpperDir 凍結 → 生成一個新的 read-only layer → 加到 LowerDir 鏈的最頂端。
LowerDir 的順序為什麼重要#
LowerDir 是用冒號串接的多個目錄,順序代表「優先級」:愈左邊的層愈接近最終視圖的「上面」。當同樣路徑的檔案在多層存在時,OverlayFS 取最上面那層的版本。這也是「後加入的 layer 可以覆蓋早期 layer 中的檔案」的實作機制。
從實驗回到觀念#
整個實驗串起來的觀念是:
- container 的根檔案系統不是真正存在某一處的「一份檔案樹」,而是多層目錄聯合掛載出來的視圖
- 寫入永遠落在最上層(UpperDir),底層 image 永遠安全
- commit 把這個「最上層」物化成 image 的一層
下一節進到 OverlayFS 本身,看 Linux kernel 是怎麼把這件事實作出來的。
延伸閱讀#
- Docker 官方文件:Use the OverlayFS storage driver ↗
- Linux kernel 文件:Overlay Filesystem ↗