配置與管理:從原理到實(shí)戰(zhàn)的避坑指南)
容器跑得穩(wěn)不穩(wěn)一半看鏡像另一半看存儲(chǔ)。我見過太多團(tuán)隊(duì)把容器編排玩得飛起結(jié)果日志把磁盤寫爆、數(shù)據(jù)庫容器重啟后數(shù)據(jù)蒸發(fā)、多個(gè)容器搶寫同一個(gè)目錄導(dǎo)致文件損壞。這些問題根子上都不是容器技術(shù)本身的問題而是Docker存儲(chǔ)配置與管理沒做扎實(shí)。這個(gè)主題聽起來像是運(yùn)維文檔里的老生常談但真正踩過坑的人都知道存儲(chǔ)這塊的細(xì)節(jié)密度極高從分層文件系統(tǒng)到存儲(chǔ)驅(qū)動(dòng)選型從數(shù)據(jù)卷生命周期到掛載權(quán)限每一層都有能讓人熬夜的坑。這篇內(nèi)容面向已經(jīng)能把容器跑起來、但一遇到數(shù)據(jù)持久化和磁盤管理就心里沒底的朋友也適合想系統(tǒng)梳理存儲(chǔ)知識(shí)體系的從業(yè)者。我會(huì)把存儲(chǔ)驅(qū)動(dòng)原理、卷與綁定掛載的取舍、空間配額、清理策略、備份恢復(fù)這些環(huán)節(jié)拆開講透每個(gè)選擇背后為什么這么做、參數(shù)怎么算、坑在哪里都給你交代清楚。1. 先搞清楚容器存儲(chǔ)的底層邏輯再動(dòng)手1.1 分層文件系統(tǒng)到底解決了什么問題容器鏡像的本質(zhì)是一堆只讀層的疊加這是理解一切存儲(chǔ)配置的起點(diǎn)。你拉下來的一個(gè)鏡像可能是基礎(chǔ)系統(tǒng)層、運(yùn)行時(shí)層、依賴庫層、應(yīng)用代碼層依次疊上去的每層都是只讀的。容器啟動(dòng)時(shí)Docker會(huì)在最上面加一個(gè)薄薄的可寫層所有運(yùn)行時(shí)的寫操作都落在這個(gè)可寫層里。這個(gè)設(shè)計(jì)的核心價(jià)值在于鏡像層可以被無數(shù)個(gè)容器共享你起十個(gè)基于同一鏡像的容器磁盤上那份只讀數(shù)據(jù)只有一份省空間也省拉取時(shí)間。但這里有個(gè)關(guān)鍵認(rèn)知必須建立起來可寫層是跟著容器生命周期走的。容器刪了可寫層就沒了里面寫的東西全部消失。很多人第一次遇到容器重啟數(shù)據(jù)沒了的困惑根源就是沒意識(shí)到自己把數(shù)據(jù)寫進(jìn)了可寫層。可寫層還有個(gè)性能問題它依賴存儲(chǔ)驅(qū)動(dòng)來實(shí)現(xiàn)寫時(shí)復(fù)制Copy-on-Write你第一次修改某個(gè)文件時(shí)驅(qū)動(dòng)要把整個(gè)文件從只讀層復(fù)制到可寫層再改文件大的話這個(gè)復(fù)制開銷很可觀。所以數(shù)據(jù)庫這類頻繁隨機(jī)寫的負(fù)載絕對(duì)不能把數(shù)據(jù)放在可寫層里跑。分層結(jié)構(gòu)帶來的另一個(gè)隱性成本是層數(shù)。每多一層元數(shù)據(jù)管理和聯(lián)合掛載的開銷就多一點(diǎn)雖然現(xiàn)代存儲(chǔ)驅(qū)動(dòng)優(yōu)化得不錯(cuò)但構(gòu)建鏡像時(shí)合并冗余層、減少層數(shù)依然是值得堅(jiān)持的習(xí)慣。我一般建議把不常變的部分放底層頻繁變的部分放上層這樣構(gòu)建緩存命中率高運(yùn)行時(shí)層結(jié)構(gòu)也清爽。1.2 存儲(chǔ)驅(qū)動(dòng)選型的判斷依據(jù)存儲(chǔ)驅(qū)動(dòng)是分層文件系統(tǒng)的具體實(shí)現(xiàn)不同驅(qū)動(dòng)在性能、穩(wěn)定性、功能支持上差異明顯。選型不是越新越好而是要看你的宿主系統(tǒng)、文件系統(tǒng)類型和負(fù)載特征三者匹配。存儲(chǔ)驅(qū)動(dòng)典型適用場(chǎng)景關(guān)鍵特性注意事項(xiàng)overlay2絕大多數(shù)現(xiàn)代Linux發(fā)行版默認(rèn)多層合并效率高inode占用相對(duì)可控要求內(nèi)核版本較新底層文件系統(tǒng)建議ext4或xfsfuse-overlayfs無root權(quán)限場(chǎng)景、用戶態(tài)運(yùn)行不需要特權(quán)即可工作性能略低于內(nèi)核態(tài)overlay2btrfs底層已用btrfs文件系統(tǒng)原生支持快照和子卷與btrfs特性耦合深遷移受限zfs底層已用zfs數(shù)據(jù)校驗(yàn)、快照能力強(qiáng)內(nèi)存占用較高配置復(fù)雜vfs調(diào)試、兼容性兜底不做寫時(shí)復(fù)制每層完整復(fù)制性能差、占空間大僅作最后手段選型的實(shí)操判斷很簡(jiǎn)單先看宿主文件系統(tǒng)ext4或xfs上優(yōu)先overlay2這是當(dāng)前最成熟穩(wěn)妥的組合如果底層已經(jīng)是btrfs或zfs并且你確實(shí)想利用它們的快照能力再考慮對(duì)應(yīng)驅(qū)動(dòng)vfs基本只在排查詭異問題時(shí)臨時(shí)用。我踩過的一個(gè)坑是在xfs上沒開ftype1就上了overlay2結(jié)果某些操作報(bào)inode相關(guān)錯(cuò)誤后來查明白是xfs創(chuàng)建時(shí)沒啟用d_type支持只能重建文件系統(tǒng)。所以部署前用xfs_info確認(rèn)ftype1這一步別省。提示存儲(chǔ)驅(qū)動(dòng)一旦選定并產(chǎn)生數(shù)據(jù)后直接切換驅(qū)動(dòng)往往需要重建整個(gè)Docker數(shù)據(jù)目錄生產(chǎn)環(huán)境務(wù)必提前規(guī)劃別等數(shù)據(jù)堆起來再換。1.3 數(shù)據(jù)目錄規(guī)劃與磁盤布局Docker默認(rèn)把鏡像、容器、卷都放在/var/lib/docker下這個(gè)目錄會(huì)隨著使用不斷膨脹。生產(chǎn)環(huán)境我強(qiáng)烈建議把它單獨(dú)掛到一個(gè)獨(dú)立分區(qū)或獨(dú)立磁盤上理由很實(shí)在鏡像層和容器日志的增長(zhǎng)速度往往超出預(yù)期如果和系統(tǒng)盤混在一起一旦寫滿會(huì)連帶系統(tǒng)服務(wù)出問題。獨(dú)立分區(qū)的好處是即使Docker數(shù)據(jù)寫滿也只影響容器不會(huì)把整臺(tái)機(jī)器拖垮。布局上還有個(gè)細(xì)節(jié)值得說如果條件允許把鏡像存儲(chǔ)和卷存儲(chǔ)分到不同磁盤。鏡像層是讀多寫少、可重建的卷里往往是不可再生的業(yè)務(wù)數(shù)據(jù)兩者IO特征和重要性都不同。分開之后鏡像頻繁拉取刪除產(chǎn)生的IO不會(huì)干擾卷上的業(yè)務(wù)讀寫備份策略也能區(qū)別對(duì)待。當(dāng)然這需要額外硬件投入中小規(guī)模部署至少做到數(shù)據(jù)目錄獨(dú)立分區(qū)這是性價(jià)比最高的做法。2. 數(shù)據(jù)卷與綁定掛載的取舍之道2.1 兩種持久化方式的本質(zhì)區(qū)別數(shù)據(jù)卷volume和綁定掛載bind mount是Docker提供的兩種主要持久化手段很多人混著用但它們的生命周期管理、權(quán)限模型、可移植性差別很大選錯(cuò)了后期維護(hù)成本很高。數(shù)據(jù)卷由Docker管理存儲(chǔ)在數(shù)據(jù)目錄下的專用區(qū)域生命周期獨(dú)立于容器你可以顯式創(chuàng)建、命名、刪除容器刪了卷還在。綁定掛載則是把宿主機(jī)上任意路徑直接映射進(jìn)容器路徑由你自己指定Docker不參與管理。打個(gè)比方數(shù)據(jù)卷像是Docker幫你租的一個(gè)倉(cāng)庫鑰匙它管著你只管往里放東西綁定掛載像是你直接把自家某個(gè)房間的門開到容器里房間在哪、里面有什么全憑你自己維護(hù)。從可移植性看數(shù)據(jù)卷明顯占優(yōu)。用卷的容器換臺(tái)機(jī)器部署只要卷跟著遷移過去配置基本不用改綁定掛載寫死了宿主機(jī)路徑換環(huán)境就得改配置路徑不存在還會(huì)自動(dòng)創(chuàng)建空目錄容易埋下數(shù)據(jù)看起來丟了的隱患。從權(quán)限角度看綁定掛載直接沿用宿主機(jī)文件權(quán)限容器內(nèi)用戶和宿主機(jī)用戶的UID/GID映射沒處理好就會(huì)遇到寫不進(jìn)去的問題數(shù)據(jù)卷由Docker初始化時(shí)會(huì)處理權(quán)限相對(duì)省心。2.2 什么場(chǎng)景該用哪種方式判斷標(biāo)準(zhǔn)其實(shí)就一條這份數(shù)據(jù)是不是容器專屬、需不需要跟著容器走。數(shù)據(jù)庫數(shù)據(jù)、應(yīng)用上傳文件、緩存目錄這類容器專屬數(shù)據(jù)優(yōu)先用數(shù)據(jù)卷管理清晰、遷移方便。而配置文件、開發(fā)時(shí)的源碼目錄、宿主機(jī)上已有的大量數(shù)據(jù)需要讓容器讀取這類場(chǎng)景綁定掛載更合適因?yàn)槟阋木褪亲屓萜髦苯涌吹剿拗鳈C(jī)上的特定內(nèi)容。開發(fā)環(huán)境里綁定掛載源碼目錄是常見做法改代碼容器里立刻生效不用重新構(gòu)建鏡像。但要注意一個(gè)坑容器內(nèi)進(jìn)程對(duì)掛載目錄的寫權(quán)限。如果容器以非root用戶運(yùn)行而宿主機(jī)目錄屬主是root寫操作會(huì)失敗。解決辦法要么調(diào)整宿主機(jī)目錄屬主要么在啟動(dòng)時(shí)用--user指定匹配的UID。我一般推薦在開發(fā)環(huán)境用--user $(id -u):$(id -g)把當(dāng)前用戶映射進(jìn)去省去權(quán)限折騰。生產(chǎn)環(huán)境還有個(gè)折中方案值得提命名卷配合初始化容器。用命名卷保證數(shù)據(jù)持久化和可移植用一個(gè)一次性容器在卷首次使用時(shí)把默認(rèn)配置或初始數(shù)據(jù)寫進(jìn)去主容器啟動(dòng)時(shí)卷里已經(jīng)有內(nèi)容了。這樣既享受了卷的管理便利又解決了空卷啟動(dòng)應(yīng)用報(bào)錯(cuò)的問題。2.3 卷的創(chuàng)建、掛載與生命周期管理卷的操作命令不多但每個(gè)都有講究。創(chuàng)建命名卷用docker volume create 卷名不指定驅(qū)動(dòng)默認(rèn)用local驅(qū)動(dòng)數(shù)據(jù)落在數(shù)據(jù)目錄的volumes子目錄下。掛載時(shí)用-v 卷名:容器內(nèi)路徑或更明確的--mount typevolume,source卷名,target容器內(nèi)路徑。這兩種寫法有區(qū)別-v在卷不存在時(shí)會(huì)自動(dòng)創(chuàng)建--mount則要求卷必須已存在后者更嚴(yán)謹(jǐn)生產(chǎn)腳本里我傾向用--mount避免拼寫錯(cuò)誤悄悄創(chuàng)建出一個(gè)新空卷。卷的生命周期管理要養(yǎng)成習(xí)慣。docker volume ls看列表docker volume inspect 卷名看詳情包括實(shí)際存儲(chǔ)路徑和掛載點(diǎn)docker volume rm 卷名刪除。這里有個(gè)高頻坑正在被容器使用的卷刪不掉會(huì)報(bào)錯(cuò)提示卷正在使用中。這是保護(hù)機(jī)制別想著強(qiáng)刪正確做法是先停掉并刪除使用該卷的容器再刪卷。還有個(gè)更隱蔽的坑docker volume prune會(huì)刪除所有未被任何容器引用的卷如果某個(gè)卷你暫時(shí)沒掛載但里面存著重要數(shù)據(jù)一執(zhí)行就沒了。所以prune類命令執(zhí)行前務(wù)必先docker volume ls確認(rèn)沒有游離的重要卷。注意匿名卷啟動(dòng)時(shí)只寫容器內(nèi)路徑不指定卷名自動(dòng)生成的卷最容易變成孤兒容器刪了卷還在但沒人記得它是什么。生產(chǎn)環(huán)境盡量用命名卷匿名卷只適合臨時(shí)測(cè)試。3. 空間配額與磁盤占用控制實(shí)戰(zhàn)3.1 容器可寫層的空間限制容器可寫層不設(shè)限的話一個(gè)跑飛的進(jìn)程能把磁盤寫滿進(jìn)而影響整臺(tái)機(jī)器上所有容器。限制可寫層空間用--storage-opt size大小參數(shù)但這個(gè)參數(shù)有前提只對(duì)支持配額能力的存儲(chǔ)驅(qū)動(dòng)生效overlay2在xfs且啟用pquota的情況下支持ext4上則不一定。所以用之前先確認(rèn)你的驅(qū)動(dòng)和文件系統(tǒng)組合是否支持不支持的話參數(shù)會(huì)被忽略或直接報(bào)錯(cuò)。具體操作上xfs文件系統(tǒng)需要在掛載時(shí)啟用pquota選項(xiàng)然后啟動(dòng)容器時(shí)加--storage-opt size10G這樣的參數(shù)。這里有個(gè)計(jì)算上的經(jīng)驗(yàn)配額要留出余量別卡著業(yè)務(wù)峰值設(shè)。比如應(yīng)用正常寫500M峰值可能到2G那配額設(shè)5G比較穩(wěn)妥既限制了失控增長(zhǎng)又不會(huì)在正常波動(dòng)時(shí)觸發(fā)寫失敗。配額觸發(fā)后的表現(xiàn)是寫操作報(bào)no space left on device應(yīng)用側(cè)要能優(yōu)雅處理這種錯(cuò)誤別直接崩潰。3.2 日志驅(qū)動(dòng)的空間黑洞與治理容器日志是磁盤占用的隱形大戶默認(rèn)的json-file日志驅(qū)動(dòng)會(huì)把標(biāo)準(zhǔn)輸出全部落盤而且默認(rèn)不輪轉(zhuǎn)。一個(gè)高頻打日志的應(yīng)用幾天就能寫出幾十G。治理手段是配置日志輪轉(zhuǎn)在daemon配置里設(shè)置log-driver和log-opts比如限制單文件大小和保留文件數(shù)。{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }這段配置的意思是單個(gè)日志文件最大50M最多保留3個(gè)單個(gè)容器日志上限就是150M。這個(gè)值怎么定看你的日志量和排查需求。排查問題通??醋罱鼛仔r(shí)到幾天的日志150M對(duì)多數(shù)應(yīng)用夠用日志特別密集的應(yīng)用可以適當(dāng)調(diào)大單文件、減少文件數(shù)或者干脆把日志驅(qū)動(dòng)換成把日志轉(zhuǎn)發(fā)到集中日志系統(tǒng)的方式本地只留少量緩沖。改完daemon配置要重啟Docker服務(wù)生效已存在的容器需要重建才能應(yīng)用新配置這點(diǎn)很多人會(huì)忽略改完發(fā)現(xiàn)老容器日志還在瘋長(zhǎng)。3.3 鏡像與構(gòu)建緩存的定期清理鏡像和構(gòu)建緩存是另一塊容易失控的空間。每次構(gòu)建產(chǎn)生的中間層、懸空鏡像dangling image沒有標(biāo)簽引用的層會(huì)慢慢堆積。docker image prune清理懸空鏡像加-a則清理所有沒有被容器使用的鏡像后者要謹(jǐn)慎可能刪掉你還要用的鏡像。docker builder prune清理構(gòu)建緩存構(gòu)建頻繁的機(jī)器上這塊能釋放出驚人空間。清理策略我建議做成定期任務(wù)而不是等磁盤告警了再手動(dòng)清。比如每周執(zhí)行一次懸空鏡像和過期構(gòu)建緩存清理每月審視一次未被使用的鏡像。但自動(dòng)清理腳本里絕對(duì)不要無腦加-a和--force組合我見過腳本把正在用于快速回滾的舊版本鏡像一起清掉出事時(shí)想回滾發(fā)現(xiàn)鏡像沒了。穩(wěn)妥做法是保留最近若干個(gè)版本或者給重要鏡像打上明確標(biāo)簽并排除在清理范圍外。清理對(duì)象命令風(fēng)險(xiǎn)等級(jí)建議頻率懸空鏡像docker image prune低每周未使用鏡像docker image prune -a中高手動(dòng)確認(rèn)后執(zhí)行構(gòu)建緩存docker builder prune低每周停止的容器docker container prune中確認(rèn)后執(zhí)行未引用卷docker volume prune高極度謹(jǐn)慎4. 掛載權(quán)限、性能與故障排查實(shí)錄4.1 權(quán)限問題的根源與解法掛載相關(guān)的報(bào)錯(cuò)里權(quán)限問題占了大多數(shù)表現(xiàn)是容器內(nèi)進(jìn)程報(bào)Permission denied。根源在于容器內(nèi)進(jìn)程的UID/GID和宿主機(jī)文件屬主不匹配。容器默認(rèn)以root運(yùn)行寫綁定掛載的宿主機(jī)目錄通常沒問題但以非root用戶運(yùn)行的安全實(shí)踐下問題就來了。解法有三條路。第一條是調(diào)整宿主機(jī)目錄屬主讓它的UID/GID和容器內(nèi)運(yùn)行用戶一致適合目錄專屬某個(gè)容器的場(chǎng)景。第二條是啟動(dòng)容器時(shí)用--user指定UID/GID讓容器進(jìn)程以宿主機(jī)上已有用戶的身份運(yùn)行適合開發(fā)環(huán)境。第三條是用數(shù)據(jù)卷代替綁定掛載讓Docker處理初始化權(quán)限。三條路各有適用面我一般建議生產(chǎn)用命名卷規(guī)避大部分權(quán)限問題開發(fā)用--user映射當(dāng)前用戶。還有個(gè)SELinux相關(guān)的坑值得單獨(dú)說。啟用了SELinux的系統(tǒng)上綁定掛載默認(rèn)可能被拒絕訪問需要在掛載參數(shù)后加:z或:Z標(biāo)簽。:z表示多個(gè)容器共享該目錄:Z表示該目錄專屬當(dāng)前容器。加錯(cuò)了標(biāo)簽會(huì)導(dǎo)致其他容器訪問不了或者安全上下文沖突。這個(gè)細(xì)節(jié)在排查明明權(quán)限都對(duì)卻讀寫失敗時(shí)經(jīng)常是元兇。4.2 掛載性能的影響因素綁定掛載的性能受宿主機(jī)文件系統(tǒng)影響大尤其是大量小文件讀寫的場(chǎng)景。跨文件系統(tǒng)掛載、網(wǎng)絡(luò)文件系統(tǒng)掛載進(jìn)容器性能損耗可能非常明顯。數(shù)據(jù)庫這類對(duì)IO敏感的應(yīng)用數(shù)據(jù)目錄一定要放在本地高性能存儲(chǔ)上別掛到網(wǎng)絡(luò)存儲(chǔ)上跑延遲和吞吐都扛不住。數(shù)據(jù)卷的性能則取決于它落在什么存儲(chǔ)上。默認(rèn)local驅(qū)動(dòng)把卷放在數(shù)據(jù)目錄所在磁盤磁盤性能就是卷的性能上限。如果業(yè)務(wù)對(duì)IO要求高可以考慮把卷放到獨(dú)立的高性能磁盤上通過卷驅(qū)動(dòng)的選項(xiàng)指定落盤位置。另外要注意容器可寫層的寫時(shí)復(fù)制開銷在大量寫場(chǎng)景下不可忽視把高頻寫目錄掛成卷能繞過這層開銷這也是數(shù)據(jù)庫容器必須掛卷的原因之一。4.3 常見故障速查與排查思路存儲(chǔ)相關(guān)的故障排查核心是快速定位問題出在哪一層是空間滿了、權(quán)限不對(duì)、掛載沒生效還是底層存儲(chǔ)出問題。我整理了一張速查表覆蓋高頻場(chǎng)景?,F(xiàn)象可能原因排查命令處理方向容器啟動(dòng)報(bào)no space left磁盤或配額滿df -h、docker system df清理空間或調(diào)整配額容器內(nèi)寫文件Permission deniedUID/GID不匹配或SELinuxls -ln 掛載路徑、getenforce調(diào)屬主或加掛載標(biāo)簽數(shù)據(jù)重啟后丟失數(shù)據(jù)寫在可寫層未掛卷docker inspect看Mounts補(bǔ)掛數(shù)據(jù)卷卷刪不掉仍被容器引用docker ps -a查引用先刪引用容器掛載目錄內(nèi)容為空綁定掛載覆蓋了鏡像內(nèi)目錄docker inspect看掛載配置確認(rèn)掛載意圖必要時(shí)先復(fù)制數(shù)據(jù)IO性能異常低掛載到網(wǎng)絡(luò)存儲(chǔ)或跨文件系統(tǒng)mount看掛載類型遷到本地高性能存儲(chǔ)排查時(shí)我習(xí)慣先跑docker system df看整體空間分布鏡像、容器、卷、構(gòu)建緩存各占多少一目了然比逐個(gè)目錄du快得多。然后docker inspect 容器名看Mounts字段確認(rèn)掛載是否按預(yù)期生效很多時(shí)候問題就是掛載配置寫錯(cuò)了沒生效。最后才深入到宿主機(jī)層面看文件系統(tǒng)、權(quán)限、SELinux。提示綁定掛載一個(gè)非空目錄到容器內(nèi)已有內(nèi)容的路徑時(shí)容器內(nèi)原內(nèi)容會(huì)被遮住看不見但沒被刪除。這個(gè)行為經(jīng)常讓人誤以為鏡像里的文件丟了其實(shí)是掛載覆蓋。需要保留原內(nèi)容的話先把它復(fù)制到宿主機(jī)目錄再掛載。5. 備份、遷移與存儲(chǔ)安全實(shí)踐5.1 數(shù)據(jù)卷的備份與恢復(fù)卷里的數(shù)據(jù)往往是業(yè)務(wù)核心資產(chǎn)備份不能靠復(fù)制數(shù)據(jù)目錄這種粗暴方式因?yàn)榫砜赡苷蝗萜鲗懭胫苯訌?fù)制可能拿到不一致的快照。穩(wěn)妥做法是起一個(gè)臨時(shí)容器掛載目標(biāo)卷在容器內(nèi)做打包輸出到宿主機(jī)或另一個(gè)卷。docker run --rm \ -v 目標(biāo)卷名:/source:ro \ -v /宿主機(jī)備份路徑:/backup \ alpine tar czf /backup/backup-$(date %Y%m%d).tar.gz -C /source .這條命令的關(guān)鍵點(diǎn)目標(biāo)卷以只讀方式掛載避免備份過程意外寫入用輕量基礎(chǔ)鏡像執(zhí)行打包不引入額外依賴備份文件名帶日期便于版本管理。恢復(fù)時(shí)反向操作把壓縮包解到目標(biāo)卷即可。要注意恢復(fù)前最好停掉使用該卷的容器避免恢復(fù)過程中應(yīng)用還在寫導(dǎo)致數(shù)據(jù)錯(cuò)亂。備份頻率和保留策略看數(shù)據(jù)重要性和變化速度。核心業(yè)務(wù)數(shù)據(jù)每天備份、保留最近若干天加每月歸檔是常見組合。備份文件本身也要驗(yàn)證定期做一次恢復(fù)演練別等真出事才發(fā)現(xiàn)備份是壞的。我見過備份腳本跑了一年沒報(bào)錯(cuò)結(jié)果恢復(fù)時(shí)發(fā)現(xiàn)壓縮包一直是空的原因是掛載路徑寫錯(cuò)了。5.2 跨主機(jī)遷移卷數(shù)據(jù)的注意事項(xiàng)遷移卷數(shù)據(jù)到另一臺(tái)主機(jī)思路和備份類似源主機(jī)打包傳輸?shù)侥繕?biāo)主機(jī)目標(biāo)主機(jī)先創(chuàng)建同名卷再解包。這里有幾個(gè)細(xì)節(jié)容易翻車。目標(biāo)主機(jī)的卷要先創(chuàng)建好直接解包到一個(gè)不存在的路徑可能落到錯(cuò)誤位置。UID/GID要保持一致否則遷移后權(quán)限錯(cuò)亂解包時(shí)用--numeric-owner保留數(shù)字屬主。傳輸過程注意完整性校驗(yàn)大文件傳輸出錯(cuò)很隱蔽。遷移前還要確認(rèn)目標(biāo)主機(jī)的存儲(chǔ)驅(qū)動(dòng)和文件系統(tǒng)兼容雖然卷數(shù)據(jù)本身是普通文件但掛載后的行為可能受驅(qū)動(dòng)影響。遷移完成后先在目標(biāo)主機(jī)上起一個(gè)測(cè)試容器驗(yàn)證數(shù)據(jù)可讀可寫再切換正式流量別直接上生產(chǎn)。5.3 存儲(chǔ)層面的安全加固要點(diǎn)存儲(chǔ)安全這塊幾個(gè)基礎(chǔ)實(shí)踐必須做到。卷和綁定掛載的宿主機(jī)路徑權(quán)限要收緊別給全局可寫最小權(quán)限原則同樣適用于存儲(chǔ)。敏感數(shù)據(jù)卷考慮加密至少做到磁盤級(jí)加密容器層面可以用支持加密的卷驅(qū)動(dòng)。備份文件里可能含敏感數(shù)據(jù)備份存儲(chǔ)的訪問控制要和源數(shù)據(jù)同等對(duì)待。還有個(gè)容易忽視的點(diǎn)容器內(nèi)不要掛載宿主機(jī)的敏感目錄比如整個(gè)/etc、Docker的socket文件。掛載Docker socket等于把宿主機(jī)控制權(quán)交出去這是嚴(yán)重的安全隱患。綁定掛載時(shí)只掛業(yè)務(wù)真正需要的路徑范圍越小越好。定期審計(jì)容器的掛載配置用docker inspect批量檢查有沒有意外的敏感掛載這個(gè)習(xí)慣能幫你發(fā)現(xiàn)配置漂移帶來的風(fēng)險(xiǎn)。6. 把存儲(chǔ)配置固化成可復(fù)用的規(guī)范6.1 用編排文件聲明存儲(chǔ)配置手工敲docker run帶一堆掛載參數(shù)容易出錯(cuò)也難以復(fù)現(xiàn)。把存儲(chǔ)配置寫進(jìn)編排文件compose文件是更可靠的做法卷定義、掛載點(diǎn)、只讀標(biāo)志都能聲明清楚版本可控團(tuán)隊(duì)共享。services: app: image: 應(yīng)用鏡像 volumes: - app-data:/data - ./config:/app/config:ro logging: driver: json-file options: max-size: 50m max-file: 3 volumes: app-data: driver: local這段配置里app-data是命名卷由編排文件統(tǒng)一管理生命周期配置文件用綁定掛載并標(biāo)記只讀防止容器意外修改日志輪轉(zhuǎn)直接在服務(wù)級(jí)別聲明。把這類配置模板化新項(xiàng)目直接套用能避免大量重復(fù)的配置錯(cuò)誤。只讀掛載這個(gè)習(xí)慣特別值得推廣配置文件、證書這類不該被容器改的內(nèi)容一律加:ro既防誤寫也是安全加固。6.2 監(jiān)控存儲(chǔ)用量并設(shè)置告警存儲(chǔ)問題最好的處理方式是提前發(fā)現(xiàn)。監(jiān)控要覆蓋幾個(gè)維度宿主機(jī)磁盤使用率、Docker數(shù)據(jù)目錄增長(zhǎng)趨勢(shì)、各卷的用量、容器日志增長(zhǎng)速度。磁盤使用率設(shè)閾值告警比如80%預(yù)警、90%嚴(yán)重告警留出處理時(shí)間。卷用量監(jiān)控能提前發(fā)現(xiàn)異常增長(zhǎng)的業(yè)務(wù)日志增長(zhǎng)速度異常往往是應(yīng)用出問題的信號(hào)。監(jiān)控工具選型上能采集磁盤指標(biāo)并支持告警的方案都行關(guān)鍵是指標(biāo)要采全、告警要能觸達(dá)。我建議至少把Docker數(shù)據(jù)目錄所在分區(qū)的使用率做成趨勢(shì)圖觀察增長(zhǎng)速度比看瞬時(shí)值更有價(jià)值能預(yù)判什么時(shí)候會(huì)滿。另外定期跑docker system df把結(jié)果記錄下來形成空間使用的歷史基線異常時(shí)對(duì)比一目了然。6.3 團(tuán)隊(duì)協(xié)作中的存儲(chǔ)規(guī)范約定存儲(chǔ)配置的混亂往往源于團(tuán)隊(duì)沒有統(tǒng)一約定。幾個(gè)值得寫進(jìn)團(tuán)隊(duì)規(guī)范的約定命名卷統(tǒng)一命名規(guī)則比如項(xiàng)目名-用途避免匿名卷所有持久化數(shù)據(jù)必須掛卷禁止依賴可寫層日志輪轉(zhuǎn)作為服務(wù)標(biāo)配不允許裸奔綁定掛載的宿主機(jī)路徑統(tǒng)一規(guī)劃不允許隨意指向系統(tǒng)目錄清理操作必須人工確認(rèn)禁止在自動(dòng)化腳本里無差別prune。這些約定看起來瑣碎但能避免大量低級(jí)問題。新成員入職時(shí)把存儲(chǔ)規(guī)范作為環(huán)境搭建的一部分講清楚比出了問題再救火高效得多。規(guī)范落地可以借助編排文件的模板和代碼審查掛載配置在審查階段就能發(fā)現(xiàn)不合規(guī)的地方。7. 幾個(gè)我踩過的坑和對(duì)應(yīng)心得第一個(gè)坑是關(guān)于卷的刪除保護(hù)。早期有次清理磁盤圖省事直接跑了帶force的批量刪除把一個(gè)暫時(shí)沒掛載但存著歷史數(shù)據(jù)的卷刪了數(shù)據(jù)找不回來。從那以后我定了個(gè)規(guī)矩任何刪除卷的操作前先docker volume inspect看清楚它是什么、什么時(shí)候創(chuàng)建的確認(rèn)無用再動(dòng)手force參數(shù)能不用就不用。第二個(gè)坑是日志輪轉(zhuǎn)配置改了沒生效。有次磁盤告警查出來是某個(gè)容器日志寫了上百G明明daemon配置里設(shè)了輪轉(zhuǎn)。后來發(fā)現(xiàn)那個(gè)容器是在配置修改之前創(chuàng)建的舊容器不會(huì)自動(dòng)應(yīng)用新的日志配置必須重建。這件事讓我養(yǎng)成了改完daemon配置后檢查存量容器的習(xí)慣該重建的重建別以為改了配置就萬事大吉。第三個(gè)坑是綁定掛載覆蓋導(dǎo)致的數(shù)據(jù)丟失假象。有次部署新版本容器起來后應(yīng)用報(bào)找不到默認(rèn)配置文件排查半天發(fā)現(xiàn)是掛載了一個(gè)空的宿主機(jī)目錄到配置目錄把鏡像里的默認(rèn)配置遮住了。解決辦法是先把鏡像里的默認(rèn)配置復(fù)制到宿主機(jī)目錄再掛載或者改用命名卷加初始化容器的方式。這個(gè)行為本身是Docker的既定邏輯但第一次遇到確實(shí)容易懵。第四個(gè)坑和xfs的ftype有關(guān)前面提過這里補(bǔ)充細(xì)節(jié)。當(dāng)時(shí)在一批機(jī)器上部署部分機(jī)器overlay2工作正常部分報(bào)錯(cuò)。對(duì)比發(fā)現(xiàn)報(bào)錯(cuò)機(jī)器的xfs是舊版本工具創(chuàng)建的沒啟用ftype1。這個(gè)參數(shù)在文件系統(tǒng)創(chuàng)建時(shí)就定了后期改不了只能備份數(shù)據(jù)、重建文件系統(tǒng)、恢復(fù)數(shù)據(jù)。所以新機(jī)器上線前的檢查清單里我把xfs_info確認(rèn)ftype加進(jìn)去了這一步花幾秒鐘能省掉后面幾小時(shí)的折騰。這些坑的共同點(diǎn)是都不是什么高深技術(shù)問題而是對(duì)存儲(chǔ)行為細(xì)節(jié)的理解不到位。Docker存儲(chǔ)的每個(gè)設(shè)計(jì)都有其邏輯理解了邏輯坑自然就少了。配置存儲(chǔ)時(shí)多問一句這個(gè)數(shù)據(jù)容器刪了還在嗎這個(gè)路徑權(quán)限對(duì)不對(duì)空間漲起來有沒有上限大部分問題在配置階段就能規(guī)避掉。