為什麼需要寫時複製#

Image 的 layer 是 read-only,且被多個 container 共用。如果 container 想修改某個檔案,不可能直接動 layer 裡的內容(會污染其他 container),也不能在啟動時就把整個檔案系統拷貝一份(成本太高、啟動會變慢)。

OverlayFS 採用的方案是寫時複製(Copy-On-Write, COW):只有真的要寫了,才把那個檔案從 LowerDir 拷貝到 UpperDir,然後對 UpperDir 的副本寫入。沒被改過的檔案永遠停在 LowerDir。

COW 的精神是「延後成本」:能不複製就不複製,直到非複製不可的那一刻。這在很多系統都會看到(fork 的記憶體頁、ZFS 的快照)。

一次寫入發生的事#

假設 container 想對 /etc/hosts 寫入一行:

  • OverlayFS 查 MergedDir,發現 /etc/hosts 來自 LowerDir
  • 因為要寫,先把整個檔案從 LowerDir 拷貝到 UpperDir 的對應路徑
  • 寫入操作落在 UpperDir 的副本上
  • 從此 container 看到的 /etc/hosts 是 UpperDir 那份;LowerDir 的原始檔案不變

這個複製是「整個檔案」為單位的,不是 block 級。所以對大檔案的小修改,會付出整檔複製的成本。實務上要避免在 container 內對 image 內的大檔案做局部寫入;改用 volume 或 bind mount 是常見做法。

Whiteout:「我刪掉了」要怎麼跨層表達#

Container 內 rm /a.txt 要怎麼處理?UpperDir 不能去動 LowerDir 的檔案,但合併視圖中 /a.txt 又必須消失。OverlayFS 用 whiteout 表達這件事:

  • 在 UpperDir 對應路徑放一個特殊標記(在 OverlayFS 中是一個 character device,major/minor 都是 0)
  • OverlayFS 在合併時看到 whiteout,就把這個路徑視為「不存在」,即使 LowerDir 仍有檔案
  • LowerDir 的原始檔案紋絲不動

這也解釋了一個常見的 Dockerfile 陷阱:

COPY secret.key /tmp/secret.key
RUN ./use-secret.sh && rm /tmp/secret.key

第一行已經把 secret.key 寫進一層 layer。第二行的 rm 只是在更上一層放 whiteout,原本那層的 secret.key 仍存在於 image 中,任何拿到 image 的人都能解出來。要避免這個問題,必須讓 COPY 與 rm 在「同一個 RUN」內完成,或用 multi-stage build 把祕密留在中間階段。

目錄的刪除:opaque 標記#

刪除一個目錄比刪檔案更微妙。如果只是放 whiteout,OverlayFS 會把該目錄整個遮掉沒問題;但若刪除後又在同名路徑建立新目錄,且不希望看到 LowerDir 中該目錄的舊內容,就需要「opaque directory」標記:

  • UpperDir 中該目錄存在
  • 該目錄帶有 trusted.overlay.opaque 擴展屬性,告訴 OverlayFS:合併時不要再 merge LowerDir 中的同名目錄

這是 OverlayFS 內部細節,一般使用者不需要直接操作,但理解它有助於解釋「為什麼某次刪除後重建,舊內容還是冒出來/不冒出來」這類詭異現象。

Rename 的跨層行為#

rename(2) 在傳統檔案系統中是原子操作,但在 OverlayFS 跨層時就麻煩了:

  • 來源在 LowerDir、目標在 UpperDir:OverlayFS 必須先做 copy-up,再 rename 到目標位置,最後在來源放 whiteout
  • 操作不再是單一 syscall 的原子動作;中途若失敗,可能留下半完成狀態
  • 為了維持原子性,OverlayFS 引入了 redirect_dir 等機制(kernel 較新版本支援)

實務影響:在 container 內對來自 image 的目錄做 mv,效能可能比想像中差,且某些工具(例如預期 rename 是 inode-preserving 的)會看到非預期的結果。

跨層的刪除總結#

把跨層刪除/修改的狀態列成表:

操作LowerDir 變化UpperDir 變化MergedDir 視圖
讀檔取最上層存在的版本
改檔拷貝 + 寫入顯示 UpperDir 版本
刪檔新增 whiteout該路徑消失
刪目錄whiteout 或 opaque dir該目錄消失
新增直接寫入顯示 UpperDir 版本

整個機制的不變式是「LowerDir 永遠不變」。所有「修改」與「刪除」都是 UpperDir 上的記錄,OverlayFS 在合併視圖時把它們表現出來。

延伸閱讀#