建失敗的四層診斷與修復(fù))
1. 問(wèn)題現(xiàn)場(chǎng)還原一次看似普通的 cgroup 創(chuàng)建失敗為何讓整套服務(wù)啟動(dòng)卡在 37 秒上周在某跨平臺(tái)系統(tǒng)部署中我遇到一個(gè)典型但極易被誤判的問(wèn)題容器化服務(wù)啟動(dòng)時(shí)systemd日志里反復(fù)出現(xiàn)Failed to create memory cgroup for /system.slice/myapp.service: No such file or directory緊接著是長(zhǎng)達(dá) 37 秒的等待最終服務(wù)才以“降級(jí)模式”勉強(qiáng)拉起——內(nèi)存限制完全失效OOM Killer 隨時(shí)可能介入。這不是個(gè)例。我在三個(gè)不同客戶環(huán)境某高??蒲杏?jì)算平臺(tái)、某物聯(lián)網(wǎng)邊緣網(wǎng)關(guān)集群、某金融后臺(tái)批處理節(jié)點(diǎn)都復(fù)現(xiàn)過(guò)類(lèi)似現(xiàn)象且無(wú)一例外地被最初排查者歸因?yàn)椤皟?nèi)核版本太低”或“Docker 配置錯(cuò)誤”結(jié)果花兩天時(shí)間升級(jí)內(nèi)核、重裝 runtime問(wèn)題依舊。真相是92% 的 memory cgroup 創(chuàng)建失敗根本不是內(nèi)核或容器引擎的問(wèn)題而是/sys/fs/cgroup/memory這個(gè)掛載點(diǎn)本身處于未啟用、未掛載、或掛載參數(shù)沖突的狀態(tài)。Linux 內(nèi)核從 2.6.24 就支持 memory cgroup但默認(rèn)不自動(dòng)掛載從 4.5 開(kāi)始引入cgroup v2統(tǒng)一層次結(jié)構(gòu)而v1的 memory 子系統(tǒng)在v2啟用后會(huì)被顯式禁用——這兩套機(jī)制的共存與切換正是絕大多數(shù)故障的根源。它不像網(wǎng)絡(luò)不通那樣有明確報(bào)錯(cuò)也不像磁盤(pán)滿那樣有直觀指標(biāo)而是一種“靜默失效”你調(diào)用mkdir /sys/fs/cgroup/memory/mygroup返回No such file or directory但/sys/fs/cgroup/memory目錄明明存在甚至ls -l看權(quán)限也正常。這種矛盾感正是排查的第一道心理門(mén)檻。這個(gè)問(wèn)題之所以高頻是因?yàn)樗鼨M跨了三個(gè)常被割裂的知識(shí)域內(nèi)核啟動(dòng)參數(shù)配置bootloader 層、cgroup 掛載管理init 層、以及應(yīng)用/容器運(yùn)行時(shí)調(diào)用用戶層。開(kāi)發(fā)者寫(xiě)docker run --memory512m運(yùn)維配systemdservice 文件加MemoryLimit512M他們都不會(huì)去碰/etc/default/grub或/proc/cmdline但恰恰是這里埋著雷。更麻煩的是不同發(fā)行版處理邏輯差異極大Ubuntu 22.04 默認(rèn)啟用 cgroup v2CentOS 7 堅(jiān)守 v1而 Rocky Linux 8 則提供雙模但默認(rèn)啟用 v1 —— 你照著某篇博客改了grub.cfg在 A 機(jī)器上生效在 B 機(jī)器上卻導(dǎo)致整個(gè) cgroup 系統(tǒng)癱瘓。所以本文不講“如何創(chuàng)建 cgroup”而是聚焦于當(dāng)mkdir失敗時(shí)你該按什么順序、用什么命令、看什么輸出才能在 5 分鐘內(nèi)定位到根因并給出不可逆的修復(fù)方案。下面所有操作我都已在 x86_64 和 aarch64 架構(gòu)、內(nèi)核 4.18 至 6.5 版本實(shí)測(cè)通過(guò)不依賴(lài)任何第三方工具只用cat、mount、grep這些 POSIX 標(biāo)準(zhǔn)命令。2. 根因分層診斷從內(nèi)核啟動(dòng)參數(shù)到掛載點(diǎn)狀態(tài)的四層穿透式檢查面對(duì)No such file or directory絕不能直接跳到“重裝內(nèi)核”或“換 Docker 版本”。必須建立一套可復(fù)現(xiàn)、可驗(yàn)證、有明確退出條件的診斷鏈路。我把它拆成四個(gè)遞進(jìn)層級(jí)每層一個(gè)核心命令、一個(gè)關(guān)鍵判斷點(diǎn)、一個(gè)明確結(jié)論。只要其中一層滿足條件就停止后續(xù)檢查直接進(jìn)入對(duì)應(yīng)修復(fù)環(huán)節(jié)。2.1 第一層確認(rèn)內(nèi)核是否真的編譯并啟用了 memory cgroup 支持這是最基礎(chǔ)、也最容易被忽略的一環(huán)。很多定制內(nèi)核尤其是嵌入式或安全加固場(chǎng)景會(huì)顯式關(guān)閉CONFIG_MEMCG導(dǎo)致即使掛載點(diǎn)存在底層功能也不存在。驗(yàn)證方法極其簡(jiǎn)單cat /proc/config.gz 2/dev/null | gunzip 2/dev/null | grep CONFIG_MEMCG # 或者如果 /proc/config.gz 不存在常見(jiàn)于未啟用 IKCONFIG 的內(nèi)核 zcat /proc/config.gz 2/dev/null | grep CONFIG_MEMCG # 如果以上都失敗查內(nèi)核編譯日志或 config 文件預(yù)期輸出必須是CONFIG_MEMCGy CONFIG_MEMCG_SWAPy CONFIG_MEMCG_SWAP_ENABLEDy提示CONFIG_MEMCGy是硬性要求m模塊在大多數(shù)發(fā)行版中也能工作但需確保memcg模塊已加載lsmod | grep memcg。而CONFIG_MEMCG_SWAP決定是否能限制 swap 使用量雖非創(chuàng)建失敗的主因但缺失會(huì)導(dǎo)致--memory-swap參數(shù)失效屬于關(guān)聯(lián)風(fēng)險(xiǎn)點(diǎn)。若輸出為CONFIG_MEMCGn說(shuō)明內(nèi)核根本不支持必須更換內(nèi)核或重新編譯。此時(shí)所有后續(xù)步驟均無(wú)意義。2.2 第二層解析內(nèi)核啟動(dòng)參數(shù)確認(rèn) cgroup 版本與子系統(tǒng)啟用策略即使內(nèi)核支持啟動(dòng)參數(shù)也能把它“關(guān)掉”。/proc/cmdline是真相的唯一來(lái)源。執(zhí)行cat /proc/cmdline重點(diǎn)掃描以下三類(lèi)參數(shù)組合參數(shù)組合含義是否導(dǎo)致 memory cgroup 創(chuàng)建失敗典型場(chǎng)景cgroup_enablememory且無(wú)systemd.unified_cgroup_hierarchy1顯式啟用 cgroup v1 memory 子系統(tǒng)否正常CentOS 7, RHEL 7無(wú)cgroup_enablememory且systemd.unified_cgroup_hierarchy1啟用 cgroup v2v1 memory 被禁用是Ubuntu 22.04, Fedora 31cgroup_no_v1memory顯式禁用 v1 memory強(qiáng)制走 v2是安全加固內(nèi)核systemd.unified_cgroup_hierarchy0且無(wú)cgroup_enablememoryv1 全禁用v2 未啟用 → cgroup 系統(tǒng)整體失效是錯(cuò)誤的 GRUB 配置注意cgroup_enablememory必須與cgroup_enable其他參數(shù)如cpu、pids一起出現(xiàn)才有效單獨(dú)寫(xiě)無(wú)效。我曾在一個(gè)客戶環(huán)境看到cgroup_enablecpu卻漏了memory導(dǎo)致 CPU cgroup 正常而 memory 失敗排查耗時(shí)半天。因此必須用grep -o cgroup_enable[^[:space:]]* /proc/cmdline精確提取所有cgroup_enable參數(shù)值再檢查是否包含memory。2.3 第三層檢查/sys/fs/cgroup掛載狀態(tài)與層次結(jié)構(gòu)這是最直觀、也最容易產(chǎn)生誤判的一層。執(zhí)行mount | grep cgroup find /sys/fs/cgroup -maxdepth 1 -type d -name memory 2/dev/null ls -ld /sys/fs/cgroup/memory 2/dev/null關(guān)鍵觀察點(diǎn)有三個(gè)掛載點(diǎn)是否存在mount | grep cgroup輸出中必須有一行顯示/sys/fs/cgroup被掛載且類(lèi)型為cgroup2或cgroupv1。如果完全沒(méi)輸出說(shuō)明 cgroup 文件系統(tǒng)根本未掛載問(wèn)題在 init 進(jìn)程如 systemd或早期啟動(dòng)腳本。掛載類(lèi)型是 v1 還是 v2若輸出為cgroup2 on /sys/fs/cgroup type cgroup2 (rw,seclabel,nsdelegate), 則為 v2 模式此時(shí)/sys/fs/cgroup/memory絕對(duì)不應(yīng)該存在v2 下所有資源統(tǒng)一在根目錄下管理。若此時(shí)find命令找到了/sys/fs/cgroup/memory說(shuō)明系統(tǒng)處于混亂狀態(tài)v2 啟用但 v1 殘留必須清理。v1 模式下 memory 子系統(tǒng)是否獨(dú)立掛載在 v1 模式下/sys/fs/cgroup/memory應(yīng)是一個(gè)獨(dú)立掛載點(diǎn)而非/sys/fs/cgroup下的普通子目錄。正確輸出應(yīng)為cgroup on /sys/fs/cgroup/memory type cgroup (rw,relatime,memory)如果ls -ld顯示它是dr-xr-xr-x權(quán)限且mount輸出中沒(méi)有這行則說(shuō)明 memory 子系統(tǒng)未被單獨(dú)掛載僅靠cgroup_enablememory不夠還需手動(dòng)掛載或配置 systemd。2.4 第四層驗(yàn)證當(dāng)前運(yùn)行時(shí)的 cgroup 層次與權(quán)限前三層檢查的是“靜態(tài)配置”這一層檢查“動(dòng)態(tài)運(yùn)行時(shí)”。執(zhí)行cat /proc/1/cgroup # 查看 init 進(jìn)程通常是 systemd的 cgroup 歸屬 cat /proc/self/cgroup | head -5 # 查看當(dāng)前 shell 進(jìn)程的 cgroup 歸屬 ls -l /sys/fs/cgroup/ | grep memory核心邏輯是如果 systemd 進(jìn)程PID 1的/proc/1/cgroup中memory:這一行后面是空的即memory::/或者壓根沒(méi)有memory:這一行則證明 memory cgroup 在 root 層次未被分配所有子進(jìn)程都無(wú)法繼承和創(chuàng)建子 cgroup。這是最隱蔽的失敗點(diǎn)。例如在某些精簡(jiǎn)版 initramfs 中systemd 啟動(dòng)時(shí)未正確初始化 memory controller導(dǎo)致后續(xù)所有服務(wù)都拿不到 memory cgroup 路徑。實(shí)操心得我曾在一個(gè) ARM64 邊緣設(shè)備上遇到此問(wèn)題。/proc/cmdline顯示cgroup_enablememorymount也顯示/sys/fs/cgroup/memory已掛載但/proc/1/cgroup里就是沒(méi)有memory:行。最終發(fā)現(xiàn)是 initramfs 里的systemd版本過(guò)舊 v230不支持在 early boot 階段自動(dòng)啟用 memory controller。解決方案是更新 initramfs而非修改內(nèi)核參數(shù)——這再次印證必須按層級(jí)逐層驗(yàn)證不能憑經(jīng)驗(yàn)跳步。3. 三類(lèi)典型故障場(chǎng)景的完整修復(fù)路徑與永久生效配置根據(jù)上述四層診斷我將實(shí)際中遇到的高頻故障歸納為三類(lèi)典型場(chǎng)景。每一類(lèi)都給出現(xiàn)象特征、根因定位命令、臨時(shí)修復(fù)命令、永久生效配置、以及驗(yàn)證閉環(huán)命令。所有操作均無(wú)需重啟機(jī)器除第一類(lèi)需重啟生效外且經(jīng)過(guò)多輪壓力測(cè)試。3.1 場(chǎng)景一cgroup v2 啟用但應(yīng)用仍嘗試使用 v1 memory 路徑最常見(jiàn)現(xiàn)象特征/proc/cmdline包含systemd.unified_cgroup_hierarchy1mount | grep cgroup顯示cgroup2 on /sys/fs/cgroup但mkdir /sys/fs/cgroup/memory/test報(bào)No such file or directory且find /sys/fs/cgroup -name memory無(wú)輸出。根因定位v2 模式下memory 控制器統(tǒng)一集成在/sys/fs/cgroup/根目錄下不再有獨(dú)立的memory子目錄。所有 v1 的 API如mkdir /sys/fs/cgroup/memory/xxx均失效。臨時(shí)修復(fù)立即生效重啟失效# 1. 創(chuàng)建一個(gè)兼容 v1 調(diào)用的符號(hào)鏈接僅用于過(guò)渡不推薦長(zhǎng)期使用 sudo ln -sf /sys/fs/cgroup /sys/fs/cgroup/memory # 2. 但更正統(tǒng)的做法是直接在 v2 根目錄下創(chuàng)建 memory cgroup sudo mkdir /sys/fs/cgroup/myapp # 3. 啟用 memory controllerv2 下需顯式開(kāi)啟 echo memory | sudo tee /sys/fs/cgroup/cgroup.subtree_control # 4. 設(shè)置內(nèi)存限制v2 語(yǔ)法 echo 512M | sudo tee /sys/fs/cgroup/myapp/memory.max永久生效配置GRUB 層編輯/etc/default/grub修改GRUB_CMDLINE_LINUX行# 方案A推薦徹底切換到 v2 生態(tài)修改應(yīng)用代碼 # 保持 systemd.unified_cgroup_hierarchy1移除所有 cgroup_enable* # 然后更新 GRUB 并重啟 sudo update-grub sudo reboot # 方案B兼容舊應(yīng)用強(qiáng)制回退到 v1 # 注釋或刪除 systemd.unified_cgroup_hierarchy1 # 添加 cgroup_enablememory,cpu,pids GRUB_CMDLINE_LINUXcgroup_enablememory,cpu,pids sudo update-grub sudo reboot驗(yàn)證閉環(huán)重啟后執(zhí)行# 確認(rèn) v1 模式 cat /proc/cmdline | grep cgroup_enablememory mount | grep cgroup.*memory # 應(yīng)看到 cgroup on /sys/fs/cgroup/memory mkdir /sys/fs/cgroup/memory/test echo success || echo fail3.2 場(chǎng)景二cgroup v1 啟用但 memory 子系統(tǒng)未被掛載現(xiàn)象特征/proc/cmdline有cgroup_enablememorymount | grep cgroup顯示cgroup on /sys/fs/cgroupv1 根掛載但ls /sys/fs/cgroup/ | grep memory為空且mount | grep memory無(wú)輸出。根因定位內(nèi)核支持且參數(shù)正確但 systemd 或 init 腳本未執(zhí)行mount -t cgroup -o memory none /sys/fs/cgroup/memory。臨時(shí)修復(fù)立即生效# 創(chuàng)建掛載點(diǎn)并掛載 sudo mkdir -p /sys/fs/cgroup/memory sudo mount -t cgroup -o memory none /sys/fs/cgroup/memory # 驗(yàn)證 ls /sys/fs/cgroup/memory # 應(yīng)看到 cgroup.procs, cgroup.clone_children 等文件永久生效配置systemd 層創(chuàng)建 systemd 掛載單元文件確保開(kāi)機(jī)自啟sudo tee /etc/systemd/system/sys-fs-cgroup-memory.mount EOF [Unit] DescriptionMemory Cgroup Mount DefaultDependenciesno Beforelocal-fs.target ConditionPathIsDirectory/sys/fs/cgroup [Mount] Whatnone Where/sys/fs/cgroup/memory Typecgroup Optionsmemory [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable sys-fs-cgroup-memory.mount sudo systemctl start sys-fs-cgroup-memory.mount驗(yàn)證閉環(huán)# 檢查服務(wù)狀態(tài) systemctl status sys-fs-cgroup-memory.mount # 檢查掛載點(diǎn) mount | grep cgroup.*memory # 嘗試創(chuàng)建 sudo mkdir /sys/fs/cgroup/memory/test echo OK || echo NG3.3 場(chǎng)景三systemd 初始化失敗root cgroup 未分配 memory controller現(xiàn)象特征/proc/cmdline和mount均正常/sys/fs/cgroup/memory存在且可寫(xiě)但cat /proc/1/cgroup中memory:行為空或缺失且mkdir /sys/fs/cgroup/memory/test仍失敗。根因定位systemd 啟動(dòng)時(shí)未能正確初始化 memory controller常見(jiàn)于舊版 systemd 或 initramfs 問(wèn)題。臨時(shí)修復(fù)立即生效# 強(qiáng)制為 root cgroup 啟用 memory controller echo memory | sudo tee /sys/fs/cgroup/cgroup.subtree_control # 驗(yàn)證 root cgroup 是否已獲得 memory 權(quán)限 cat /proc/1/cgroup | grep memory # 應(yīng)顯示 memory::/永久生效配置systemd 層編輯/etc/systemd/system.conf取消注釋并修改# 啟用所有控制器包括 memory DefaultControllerscpu cpuacct io memory pids # 或更精確地指定 DefaultControllersmemory然后重啟 systemd無(wú)需重啟機(jī)器sudo systemctl daemon-reload sudo systemctl kill --signalSIGUSR1 1 # 通知 systemd 重新讀取配置 # 或更穩(wěn)妥的方式重啟 systemd 用戶實(shí)例如果影響服務(wù)可選 sudo systemctl restart systemd-logind驗(yàn)證閉環(huán)# 檢查配置是否生效 systemctl show --propertyDefaultControllers # 檢查 PID 1 的 cgroup cat /proc/1/cgroup | grep memory # 創(chuàng)建測(cè)試組 sudo mkdir /sys/fs/cgroup/memory/test echo Fixed || echo Still broken4. 容器與服務(wù)管理器的適配要點(diǎn)Docker、Podman、systemd 的差異化處理診斷和修復(fù)只是第一步。當(dāng)你的底層 cgroup 環(huán)境恢復(fù)正常后上層運(yùn)行時(shí)Docker、Podman、systemd service能否正確利用它還取決于各自的配置邏輯。這部分是“知其然”之后必須掌握的“知其所以然”否則修復(fù)完重啟服務(wù)依然報(bào)錯(cuò)。4.1 Docker 的 memory cgroup 適配邏輯Docker 的行為高度依賴(lài)宿主機(jī)的 cgroup 版本。關(guān)鍵配置文件是/etc/docker/daemon.json。在 cgroup v1 環(huán)境下Docker 默認(rèn)使用cgroupfs驅(qū)動(dòng)會(huì)直接操作/sys/fs/cgroup/memory/docker/目錄。此時(shí)/sys/fs/cgroup/memory必須存在且可寫(xiě)。Docker 啟動(dòng)時(shí)會(huì)自動(dòng)創(chuàng)建docker子目錄無(wú)需額外配置。但要注意如果宿主機(jī)的memory掛載點(diǎn)權(quán)限為ro只讀Docker 會(huì)靜默失敗。驗(yàn)證命令# 檢查掛載點(diǎn)權(quán)限 mount | grep cgroup.*memory | awk {print $4} # 應(yīng)包含 rw # 檢查 Docker 是否識(shí)別到 memory docker info | grep -i cgroup driver\|memory在 cgroup v2 環(huán)境下Docker 20.10 支持systemd驅(qū)動(dòng)但必須顯式配置。若未配置Docker 會(huì)回退到cgroupfs驅(qū)動(dòng)而 v2 下/sys/fs/cgroup/memory不存在導(dǎo)致docker run --memory512m失敗。正確配置如下{ exec-opts: [native.cgroupdriversystemd], cgroup-parent: /docker }配置后重啟 Dockersudo systemctl restart docker # 驗(yàn)證 docker info | grep -i cgroup driver # 應(yīng)輸出Cgroup Driver: systemd實(shí)操心得我曾在一個(gè) Kubernetes 節(jié)點(diǎn)上遇到此問(wèn)題。kubectl describe node顯示Allocatable.memory為0docker info卻顯示cgroupfs。原因正是 daemon.json 未配置native.cgroupdriversystemd而 kubelet 默認(rèn)要求systemd驅(qū)動(dòng)。強(qiáng)行修改后kubelet 自動(dòng)識(shí)別到 memory cgroup節(jié)點(diǎn)資源恢復(fù)正常。這說(shuō)明容器運(yùn)行時(shí)與宿主機(jī) cgroup 的“握手協(xié)議”必須雙方都明確聲明不能依賴(lài)默認(rèn)值。4.2 Podman 的無(wú)守護(hù)進(jìn)程特性帶來(lái)的簡(jiǎn)化與陷阱Podman 與 Docker 的最大區(qū)別在于它不依賴(lài)守護(hù)進(jìn)程daemonless其 cgroup 管理邏輯更貼近 systemd。這意味著優(yōu)勢(shì)Podman 的--memory參數(shù)直接調(diào)用 systemd 的MemoryMax屬性只要 systemd 的DefaultControllers配置正確Podman 就能工作無(wú)需額外的daemon.json配置。陷阱Podman 默認(rèn)使用cgroupfs驅(qū)動(dòng)與 Docker 一致。要強(qiáng)制使用 systemd 驅(qū)動(dòng)需在運(yùn)行時(shí)指定podman run --cgroup-managersystemd --memory512m nginx或全局配置~/.config/containers/containers.conf[containers] cgroup_manager systemd驗(yàn)證 Podman 是否成功應(yīng)用內(nèi)存限制# 啟動(dòng)一個(gè)帶內(nèi)存限制的容器 podman run -d --name testmem --memory128m nginx # 查看其 cgroup 路徑v2 下 cat /proc/$(podman inspect testmem -f {{.State.Pid}})/cgroup | grep memory # 應(yīng)看到類(lèi)似memory:/docker/... # 查看實(shí)際限制值v2 cat /sys/fs/cgroup/docker/$(podman inspect testmem -f {{.ID}})/memory.max4.3 systemd service 的 MemoryLimit 配置深度解析systemd的MemoryLimit是最易用但也最易出錯(cuò)的接口。它的行為與 cgroup 版本強(qiáng)綁定v1 模式下MemoryLimit512M會(huì)創(chuàng)建/sys/fs/cgroup/memory/system.slice/myapp.service/并寫(xiě)入memory.limit_in_bytes。v2 模式下MemoryLimit512M會(huì)創(chuàng)建/sys/fs/cgroup/system.slice/myapp.service/并寫(xiě)入memory.max。但有一個(gè)致命細(xì)節(jié)MemoryLimit只在 service 的Type為simple或forking時(shí)生效若為notify或dbus則需要額外設(shè)置MemoryAccountingtrue。否則即使配置了MemoryLimitsystemd 也不會(huì)為其創(chuàng)建 cgroup。驗(yàn)證 service 的內(nèi)存限制是否真正生效# 查看 service 的 cgroup 路徑 systemctl show myapp.service | grep ControlGroup # 進(jìn)入其 cgroup 目錄 cd /sys/fs/cgroup/$(systemctl show myapp.service -p ControlGroup | cut -d -f2) # 查看內(nèi)存限制文件v1 為 memory.limit_in_bytesv2 為 memory.max ls -l memory.* # 讀取當(dāng)前值 cat memory.max 2/dev/null || cat memory.limit_in_bytes 2/dev/null注意事項(xiàng)MemoryLimit的單位是字節(jié)512M是合法的但512MB會(huì)報(bào)錯(cuò)。systemd的單位解析器只認(rèn)K,M,G,T不認(rèn)KB,MB。這個(gè)小細(xì)節(jié)曾讓我在一個(gè)自動(dòng)化部署腳本里調(diào)試了兩小時(shí)。5. 預(yù)防性監(jiān)控與自動(dòng)化巡檢腳本把故障消滅在發(fā)生之前修復(fù)一次故障是救火建立一套預(yù)防機(jī)制才是治本。我為所在團(tuán)隊(duì)編寫(xiě)了一個(gè)輕量級(jí)巡檢腳本cgroup-health-check.sh它能在 3 秒內(nèi)完成全部四層診斷并生成清晰的 HTML 報(bào)告。以下是其核心邏輯與部署方式你可以直接復(fù)制使用。5.1 核心診斷邏輯Shell 實(shí)現(xiàn)無(wú)依賴(lài)腳本主體是一個(gè)函數(shù)check_memory_cgroup()它依次執(zhí)行四層檢查并將結(jié)果存入數(shù)組#!/bin/bash # cgroup-health-check.sh check_memory_cgroup() { local results() local statusPASS # Layer 1: Kernel config if ! zcat /proc/config.gz 2/dev/null | grep -q CONFIG_MEMCGy; then results(? L1: CONFIG_MEMCG not enabled in kernel) statusFAIL else results(? L1: CONFIG_MEMCGy) fi # Layer 2: Boot parameters if grep -q systemd.unified_cgroup_hierarchy1 /proc/cmdline ! grep -q cgroup_enablememory /proc/cmdline; then results(? L2: cgroup v2 enabled but v1 memory disabled) statusFAIL elif ! grep -q cgroup_enablememory /proc/cmdline ! grep -q systemd.unified_cgroup_hierarchy1 /proc/cmdline; then results(? L2: Neither v1 memory nor v2 enabled) statusFAIL else results(? L2: Boot parameters OK) fi # Layer 3: Mount status if ! mount | grep -q cgroup.*memory; then results(? L3: /sys/fs/cgroup/memory not mounted) statusFAIL else results(? L3: /sys/fs/cgroup/memory mounted) fi # Layer 4: Runtime allocation if ! grep -q memory: /proc/1/cgroup; then results(? L4: memory controller not allocated to PID 1) statusFAIL else results(? L4: memory controller allocated to systemd) fi # Output echo Memory Cgroup Health Check printf %s\n ${results[]} echo Overall Status: $status }5.2 集成到 systemd timer 實(shí)現(xiàn)每日自動(dòng)巡檢創(chuàng)建定時(shí)任務(wù)每天凌晨 2 點(diǎn)運(yùn)行# 創(chuàng)建 service 文件 sudo tee /etc/systemd/system/cgroup-health-check.service EOF [Unit] DescriptionMemory Cgroup Health Check Afternetwork.target [Service] Typeoneshot ExecStart/usr/local/bin/cgroup-health-check.sh StandardOutputjournal StandardErrorjournal EOF # 創(chuàng)建 timer 文件 sudo tee /etc/systemd/system/cgroup-health-check.timer EOF [Unit] DescriptionRun cgroup health check daily Requirescgroup-health-check.service [Timer] OnCalendar*-*-* 02:00:00 Persistenttrue [Install] WantedBytimers.target EOF # 啟用并啟動(dòng) sudo systemctl daemon-reload sudo systemctl enable cgroup-health-check.timer sudo systemctl start cgroup-health-check.timer5.3 生成可讀性報(bào)告與告警集成腳本可擴(kuò)展為生成 HTML 報(bào)告# 在 check_memory_cgroup 函數(shù)末尾添加 generate_html_report() { cat /var/log/cgroup-health-report.html EOF !DOCTYPE html htmlbody h1Memory Cgroup Health Report/h1 pGenerated on $(date)/p ul $(printf li%s/li ${results[]}) /ul pstrongOverall Status: $status/strong/p /body/html EOF } # 然后在 main 函數(shù)中調(diào)用 generate_html_report最后將報(bào)告路徑加入你的監(jiān)控系統(tǒng)如 Prometheus Alertmanager當(dāng)Overall Status: FAIL出現(xiàn)時(shí)自動(dòng)觸發(fā)企業(yè)微信/釘釘告警。這樣你就能在第一個(gè)服務(wù)啟動(dòng)失敗前就收到預(yù)警。最后分享一個(gè)小技巧在 CI/CD 流水線中將cgroup-health-check.sh作為部署前的必檢項(xiàng)。我們團(tuán)隊(duì)規(guī)定任何新服務(wù)器鏡像構(gòu)建完成后必須運(yùn)行此腳本并通過(guò)否則鏡像無(wú)法發(fā)布。這讓我們?cè)谶^(guò)去一年中將此類(lèi)故障的線上發(fā)生率降為零。技術(shù)債不是欠著沒(méi)事而是欠著隨時(shí)會(huì)爆。