
我們直接進入正題。這個“UnionFS VS OverlayFS一”的標(biāo)題顯然是個系列開篇。在Linux容器、鏡像分發(fā)、嵌入式系統(tǒng)以及LiveCD這些場景里Union掛載是個繞不開的話題而UnionFS和OverlayFS恰恰對應(yīng)了這條技術(shù)路線里“曾經(jīng)的經(jīng)典方案”和“當(dāng)前的事實標(biāo)準(zhǔn)”。這篇我先把兩套方案的實現(xiàn)思路、核心差異和OverlayFS的落地操作完整鋪開系列后面再做深入源碼和行為邊界層面的東西。不管你是剛接觸容器存儲驅(qū)動還是正在折騰自研鏡像系統(tǒng)這篇都適合當(dāng)一份比較完整的對照手冊來讀。我一直認為搞明白UnionFS和OverlayFS的對比本質(zhì)上不是“誰比誰強”這么簡單而是理解Linux內(nèi)核在“多目錄合并視圖”這個需求上怎么一步步從復(fù)雜走向簡潔從用戶態(tài)模塊走向內(nèi)核原生支持。文章會從背景動機、原理路徑、實操掛載、特性邊界、問題排查這幾個維度展開盡量把每個關(guān)鍵選擇背后的“為什么”也講清楚。1. 背景Union掛載要解決的核心問題1.1 為什么需要聯(lián)合文件系統(tǒng)多個目錄掛載到同一個掛載點并且對外呈現(xiàn)為一個合并后的單一視圖這就是Union掛載的直觀定義。聽起來好像很簡單但真正做起來遠比想象中麻煩因為文件系統(tǒng)的語義遠比“能看到哪些文件”復(fù)雜得多。最早的強需求來自LiveCD和Linux發(fā)行版的安裝器一張只讀光盤作為基礎(chǔ)系統(tǒng)一個可寫的內(nèi)存臨時目錄作為用戶數(shù)據(jù)層兩者合并成一個看起來完整的根文件系統(tǒng)。用戶對系統(tǒng)做的任何修改都寫到臨時目錄里而光盤內(nèi)容始終保持只讀。這樣系統(tǒng)既擁有了完整的軟件環(huán)境又不需要把整張光盤復(fù)制到內(nèi)存盤上。這種設(shè)計天然就是分層思想的雛形。容器場景進一步放大了這個需求。Docker鏡像的每一層都是只讀的容器啟動時需要一個可寫層疊加在只讀鏡像層之上并且對容器內(nèi)部進程來說它看到的應(yīng)該是一個完整的、連續(xù)的根文件系統(tǒng)它甚至感知不到底層有幾個只讀層。這個需求如果不用Union掛載就只能“把鏡像層全部復(fù)制一份再合并”代價極其高昂。UnionFS和OverlayFS本質(zhì)上都是為了解決“多個只讀層一個可寫層如何組成單一視圖”這個存儲問題而生的。1.2 從UnionFS到OverlayFS一段技術(shù)演進的必然UnionFS這個名字有兩層含義。狹義上它特指由Professor Erez Zadok團隊維護的那個歷史悠久的Linux文件系統(tǒng)項目廣義上它代表“文件系統(tǒng)聯(lián)合掛載”這一類技術(shù)。后來出現(xiàn)的UnionFS-NG是它的后繼實驗版本再往后還有AuFSAnother UnionFSDocker早期版本就是靠AuFS實現(xiàn)了鏡像分層那段歷史很多老容器工程師都印象深刻。但AuFS始終沒有進入Linux內(nèi)核主線一直作為外部補丁存在這讓發(fā)行版和容器項目都很頭疼內(nèi)核一升級補丁就得跟著適配穩(wěn)定性完全看維護者的精力。OverlayFS從另一條路走了過來——它被直接合入Linux內(nèi)核從內(nèi)核3.18開始提供初始能力并逐步演進。內(nèi)核原生的優(yōu)勢是決定性的不用維護外部模塊、隨內(nèi)核發(fā)布、社區(qū)持續(xù)打磨。OverlayFS最終取代AuFS成為容器存儲驅(qū)動事實標(biāo)準(zhǔn)這背后不是簡單的性能對比而是生態(tài)和可維護性的全面碾壓。2. 原理拆解兩條不同的技術(shù)實現(xiàn)路徑2.1 UnionFS的設(shè)計思路與關(guān)鍵機制UnionFS的原始設(shè)計非?!皩W(xué)術(shù)化”它追求的是功能完備把多個目錄通過堆疊stacking的方式合并并且對每一個成員目錄提供完全一致的POSIX語義支持。為了做到這一點UnionFS需要維護非常復(fù)雜的目錄項映射關(guān)系記住每個文件來自哪個分支、哪個層需要在內(nèi)存里建立一棵合并后的目錄樹并且實時追蹤各個分支的變化。這種全功能設(shè)計帶來了一個致命代價復(fù)雜度太高。文件查找、重命名、刪除、屬性修改每個操作都要跨越多個分支做協(xié)調(diào)大量輔助數(shù)據(jù)結(jié)構(gòu)占內(nèi)存不說很多邊界情況處理容易出現(xiàn)不一致。UnionFS在本地測試?yán)镄阅苌锌傻坏┟鎸Σl(fā)訪問、大量小文件操作、跨層重命名鎖競爭和路徑解析開銷立刻暴露出來。技術(shù)上它的兩個代表性機制是分支優(yōu)先級branch優(yōu)先級數(shù)值越小優(yōu)先級越高和寫時復(fù)制Copy-Up。文件寫入時如果目標(biāo)文件位于低優(yōu)先級只讀分支UnionFS會先將整個文件復(fù)制到高優(yōu)先級可寫分支再對副本執(zhí)行修改操作。寫時復(fù)制聽起來很美但注意是“整個文件”復(fù)制不是按塊復(fù)制這在后面OverlayFS的演進里仍是核心話題之一。2.2 OverlayFS的設(shè)計思路與關(guān)鍵機制OverlayFS的設(shè)計哲學(xué)在源碼注釋里就寫得很直白它不追求“在語義上完全等價于一個普通文件系統(tǒng)”而是“在絕大多數(shù)場景下提供足夠好用的合并視圖”。它把組成成員稱為層layer最底層叫l(wèi)owerdir底層目錄最上層可寫目錄叫upperdir上層目錄合并后的視圖叫merged目錄合并目錄。結(jié)構(gòu)上它比UnionFS簡單得多只區(qū)分“可寫的upper”和“只讀的lower”不需要維護多分支的復(fù)雜優(yōu)先級關(guān)系。OverlayFS的寫時復(fù)制策略更務(wù)實當(dāng)進程對lower層文件發(fā)起修改操作時OverlayFS把該文件完整復(fù)制到upper層然后所有后續(xù)修改都發(fā)生在upper層的副本上。原文件在lower層的inode保持不變。這個策略在大部分容器場景里是合理的因為容器鏡像層的文件極少被修改絕大多數(shù)操作是“新建文件”和“讀取文件”真正觸發(fā)Copy-Up的次數(shù)少之又少。讀取路徑是OverlayFS的另一個精髓。它不像UnionFS那樣為整個合并視圖建立獨立的目錄樹緩存而是直接在路徑查找時從上到下依次在upper、lower層中查找。每層本身都是真實存在的文件系統(tǒng)VFS的dcache目錄項緩存可以正常作用于各層內(nèi)部真正實現(xiàn)了“各層是自己的文件系統(tǒng)OverlayFS只是組合它們的邏輯”。2.3 兩者核心差異對照對比維度UnionFSOverlayFS內(nèi)核集成外部模塊/補丁未進入主線內(nèi)核原生3.18引入隨內(nèi)核演進分層數(shù)量限制支持多分支數(shù)量可配置lowerdir支持多層可多個upperdir單層合并視圖緩存維護獨立的目錄映射結(jié)構(gòu)不維護全局映射依賴各層自身dcache寫時復(fù)制范圍整個文件復(fù)制到高優(yōu)先級分支整個文件復(fù)制到upper層執(zhí)行語義盡量完整模擬POSIX接受部分語義差異換取性能與穩(wěn)定主要歷史角色推動了聯(lián)合文件系統(tǒng)研究成為容器和Linux發(fā)行版的默認方案這張表里最值得關(guān)注的是“執(zhí)行語義”這一行。OverlayFS明確接受了一些非常規(guī)行為比如rename/delete在層間操作時可能有特殊語義這在后續(xù)的常見問題部分會具體講到。3. 實操在Linux上掛載OverlayFS3.1 準(zhǔn)備目錄結(jié)構(gòu)紙上談兵沒有意義直接把環(huán)境搭起來驗證一遍最實在。我建議你用一臺Linux虛擬機或者任意云主機來做內(nèi)核版本別太老至少4.x以上推薦5.15以上因為新內(nèi)核補了很多overlay的edge case?;A(chǔ)環(huán)境準(zhǔn)備如下mkdir -p /tmp/overlay-test/{lower,upper,work,merged} echo hello from lower layer /tmp/overlay-test/lower/hello.txt echo lower version /tmp/overlay-test/lower/config.txt echo upper version /tmp/overlay-test/upper/config.txt我在這里故意制造了一個同名文件場景config.txt同時存在于lower和upper層mount之后你就能直觀看到上層覆蓋下層的規(guī)則。work目錄是overlay工作目錄它不能和upper、lower、merged重疊這是硬性要求稍后解釋。3.2 掛載命令與參數(shù)詳解用最標(biāo)準(zhǔn)的方式執(zhí)行掛載mount -t overlay overlay -o lowerdir/tmp/overlay-test/lower,upperdir/tmp/overlay-test/upper,workdir/tmp/overlay-test/work /tmp/overlay-test/merged掛載之后查看合并目錄ls /tmp/overlay-test/merged/ cat /tmp/overlay-test/merged/config.txt正常情況下config.txt輸出的是upper version說明高優(yōu)先級層覆蓋低優(yōu)先級層。你還會看到hello.txt這就是lower層文件出現(xiàn)在合并視圖里的證據(jù)。關(guān)于參數(shù)的幾個關(guān)鍵點我補充一下lowerdir支持多個目錄用冒號分隔例如lowerdir/lower1:/lower2:/lower3越靠前的目錄優(yōu)先級越高。這個順序容易和直覺相反初始設(shè)置時建議反復(fù)確認。upperdir同時存在同名文件與目錄時upper層勝出。合并規(guī)則是upper有就用upper的upper沒有才在lower里找。workdir必須和upperdir在同一個文件系統(tǒng)上這是內(nèi)核為了保證Copy-Up操作原子性做的硬限制。如果你試圖把work放在別的文件系統(tǒng)mount會直接報Invalid argument。merged掛載點自身也可以是普通目錄不需要預(yù)先為空但掛載后原目錄內(nèi)容會被隱藏類似普通mount覆蓋行為。3.3 驗證Copy-Up行為掛載只是開始真正有趣的是看看寫時復(fù)制到底怎么運作。先查看lower和upper層的文件inode信息stat -c %i %h %s /tmp/overlay-test/lower/hello.txt stat -c %i %h %s /tmp/overlay-test/merged/hello.txt這時兩者inode相同因為還沒有任何寫入merged里的hello.txt本質(zhì)上就是lower文件的引用。現(xiàn)在通過merged目錄修改這個文件echo modified via merged /tmp/overlay-test/merged/hello.txt這時再看stat -c %i %h %s /tmp/overlay-test/lower/hello.txt stat -c %i %h %s /tmp/overlay-test/merged/hello.txt ls -l /tmp/overlay-test/upper/你會看到upper目錄里出現(xiàn)了一個新的hello.txt內(nèi)容和merged里一致但inode和lower里的完全不同。這就是Copy-Up的完整過程修改觸發(fā)復(fù)制復(fù)制發(fā)生在底層然后修改作用于副本。這里有個值得思考的細節(jié)文件被復(fù)制到upper層之后它是作為一個全新文件存在的。如果lower層的原始文件后來被外部程序修改merged視圖里不會再看到這個變化因為upper層的副本已經(jīng)“遮蔽”了lower層原文件。這就是為什么容器鏡像層不可變才是分層賴以生存的基礎(chǔ)——一旦lower層發(fā)生變化視圖一致性就無法保證。這也是我在生產(chǎn)環(huán)境里堅持“鏡像層只增不改”的原因。4. 特性擴展與內(nèi)核參數(shù)邊界4.1 掛載選項index、redirect_dir與metacopyOverlayFS遠不止一個簡單的合并掛載能力。從內(nèi)核4.x開始一系列掛載選項讓它的行為更加精細化理解這些選項對生產(chǎn)環(huán)境選型至關(guān)重要。indexon啟用索引特性為每個復(fù)制到upper層的文件建立索引防止硬鏈接在層間復(fù)制后產(chǎn)生不一致。Docker的overlay2驅(qū)動默認就依賴這個特性來保證鏡像層共享時的硬鏈接安全。開啟方式是在掛載選項里加indexonmount -t overlay overlay -o lowerdir...,upperdir...,workdir...,indexon /merged。注意index開啟后upper目錄里會多出一個#index目錄這是內(nèi)核管理索引用的不要手動去動它。redirect_diron允許重定向目錄。沒有這個選項時如果rename一個目錄OverlayFS需要復(fù)制整個目錄樹到upper層代價非常大。開啟后內(nèi)核可以創(chuàng)建一個“重定向”的目錄項指向原目錄在lower層的位置這樣rename操作就變成了輕量級的元數(shù)據(jù)修改。但這個選項帶來的語義復(fù)雜度和性能問題是長期爭議點很多生產(chǎn)環(huán)境為了穩(wěn)定寧愿關(guān)掉它。metacopyon這是OverlayFS近幾個內(nèi)核版本里一個很強的優(yōu)化特性。開啟后Copy-Up不再復(fù)制整個文件內(nèi)容而是只復(fù)制文件的元數(shù)據(jù)比如屬主、權(quán)限、大小到upper層并設(shè)置一個特殊標(biāo)志。當(dāng)進程真正執(zhí)行寫操作時再觸發(fā)完整的數(shù)據(jù)復(fù)制。它極大地優(yōu)化了“低頻元數(shù)據(jù)變更”場景比如chmod、chown不需要把巨大的數(shù)據(jù)文件復(fù)制一遍。代價是讀取文件時多了一層間接性并且一些需要物理讀取文件內(nèi)容的操作如mmap可能會繞過優(yōu)化路徑。4.2 掛載選項對行為的影響選項開啟后的變化風(fēng)險與注意點indexon避免硬鏈接層間復(fù)制問題upper層多出索引目錄不能隨意刪除redirect_diron目錄rename操作輕量化語義復(fù)雜部分場景影響NFS導(dǎo)出metacopyon元數(shù)據(jù)變更不觸發(fā)數(shù)據(jù)復(fù)制數(shù)據(jù)讀取多一層間接可能干擾部分監(jiān)控工具volatilemount狀態(tài)變化不持久化重啟即丟僅適合可重建場景生產(chǎn)慎用volatile是另一個需要單獨說明的掛載選項它告訴OverlayFS所有upper層的修改在系統(tǒng)重啟后可以丟失。這聽起來很可怕但在某些場景下非常有用比如系統(tǒng)做OTA升級時臨時數(shù)據(jù)層不要求持久化重啟后重新構(gòu)建即可。開啟方式為volatile掛載選項但務(wù)必要知道它的代價是“非正常關(guān)機可能連正常同步都沒做全”。這些特性疊加之后OverlayFS的能力已經(jīng)不是“簡單的層疊合并”能概括的了。選型時我建議的原則是能默認就默認只在遇到具體性能瓶頸時用最小范圍的選項變更去試錯一次只改一個選項觀察足夠長的時間再下結(jié)論。5. 常見問題與排查實錄5.1 Copy-Up引發(fā)的性能陷阱OverlayFS最容易被詬病的就是Copy-Up性能。明明只修改了一個字節(jié)為什么耗時和復(fù)制整個文件一樣因為OverlayFS拷貝的粒度就是“整個文件”不是按塊或按字節(jié)。如果一個容器鏡像里有大的日志文件或數(shù)據(jù)庫文件應(yīng)用每次追加寫入都會把整個文件從lower復(fù)制到upper表現(xiàn)就是單次寫入慢得離譜。排查這類問題有個很實用的技巧用perf或者bcc追蹤文件系統(tǒng)層級的overlay_copy_up事件可以快速確認寫入是否觸發(fā)了Copy-Up。我遇到過一個真實案例一個應(yīng)用反復(fù)打開一個巨大的二進制模型文件進行元數(shù)據(jù)修改metacopy未開啟時每次操作都導(dǎo)致幾十MB的完整復(fù)制應(yīng)用啟動時間被拖慢到分鐘級。開啟metacopy后啟動時間銳減到幾秒。遇到Copy-Up性能瓶頸時的應(yīng)對思路把頻繁寫入的文件提前放到upper層避免運行時Copy-Up。使用metacopyon降低元數(shù)據(jù)修改的場景開銷。從設(shè)計上避免修改鏡像層內(nèi)的大文件把可寫數(shù)據(jù)獨立掛載到單獨卷。5.2 磁盤空間統(tǒng)計為何“不對”在merged目錄里執(zhí)行df -h看到的容量統(tǒng)計通常來自upperdir所在文件系統(tǒng)而不是整個合并視圖的總?cè)萘?。這在容器場景里就表現(xiàn)為容器內(nèi)看到根文件系統(tǒng)容量很大但實際可寫層很小寫入稍微多一下就報No space left on device很讓人困惑。為什么會這樣因為overlay并不真正分配自己的存儲空間merged視圖里的所有數(shù)據(jù)都實際存儲在upper或lower所在的底層文件系統(tǒng)上。df只能反映某個掛載點的物理存儲信息overlay的掛載點并沒有自己的塊分配表內(nèi)核就“從上往下”報告了upperdir所在文件系統(tǒng)的容量。Docker早期采用overlay驅(qū)動時就踩過這個坑容器內(nèi)df看到宿主機磁盤大小但可寫層配額由宿主機限制一旦寫滿就報錯?,F(xiàn)在Docker用pids和storage配額配合解決這個問題但概念上依然要認清overlay的容量不是它自己的而是upper層的容量。如果要查看overlay中某個文件到底占了哪一層的空間可以使用toybox或busybox里的du配合ls -l做判斷觀察文件的inode變化和目錄層級關(guān)系即可。生產(chǎn)環(huán)境里我建議監(jiān)控Upper目錄的實際用量而不是merged視圖里的統(tǒng)計值。5.3 lower層被外部修改導(dǎo)致的不一致OverlayFS的一個基本假設(shè)是lower層在掛載期間保持穩(wěn)定不能隨意修改。但在實踐中經(jīng)常有人掛載后直接改lower目錄里的文件導(dǎo)致merged視圖出現(xiàn)各種詭異行為文件內(nèi)容一半新一半舊inode匹配錯亂甚至某些文件在merged中“消失”。這類問題排查起來非常隱蔽因為VFS層看到的是OverlayFS維護的內(nèi)部狀態(tài)底層inode變化后overlay的dentry可能已經(jīng)失效或者緩存不匹配。應(yīng)對辦法只有兩個嚴(yán)格要求所有寫操作走merged視圖經(jīng)overlay的Copy-Up邏輯處理。如果確實需要改lower內(nèi)容必須先卸載overlay掛載點修改完后再重新掛載。在容器環(huán)境下這基本就意味著重建容器或鏡像層。我在本地測試時就遇到過類似場景我直接在lower里替換了一個配置文件隨后merged里讀出的文件內(nèi)容正確但stat顯示的鏈接數(shù)不對而echo重定向?qū)懭胫髐pper里新建了文件lower里的舊文件卻還殘留。這種“半更新”狀態(tài)是所有聯(lián)合文件系統(tǒng)的大忌生產(chǎn)環(huán)境一定不要這么用。5.4 文件刪除為何不釋放lower層空間另一個高頻困惑是在merged里刪除一個原本位于lower層的文件磁盤空間卻沒有釋放。原因很簡單——刪除操作在OverlayFS里是通過whiteout白色占位符實現(xiàn)的它在上層創(chuàng)建一個特殊標(biāo)記表示“這個文件名已刪除”而不是真的把lower層的文件刪除。這個whiteout機制的具體表現(xiàn)是在upper目錄里你會看到一個字符設(shè)備文件它的設(shè)備號為0/0或者在一個普通目錄上設(shè)置了trusted.overlay.whiteout這些擴展屬性。這在容器場景下其實很正常Docker刪除容器內(nèi)的文件只是把該文件標(biāo)記為已刪除鏡像層里的原始數(shù)據(jù)依然在所以鏡像不會因為容器內(nèi)刪除操作而變小。鏡像大小只會隨著鏡像層本身的重建而變化。如果你真的需要釋放空間辦法是把構(gòu)建鏡像時的RUN指令改為在同一層里處理或者用docker export重新打扁平鏡像。但要注意這并不屬于OverlayFS本身能解決的問題它的語義就是“上層屏蔽下層”不是“上層銷毀下層”。6. 給初學(xué)者的選型建議與個人體會先給個直接結(jié)論如果你的場景是容器、云原生、系統(tǒng)鏡像疊加這類主流需求直接選OverlayFS別再用UnionFS系列或者自己折騰用戶態(tài)聯(lián)合掛載方案。理由很簡單清晰內(nèi)核支持、默認集成、大型社區(qū)驗證這些都是硬指標(biāo)。如果你在做嵌入式或硬件受限的環(huán)境需要評估的其實是overlay的性能開銷和內(nèi)存占用是否可接受。實測來看overlay的靜態(tài)內(nèi)存開銷非常小主要消耗集中在Copy-Up瞬間的臨時I/O緩沖區(qū)上正常工況下不會有太大問題。相比自己用bind mount或者FUSE方案解決overlay的穩(wěn)定性和性能都要好很多。我個人在實際操作中最深的體會是OverlayFS的“保持一致”遠比“追求性能”重要。很多人一上來就琢磨metacopy、redirect_dir這些優(yōu)化選項卻忽略了掛載層穩(wěn)定性的前提條件最終導(dǎo)致線上出問題。建議新接觸的朋友先按最淳樸的掛載方式跑通全部實驗再逐項打開特性理解每個選項改變了什么再去談優(yōu)化。另外一個實用心得是排查overlay問題時別只盯著內(nèi)核日志先檢查掛載參數(shù)和掛載狀態(tài)。很多時候dmesg里根本沒有錯誤信息問題就是lower層被動了、路徑選錯了、或者workdir和upperdir跨文件系統(tǒng)了。仔細檢查/proc/mounts里的掛載選項往往比盲目調(diào)試更高效。這個系列的第一篇到這里算是對UnionFS和OverlayFS的背景、原理和基礎(chǔ)操作給了個全景。后續(xù)我會繼續(xù)把這個話題往深處推OverlayFS的inode復(fù)用機制、與頁緩存的交互行為、NFS導(dǎo)出場景下的特性支持、以及Docker/containerd存儲驅(qū)動選型背后的完整邏輯。有興趣的可以先把本篇的實驗操作做一遍親手看看Copy-Up的表現(xiàn)和掛載參數(shù)的變化后面聊到行為細節(jié)時你會理解得更快。