內(nèi)存Cgroup創(chuàng)建原理:從內(nèi)核機(jī)制到實(shí)踐應(yīng)用
在容器化和微服務(wù)架構(gòu)里,你是否常遇到這些困惑:Docker 容器突然因內(nèi)存溢出被 OOM Killer 終止?Kubernetes 集群中 Pod 間的內(nèi)存爭搶拖慢整個服務(wù)?其實(shí),解決這些問題的核心底層技術(shù),正是 Linux 內(nèi)核的內(nèi)存 cgroup(Control Group 內(nèi)存子系統(tǒng))—— 它不僅是容器實(shí)現(xiàn)內(nèi)存配額的 “隱形管家”,更是保障分布式系統(tǒng)資源穩(wěn)定的關(guān)鍵支撐。很多開發(fā)者熟悉用--memory給 Docker 設(shè)限,卻不清楚背后如何從內(nèi)核層面攔截內(nèi)存分配、統(tǒng)計資源使用;運(yùn)維同學(xué)面對 cgroup 目錄下的memory.limit_in_bytes等文件,也常困惑參數(shù)配置的底層邏輯。
先拆解內(nèi)存 cgroup 的創(chuàng)建全流程(從文件系統(tǒng)掛載到進(jìn)程綁定),再深扒內(nèi)核中mem_cgroup數(shù)據(jù)結(jié)構(gòu)、OOM 處理機(jī)制等核心原理,還會結(jié)合 Docker/K8s 的實(shí)戰(zhàn)配置案例,對比 cgroup v1/v2 差異,給出生產(chǎn)環(huán)境優(yōu)化建議。無論你是想搞懂容器內(nèi)存隔離的底層邏輯,還是需要解決資源管控的實(shí)際問題,跟著內(nèi)容一步步拆解,都能理清內(nèi)存 cgroup 的 “來龍去脈”。
一、什么是內(nèi)存 cgroup ?
1.1 cgroup 概述
Cgroups(Control Groups)是 Linux 內(nèi)核提供的物理資源隔離機(jī)制,通過層級化的控制組(Control Group)實(shí)現(xiàn)對進(jìn)程組的資源限制、統(tǒng)計與隔離。它就像是一個精密的資源分配器,把系統(tǒng)資源(如 CPU、內(nèi)存、磁盤 I/O 等)按照不同的規(guī)則,精準(zhǔn)地分配給各個進(jìn)程組。
想象一下,你的服務(wù)器是一個大型公寓,里面有很多租客(進(jìn)程)。cgroup 就像是公寓管理員,它把租客們分成不同的小組(控制組),然后為每個小組分配不同的資源額度。比如,有些小組可能被允許使用更多的 CPU 資源,就像一些租客被分配到了更大的房間;而有些小組可能被限制了內(nèi)存使用量,就像一些租客的水電費(fèi)被限制了額度。
Cgroups 最早是由 Google 的工程師在 2006 年發(fā)起的,當(dāng)時叫作 “進(jìn)程容器”(Process Container),在 2007 年被重命名為 cgroups,并在 2008 年合并到 2.6.24 版內(nèi)核后正式對外發(fā)布,這一階段的 cgroups 被稱為 “第一代 cgroups”。隨著技術(shù)的發(fā)展,在 2016 年 3 月發(fā)布的 Linux Kernel 4.5 版本中,搭載了由 Facebook 工程師重新編寫的 “第二代 cgroups”,使得管理員能更加清晰、精確地控制資源的層級關(guān)系。
內(nèi)存 cgroup 作為 cgroups 子系統(tǒng)之一,專注于內(nèi)存資源的精細(xì)化管理,是 Docker、Kubernetes 等容器技術(shù)實(shí)現(xiàn)內(nèi)存配額控制的核心底層技術(shù)。在容器化的世界里,每個容器都像是一個獨(dú)立的小公寓,內(nèi)存 cgroup 確保每個容器都能在自己的內(nèi)存配額內(nèi)運(yùn)行,不會出現(xiàn)某個容器過度占用內(nèi)存,導(dǎo)致其他容器無法正常工作的情況。
對于內(nèi)核開發(fā)者或好奇者來說,內(nèi)存Cgroup的實(shí)現(xiàn)代碼位于 Linux內(nèi)核源代碼樹 中。
- 主要代碼路徑: linux/mm/memcontrol.c
- 頭文件: linux/memcontrol.h
在Linux內(nèi)核運(yùn)行時,每個進(jìn)程的 task_struct(進(jìn)程描述符)中都有一個指針指向其所屬的cgroup。
// 在 task_struct 中(簡化表示)
struct task_struct {
// ...
struct css_set __rcu *cgroups; // 指向cgroup組的集合
// ...
};而每個cgroup本身在內(nèi)核中也是一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(struct mem_cgroup),它維護(hù)了該組內(nèi)所有進(jìn)程的內(nèi)存使用統(tǒng)計、限制、計數(shù)器等。所以,從任何一個進(jìn)程出發(fā),都可以找到其對應(yīng)的內(nèi)存Cgroup控制結(jié)構(gòu)。
1.2虛擬文件系統(tǒng)驅(qū)動的管控邏輯
解內(nèi)存 Cgroup 的定義,需深入其底層實(shí)現(xiàn)本質(zhì) —— 它并非獨(dú)立的 “程序”,而是依托 Linux 虛擬文件系統(tǒng)(VFS)構(gòu)建的配置與管控接口集合。其本質(zhì)邏輯可概括為兩點(diǎn):
(1)以文件為 “配置入口”:內(nèi)存 Cgroup 的所有管控操作,均通過/sys/fs/cgroup/memory/目錄下的虛擬文件完成。例如:
- 創(chuàng)建控制組:在該目錄下新建子目錄(如mkdir /sys/fs/cgroup/memory/app1),系統(tǒng)會自動生成memory.limit_in_bytes(內(nèi)存硬限制)、cgroup.procs(組內(nèi)進(jìn)程 PID 列表)等默認(rèn)配置文件;
- 修改內(nèi)存限制:向memory.limit_in_bytes寫入數(shù)值(如echo 100M > 該文件路徑),即可實(shí)時生效;
- 綁定進(jìn)程:向cgroup.procs寫入進(jìn)程 PID(如echo 1234 > 該文件路徑),即可將進(jìn)程納入控制組管控。
(2)內(nèi)核層 “實(shí)時攔截與調(diào)控”:當(dāng)進(jìn)程屬于某個內(nèi)存 Cgroup 時,其每一次內(nèi)存申請 / 釋放操作,都會被 Linux 內(nèi)核的內(nèi)存管理模塊攔截,對照該控制組的配置(如限制值、隔離規(guī)則)進(jìn)行校驗:
- 若申請內(nèi)存未超限制:正常分配;
- 若超硬限制:觸發(fā) OOM(Out Of Memory)機(jī)制,優(yōu)先殺死組內(nèi)低優(yōu)先級進(jìn)程;
- 若超軟限制且系統(tǒng)內(nèi)存緊張:優(yōu)先回收該組的超額內(nèi)存。
這種 “文件配置 + 內(nèi)核攔截” 的本質(zhì),讓內(nèi)存 Cgroup 具備了 “輕量化、可動態(tài)調(diào)整、無額外性能損耗” 的特點(diǎn)。
1.3內(nèi)存 cgroup 核心功能
(1)資源限制:通過memory.limit_in_bytes設(shè)定內(nèi)存使用上限,超出時觸發(fā) OOM Killer 機(jī)制終止進(jìn)程。就好比給每個進(jìn)程組分配了一個固定大小的 “內(nèi)存口袋”,當(dāng)進(jìn)程組往這個口袋里裝的東西(使用的內(nèi)存)超過口袋的容量時,OOM Killer(Out-Of-Memory Killer)就會像一個嚴(yán)厲的警察一樣出現(xiàn),把占用內(nèi)存過多的進(jìn)程給 “抓起來”(終止掉),以保證系統(tǒng)的穩(wěn)定性。例如,在一個多容器的 Kubernetes 集群中,如果某個容器的內(nèi)存使用量超過了memory.limit_in_bytes設(shè)定的上限,Kubernetes 就會根據(jù) OOM Killer 的策略,選擇終止這個容器內(nèi)的某些進(jìn)程,甚至直接殺掉整個容器,以釋放內(nèi)存資源給其他容器使用。
(2)使用統(tǒng)計:通過memory.usage_in_bytes實(shí)時監(jiān)控內(nèi)存占用,支持內(nèi)存 + 交換空間(memsw)的聯(lián)合統(tǒng)計。這就像是給每個進(jìn)程組配備了一個 “內(nèi)存用量計數(shù)器”,可以隨時查看進(jìn)程組當(dāng)前使用了多少內(nèi)存。同時,它還能把內(nèi)存和交換空間(就像是內(nèi)存不夠用時的 “備用倉庫”)的使用情況一起統(tǒng)計起來,讓管理員對進(jìn)程組的內(nèi)存使用情況有更全面的了解。比如,在排查系統(tǒng)性能問題時,管理員可以通過查看memory.usage_in_bytes和memory.memsw.usage_in_bytes的值,判斷某個進(jìn)程組是否存在內(nèi)存泄漏或者過度使用交換空間的情況。
(3)層級繼承:子控制組自動繼承父組的內(nèi)存配置,支持多級資源配額管理。這就像一個家族企業(yè),家族的規(guī)則(內(nèi)存配置)會從長輩(父控制組)傳遞給晚輩(子控制組)。通過這種層級繼承的方式,可以方便地實(shí)現(xiàn)對不同層次的進(jìn)程組進(jìn)行統(tǒng)一的內(nèi)存管理。例如,在一個大型的分布式系統(tǒng)中,可能會有多個服務(wù)模塊,每個服務(wù)模塊又包含多個子模塊??梢酝ㄟ^創(chuàng)建一個父控制組,為整個服務(wù)模塊設(shè)定一個總的內(nèi)存配額,然后為每個子模塊創(chuàng)建子控制組,這些子控制組會自動繼承父控制組的內(nèi)存配置,同時還可以根據(jù)子模塊的實(shí)際需求,對內(nèi)存配額進(jìn)行進(jìn)一步的微調(diào)。
(4)壓力通知:通過memory.pressure_level接口實(shí)時反饋內(nèi)存壓力狀態(tài),輔助內(nèi)核調(diào)度決策。這就像是給系統(tǒng)安裝了一個 “內(nèi)存壓力報警器”,當(dāng)內(nèi)存壓力達(dá)到一定程度時,它會及時發(fā)出警報,告訴內(nèi)核現(xiàn)在內(nèi)存有點(diǎn)緊張了,需要調(diào)整調(diào)度策略,比如優(yōu)先調(diào)度那些內(nèi)存使用較少的進(jìn)程,或者把一些不常用的數(shù)據(jù)從內(nèi)存中換到交換空間中,以緩解內(nèi)存壓力。比如,在一個高并發(fā)的 Web 服務(wù)器環(huán)境中,當(dāng)大量用戶同時訪問服務(wù)器時,內(nèi)存使用量可能會迅速上升。此時,memory.pressure_level就會向內(nèi)核反饋內(nèi)存壓力狀態(tài),內(nèi)核根據(jù)這個反饋,調(diào)整進(jìn)程的調(diào)度優(yōu)先級,保證關(guān)鍵業(yè)務(wù)進(jìn)程有足夠的內(nèi)存可用,避免因為內(nèi)存不足導(dǎo)致服務(wù)器響應(yīng)變慢甚至崩潰。
二、內(nèi)存 cgroup 創(chuàng)建核心流程解析
2.1cgroup 文件系統(tǒng)掛載(初始化)
在使用內(nèi)存 cgroup 之前,首先要進(jìn)行 cgroup 文件系統(tǒng)的掛載,這一步就像是為內(nèi)存 cgroup 搭建一個 “工作場地”。通過mount -t cgroup -o memory memory /sys/fs/cgroup/memory這條命令,我們把內(nèi)存子系統(tǒng)掛載到了/sys/fs/cgroup/memory這個指定目錄上。這就好比在一個大倉庫(文件系統(tǒng))里劃分出了一個專門的區(qū)域(掛載目錄),用來存放與內(nèi)存 cgroup 相關(guān)的 “物品”。
掛載完成后,在這個掛載目錄下會生成一系列文件接口,這些文件接口就像是一個個控制開關(guān),我們可以通過它們來對內(nèi)存 cgroup 進(jìn)行各種操作。比如memory.limit_in_bytes文件,它就像是一個 “內(nèi)存上限控制器”,我們可以通過修改這個文件的值,來設(shè)定某個控制組可以使用的最大內(nèi)存量;還有cgroup.procs文件,它就像是一個 “進(jìn)程收納箱”,我們把進(jìn)程的 PID(進(jìn)程標(biāo)識符,就像是每個進(jìn)程的身份證號碼)寫入這個文件,就可以把對應(yīng)的進(jìn)程加入到這個控制組中,讓這個進(jìn)程受到該控制組內(nèi)存配置的管理。
同時,在掛載時會自動創(chuàng)建一個根控制組(root cgroup)。這個根控制組就像是一個大家族的族長,它包含了系統(tǒng)中所有的進(jìn)程,是整個控制組層級結(jié)構(gòu)的根基。根控制組的內(nèi)存配置就像是家族的基本家規(guī),會作為后續(xù)創(chuàng)建的子控制組的默認(rèn)模板。當(dāng)我們創(chuàng)建子控制組時,如果沒有特別指定內(nèi)存配置,子控制組就會自動繼承根控制組的內(nèi)存配置。例如,根控制組設(shè)置了內(nèi)存使用的軟限制和硬限制,那么新創(chuàng)建的子控制組在沒有額外設(shè)置的情況下,也會遵循這些限制 。
內(nèi)存 cgroup 的 bash 腳本示例如下:
#!/bin/bash
# 檢查cgroup內(nèi)存子系統(tǒng)是否已掛載
if ! mountpoint -q /sys/fs/cgroup/memory; then
echo "正在掛載cgroup內(nèi)存子系統(tǒng)..."
sudo mount -t cgroup -o memory memory /sys/fs/cgroup/memory
if [ $? -ne 0 ]; then
echo "掛載失敗,請檢查權(quán)限和系統(tǒng)配置"
exit 1
fi
fi
# 創(chuàng)建一個名為my_memory_group的子控制組
CGROUP_DIR="/sys/fs/cgroup/memory/my_memory_group"
echo "創(chuàng)建子控制組: $CGROUP_DIR"
sudo mkdir -p $CGROUP_DIR
# 設(shè)置內(nèi)存限制為512MB (512 * 1024 * 1024 = 536870912字節(jié))
echo "設(shè)置內(nèi)存限制為512MB"
sudo sh -c "echo 536870912 > $CGROUP_DIR/memory.limit_in_bytes"
# 設(shè)置內(nèi)存軟限制為256MB
echo "設(shè)置內(nèi)存軟限制為256MB"
sudo sh -c "echo 268435456 > $CGROUP_DIR/memory.soft_limit_in_bytes"
# 啟動一個測試進(jìn)程并將其加入控制組
echo "啟動測試進(jìn)程并加入控制組..."
sleep 300 & # 啟動一個睡眠300秒的進(jìn)程作為示例
PID=$!
echo "將進(jìn)程PID $PID 加入控制組"
sudo sh -c "echo $PID > $CGROUP_DIR/cgroup.procs"
# 顯示控制組當(dāng)前狀態(tài)
echo "當(dāng)前控制組狀態(tài):"
echo "內(nèi)存限制: $(cat $CGROUP_DIR/memory.limit_in_bytes) 字節(jié)"
echo "內(nèi)存軟限制: $(cat $CGROUP_DIR/memory.soft_limit_in_bytes) 字節(jié)"
echo "控制組中的進(jìn)程: $(cat $CGROUP_DIR/cgroup.procs)"
# 等待用戶輸入后清理資源
read -p "按任意鍵停止測試并清理資源..."
# 終止測試進(jìn)程
echo "終止測試進(jìn)程 $PID"
sudo kill $PID 2>/dev/null
# 清理創(chuàng)建的控制組
echo "清理控制組 $CGROUP_DIR"
sudo rmdir $CGROUP_DIR
echo "演示完成"這個腳本演示了內(nèi)存 cgroup 的基本使用流程:
- 首先檢查 cgroup 內(nèi)存子系統(tǒng)是否已掛載,如果沒有則進(jìn)行掛載
- 創(chuàng)建一個名為my_memory_group的子控制組
- 為該控制組設(shè)置 512MB 的內(nèi)存硬限制和 256MB 的內(nèi)存軟限制
- 啟動一個測試進(jìn)程(sleep 300)并將其 PID 加入到控制組中
- 顯示當(dāng)前控制組的配置信息
- 等待用戶確認(rèn)后,清理創(chuàng)建的進(jìn)程和控制組
使用時需要注意:
- 運(yùn)行腳本需要 root 權(quán)限(使用 sudo)
- 不同系統(tǒng)的 cgroup 路徑可能略有差異
- 清理步驟很重要,避免留下無用的控制組
- 實(shí)際使用中可以根據(jù)需要修改內(nèi)存限制值和測試進(jìn)程
2.2控制組創(chuàng)建與參數(shù)配置
當(dāng) cgroup 文件系統(tǒng)掛載完成后,就可以開始創(chuàng)建具體的控制組并進(jìn)行參數(shù)配置了。創(chuàng)建子控制組非常簡單,只需要在掛載目錄下創(chuàng)建一個子目錄就行。比如執(zhí)行sudo mkdir /sys/fs/cgroup/memory/myapp,這樣就在/sys/fs/cgroup/memory目錄下創(chuàng)建了一個名為myapp的子控制組。這就好比在前面劃分好的專門區(qū)域里,又隔出了一個小房間,用來放置特定的 “物品”(進(jìn)程)。在創(chuàng)建這個子目錄的同時,內(nèi)核會自動為這個控制組生成專屬的配置文件,就像是給這個小房間配備了一套專屬的管理規(guī)則。
接下來就是關(guān)鍵的參數(shù)配置環(huán)節(jié)了,這一步?jīng)Q定了控制組內(nèi)進(jìn)程的內(nèi)存使用規(guī)則。
- 硬限制設(shè)置:通過echo 1073741824 > memory.limit_in_bytes這樣的命令,我們可以設(shè)置內(nèi)存使用的硬限制。這里的1073741824是字節(jié)數(shù),換算過來就是 1GB,這就意味著這個控制組內(nèi)的所有進(jìn)程,總共最多只能使用 1GB 的內(nèi)存。當(dāng)進(jìn)程使用的內(nèi)存達(dá)到這個限制時,如果再嘗試申請更多內(nèi)存,就會觸發(fā) OOM Killer 機(jī)制,相關(guān)進(jìn)程可能會被終止,以保證系統(tǒng)的穩(wěn)定性。就像一個杯子,它的容量是固定的(硬限制),當(dāng)水(內(nèi)存使用)裝滿了這個杯子,就再也裝不下更多的水了,再往里倒水(申請內(nèi)存)就會溢出來(觸發(fā) OOM Killer) 。
- 軟限制設(shè)置:memory.soft_limit_in_bytes參數(shù)用于設(shè)置軟限制。軟限制允許進(jìn)程在一定時間內(nèi)臨時超額使用內(nèi)存,這就像是給進(jìn)程一個 “彈性額度”。比如某個進(jìn)程在業(yè)務(wù)高峰期,可能會短暫地需要更多內(nèi)存,軟限制就可以滿足這種臨時需求。但是這個彈性額度是有限的,它需要配合memory.limit_in_bytes硬限制來使用。當(dāng)進(jìn)程使用的內(nèi)存超過軟限制但還未達(dá)到硬限制時,系統(tǒng)會根據(jù)內(nèi)存壓力等情況,對進(jìn)程進(jìn)行相應(yīng)的處理,比如降低進(jìn)程的內(nèi)存分配優(yōu)先級,或者在內(nèi)存緊張時回收部分內(nèi)存。這就好比信用卡有一個固定的信用額度(硬限制),同時還有一個臨時額度(軟限制),在臨時額度內(nèi)可以暫時多消費(fèi)一些,但最終還是不能超過固定信用額度 。
- 交換空間控制:通過echo 0 > memory.swappiness命令,可以禁止控制組內(nèi)的進(jìn)程使用交換分區(qū)。交換分區(qū)就像是內(nèi)存不夠用時的 “備用倉庫”,把暫時不用的數(shù)據(jù)存放到交換分區(qū),以騰出內(nèi)存空間。但是使用交換分區(qū)會帶來性能損耗,因為從磁盤(交換分區(qū)所在的存儲設(shè)備)讀寫數(shù)據(jù)比從內(nèi)存讀寫數(shù)據(jù)要慢得多。所以當(dāng)我們設(shè)置memory.swappiness為 0 時,就像是把這個 “備用倉庫” 的門給關(guān)上了,確保內(nèi)存限制的嚴(yán)格生效,避免因為使用交換分區(qū)而導(dǎo)致內(nèi)存限制被繞過的情況。例如,在對性能要求極高的實(shí)時計算場景中,就需要嚴(yán)格控制內(nèi)存使用,避免使用交換分區(qū)帶來的性能波動 。
2.3進(jìn)程關(guān)聯(lián)與動態(tài)管理
創(chuàng)建好控制組并配置好參數(shù)后,就需要把進(jìn)程關(guān)聯(lián)到控制組中,讓進(jìn)程受到控制組內(nèi)存配置的管理,并且在運(yùn)行過程中還需要對進(jìn)程進(jìn)行動態(tài)管理。
添加進(jìn)程到控制組的操作也很簡單,通過echo <PID> > cgroup.procs命令,把目標(biāo)進(jìn)程的 PID 寫入控制組的cgroup.procs文件中,就可以把這個進(jìn)程添加到對應(yīng)的控制組中。這就像是把一個員工(進(jìn)程)分配到一個特定的項目組(控制組)中,讓他遵循這個項目組的規(guī)則(內(nèi)存配置)工作。這個操作還支持批量添加,只需要不斷地把不同進(jìn)程的 PID 追加寫入cgroup.procs文件即可,就像一次把多個員工分配到同一個項目組。
同時,如果開啟了cgroup.clone_children選項,子進(jìn)程會自動繼承父進(jìn)程所在的控制組。這就好比一個家族企業(yè)中,父親(父進(jìn)程)在某個部門(控制組)工作,他的子女(子進(jìn)程)出生后(創(chuàng)建后)也會自動進(jìn)入這個部門工作。例如,在一個 Web 服務(wù)器應(yīng)用中,主進(jìn)程創(chuàng)建了多個子進(jìn)程來處理不同的用戶請求,開啟cgroup.clone_children后,這些子進(jìn)程會自動繼承主進(jìn)程所在控制組的內(nèi)存配置,確保整個應(yīng)用的內(nèi)存使用是可控的 。
在進(jìn)程運(yùn)行過程中,還支持運(yùn)行中進(jìn)程跨控制組遷移,這一特性在很多場景下都非常有用。通過修改cgroup.procs文件,把進(jìn)程的 PID 從一個控制組的cgroup.procs文件移動到另一個控制組的cgroup.procs文件中,就可以實(shí)現(xiàn)進(jìn)程的跨控制組遷移。這就像是一個員工從一個項目組調(diào)到另一個項目組,他需要遵循新的項目組規(guī)則(新控制組的內(nèi)存配置)。例如,在微服務(wù)架構(gòu)中,當(dāng)某個微服務(wù)的負(fù)載突然增加,需要更多的內(nèi)存資源時,可以通過將該微服務(wù)相關(guān)的進(jìn)程遷移到一個內(nèi)存配額更大的控制組中,實(shí)現(xiàn)動態(tài)資源重分配,保證微服務(wù)的正常運(yùn)行,這對于實(shí)現(xiàn)微服務(wù)的彈性擴(kuò)縮容非常關(guān)鍵 。
進(jìn)程添加到控制組、子進(jìn)程繼承以及進(jìn)程遷移功能的 bash 腳本示例:
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 檢查并掛載cgroup內(nèi)存子系統(tǒng)
CGROUP_MOUNT="/sys/fs/cgroup/memory"
if ! mountpoint -q $CGROUP_MOUNT; then
echo "掛載cgroup內(nèi)存子系統(tǒng)..."
mount -t cgroup -o memory memory $CGROUP_MOUNT
if [ $? -ne 0 ]; then
echo "掛載失敗"
exit 1
fi
fi
# 創(chuàng)建兩個測試控制組
GROUP1="$CGROUP_MOUNT/group1"
GROUP2="$CGROUP_MOUNT/group2"
echo "創(chuàng)建控制組..."
mkdir -p $GROUP1 $GROUP2
# 為不同控制組設(shè)置不同的內(nèi)存限制
echo "配置控制組內(nèi)存限制..."
echo 268435456 > $GROUP1/memory.limit_in_bytes # 256MB
echo 536870912 > $GROUP2/memory.limit_in_bytes # 512MB
# 1. 演示添加進(jìn)程到控制組
echo -e "\n=== 演示添加進(jìn)程到控制組 ==="
sleep 300 &
PID1=$!
echo "將進(jìn)程 $PID1 添加到 group1"
echo $PID1 > $GROUP1/cgroup.procs
echo "group1 中的進(jìn)程: $(cat $GROUP1/cgroup.procs)"
# 2. 演示批量添加進(jìn)程
echo -e "\n=== 演示批量添加進(jìn)程 ==="
sleep 300 &
PID2=$!
sleep 300 &
PID3=$!
echo "批量添加進(jìn)程 $PID2 和 $PID3 到 group1"
echo $PID2 >> $GROUP1/cgroup.procs
echo $PID3 >> $GROUP1/cgroup.procs
echo "group1 中的進(jìn)程: $(cat $GROUP1/cgroup.procs)"
# 3. 演示子進(jìn)程繼承控制組 (cgroup.clone_children)
echo -e "\n=== 演示子進(jìn)程繼承控制組 ==="
echo "開啟 group1 的 clone_children 選項"
echo 1 > $GROUP1/cgroup.clone_children
echo "在 group1 中啟動一個父進(jìn)程,該進(jìn)程會創(chuàng)建子進(jìn)程"
(
# 這個腳本在group1中運(yùn)行,會創(chuàng)建子進(jìn)程
echo "父進(jìn)程 PID: $$ 在控制組: $(cat /proc/$$/cgroup | grep memory | cut -d: -f3)"
sleep 300 & # 子進(jìn)程1
sleep 300 & # 子進(jìn)程2
wait
) &
PID_PARENT=$!
echo $PID_PARENT > $GROUP1/cgroup.procs
# 等待子進(jìn)程創(chuàng)建
sleep 2
echo "group1 中的進(jìn)程(包含子進(jìn)程): $(cat $GROUP1/cgroup.procs)"
# 4. 演示進(jìn)程遷移
echo -e "\n=== 演示進(jìn)程遷移 ==="
echo "將進(jìn)程 $PID1 從 group1 遷移到 group2"
echo $PID1 > $GROUP2/cgroup.procs
echo "group1 中的進(jìn)程: $(cat $GROUP1/cgroup.procs)"
echo "group2 中的進(jìn)程: $(cat $GROUP2/cgroup.procs)"
# 等待用戶輸入,觀察效果
read -p "按任意鍵繼續(xù)清理資源..."
# 清理操作
echo -e "\n清理資源..."
# 終止所有測試進(jìn)程
echo "終止測試進(jìn)程..."
for pid in $(cat $GROUP1/cgroup.procs $GROUP2/cgroup.procs); do
kill $pid 2>/dev/null
done
# 刪除控制組
echo "刪除控制組..."
rmdir $GROUP1 $GROUP2
echo "演示完成"這個腳本展示了 cgroup 進(jìn)程管理的幾個關(guān)鍵功能:
- 添加進(jìn)程到控制組:通過將 PID 寫入 cgroup.procs 文件,將進(jìn)程納入控制組管理
- 批量添加進(jìn)程:通過追加方式將多個 PID 寫入 cgroup.procs,實(shí)現(xiàn)批量管理
- 子進(jìn)程繼承:通過開啟 cgroup.clone_children 選項,使子進(jìn)程自動繼承父進(jìn)程的控制組
- 進(jìn)程遷移:通過將 PID 從一個控制組的 cgroup.procs 文件移動到另一個,實(shí)現(xiàn)進(jìn)程在不同控制組間的遷移
腳本使用了兩個不同內(nèi)存限制的控制組(256MB 和 512MB),以便清晰展示進(jìn)程在不同控制組間遷移的效果。實(shí)際使用時,可以根據(jù)需要調(diào)整內(nèi)存限制值和測試進(jìn)程類型。
運(yùn)行此腳本需要 root 權(quán)限,因為 cgroup 操作通常需要管理員權(quán)限。完成演示后,腳本會自動清理創(chuàng)建的進(jìn)程和控制組,避免系統(tǒng)資源殘留。
三、內(nèi)存 cgroup 內(nèi)核實(shí)現(xiàn)關(guān)鍵機(jī)制
3.1內(nèi)存統(tǒng)計與監(jiān)控原理
(1)數(shù)據(jù)結(jié)構(gòu)支撐:在內(nèi)核層面,內(nèi)存 cgroup 的狀態(tài)維護(hù)依賴于一系列精心設(shè)計的數(shù)據(jù)結(jié)構(gòu),其中struct mem_cgroup扮演著核心角色。它就像是內(nèi)存 cgroup 的 “管家”,全面記錄著控制組的各種內(nèi)存相關(guān)狀態(tài)信息 。struct mem_cgroup中包含一個page_counter類型的成員,專門用于精準(zhǔn)統(tǒng)計內(nèi)存使用量,它會實(shí)時跟蹤控制組內(nèi)進(jìn)程對內(nèi)存的占用情況,就像一個精準(zhǔn)的 “內(nèi)存用量計數(shù)器”,時刻更新著內(nèi)存使用的數(shù)值。
同時,vmstats_percpu數(shù)據(jù)結(jié)構(gòu)記錄各 CPU 核心的內(nèi)存開銷情況,像常駐內(nèi)存集(RSS,Resident Set Size)、頁緩存(Page Cache)等關(guān)鍵指標(biāo)都被詳細(xì)記錄其中。不同 CPU 核心上的內(nèi)存使用情況可能會因為進(jìn)程的分布和執(zhí)行情況而有所不同,vmstats_percpu就像是為每個 CPU 核心配備了一個專屬的 “內(nèi)存使用記錄員”,分別記錄它們的內(nèi)存開銷,為系統(tǒng)提供了更細(xì)致的內(nèi)存使用數(shù)據(jù),幫助系統(tǒng)管理員更全面地了解內(nèi)存使用的分布情況 。
(2)實(shí)時統(tǒng)計接口:內(nèi)存 cgroup 提供了豐富的實(shí)時統(tǒng)計接口,方便用戶獲取內(nèi)存使用的詳細(xì)信息。memory.stat文件就是其中一個重要的接口,它提供了極為詳細(xì)的統(tǒng)計數(shù)據(jù),涵蓋了活躍匿名內(nèi)存(active_anon)、非活躍文件內(nèi)存(inactive_file)等多個維度。活躍匿名內(nèi)存反映了正在被進(jìn)程積極使用且未與文件關(guān)聯(lián)的內(nèi)存量,而非活躍文件內(nèi)存則表示那些雖然與文件相關(guān)聯(lián),但當(dāng)前處于非活躍狀態(tài)的內(nèi)存量 。這些數(shù)據(jù)對于深入分析內(nèi)存使用情況非常關(guān)鍵,比如通過對比活躍匿名內(nèi)存和非活躍文件內(nèi)存的大小,系統(tǒng)管理員可以判斷內(nèi)存中的數(shù)據(jù)是更多地被進(jìn)程直接使用,還是更多地以文件緩存的形式存在,從而為內(nèi)存優(yōu)化提供依據(jù) 。
memory.max_usage_in_bytes文件記錄了控制組歷史峰值內(nèi)存使用量,這一數(shù)據(jù)在容量規(guī)劃和異常診斷中發(fā)揮著重要作用。在進(jìn)行系統(tǒng)容量規(guī)劃時,了解控制組曾經(jīng)達(dá)到的最大內(nèi)存使用量,可以幫助管理員合理分配內(nèi)存資源,避免未來因為內(nèi)存不足而導(dǎo)致系統(tǒng)故障。
例如,在一個電商系統(tǒng)中,在促銷活動期間,某些服務(wù)的內(nèi)存使用量可能會大幅增加,通過查看memory.max_usage_in_bytes,管理員可以提前預(yù)估這些服務(wù)在未來類似活動中的內(nèi)存需求,為系統(tǒng)添加足夠的內(nèi)存資源,確保系統(tǒng)在高并發(fā)情況下的穩(wěn)定性 。在異常診斷方面,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存相關(guān)的問題時,對比當(dāng)前內(nèi)存使用量和歷史峰值內(nèi)存使用量,可以幫助管理員快速判斷是否存在內(nèi)存泄漏或者其他異常情況。如果當(dāng)前內(nèi)存使用量接近或超過歷史峰值,且持續(xù)上升,很可能存在內(nèi)存泄漏問題,需要進(jìn)一步排查和解決 。通過 cgroup 跟蹤內(nèi)存使用峰值,幫助進(jìn)行資源規(guī)劃和異常診斷:
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 配置監(jiān)控參數(shù)
CGROUP_MOUNT="/sys/fs/cgroup/memory"
ECOMMERCE_GROUP="$CGROUP_MOUNT/ecommerce_service"
MONITOR_INTERVAL=2 # 監(jiān)控間隔(秒)
DURATION=30 # 監(jiān)控時長(秒)
# 清理殘留資源
cleanup() {
# 終止監(jiān)控進(jìn)程
if [ -n "$MONITORED_PID" ]; then
kill $MONITORED_PID 2>/dev/null
echo -e "\n已終止模擬服務(wù)進(jìn)程"
fi
# 刪除控制組
if [ -d "$ECOMMERCE_GROUP" ]; then
rmdir $ECOMMERCE_GROUP
echo "已刪除控制組"
fi
}
# 確保cgroup已掛載
if ! mountpoint -q $CGROUP_MOUNT; then
echo "掛載cgroup內(nèi)存子系統(tǒng)..."
mount -t cgroup -o memory memory $CGROUP_MOUNT || {
echo "掛載失敗"
exit 1
}
fi
# 創(chuàng)建電商服務(wù)控制組
mkdir -p $ECOMMERCE_GROUP || {
echo "創(chuàng)建控制組失敗"
exit 1
}
# 設(shè)置內(nèi)存限制(模擬促銷期間可能需要的內(nèi)存)
echo "設(shè)置服務(wù)內(nèi)存限制為1GB"
echo 1073741824 > $ECOMMERCE_GROUP/memory.limit_in_bytes # 1GB
# 啟動模擬電商服務(wù)進(jìn)程(會逐漸增加內(nèi)存使用)
echo "啟動模擬電商服務(wù)進(jìn)程..."
(
# 模擬內(nèi)存使用增長,模擬促銷期間的內(nèi)存變化
data=""
while true; do
# 每次循環(huán)增加內(nèi)存使用
data+=$(printf "x%.0s" {1..100000}) # 增加數(shù)據(jù)量
sleep 1
done
) &
MONITORED_PID=$!
# 將進(jìn)程加入控制組
echo $MONITORED_PID > $ECOMMERCE_GROUP/cgroup.procs
echo "服務(wù)進(jìn)程PID: $MONITORED_PID 已加入監(jiān)控"
# 監(jiān)控內(nèi)存使用情況
echo -e "\n開始監(jiān)控內(nèi)存使用(持續(xù)$DURATION秒)..."
echo "時間(秒) | 當(dāng)前使用(MB) | 峰值使用(MB)"
echo "----------------------------------------"
end_time=$((SECONDS + DURATION))
while [ $SECONDS -lt $end_time ]; do
# 獲取當(dāng)前內(nèi)存使用量
current=$(cat $ECOMMERCE_GROUP/memory.usage_in_bytes)
# 獲取內(nèi)存使用峰值
peak=$(cat $ECOMMERCE_GROUP/memory.max_usage_in_bytes)
# 轉(zhuǎn)換為MB
current_mb=$((current / 1024 / 1024))
peak_mb=$((peak / 1024 / 1024))
# 顯示監(jiān)控數(shù)據(jù)
echo "$((SECONDS - (end_time - DURATION))) | $current_mb | $peak_mb"
sleep $MONITOR_INTERVAL
done
# 分析結(jié)果
echo -e "\n=== 內(nèi)存使用分析 ==="
current=$(cat $ECOMMERCE_GROUP/memory.usage_in_bytes)
peak=$(cat $ECOMMERCE_GROUP/memory.max_usage_in_bytes)
limit=$(cat $ECOMMERCE_GROUP/memory.limit_in_bytes)
current_mb=$((current / 1024 / 1024))
peak_mb=$((peak / 1024 / 1024))
limit_mb=$((limit / 1024 / 1024))
echo "內(nèi)存限制: $limit_mb MB"
echo "最終內(nèi)存使用: $current_mb MB"
echo "內(nèi)存使用峰值: $peak_mb MB"
# 異常判斷邏輯
usage_ratio=$((peak * 100 / limit))
if [ $usage_ratio -gt 90 ]; then
echo "警告: 內(nèi)存使用峰值已超過限制的90%,系統(tǒng)可能面臨內(nèi)存壓力"
elif [ $usage_ratio -gt 70 ]; then
echo "提示: 內(nèi)存使用峰值已超過限制的70%,建議關(guān)注資源使用情況"
fi
# 檢查是否存在內(nèi)存泄漏跡象(當(dāng)前使用接近峰值且持續(xù)增長)
if [ $current -gt $((peak * 95 / 100)) ]; then
echo "警告: 當(dāng)前內(nèi)存使用接近歷史峰值,可能存在內(nèi)存泄漏風(fēng)險"
fi
# 清理資源
cleanup
echo -e "\n監(jiān)控結(jié)束"通過這個腳本,管理員可以:
- 觀察服務(wù)在高負(fù)載下的內(nèi)存峰值,為未來促銷活動做資源規(guī)劃
- 及時發(fā)現(xiàn)內(nèi)存使用異常(如接近限制或持續(xù)增長)
- 輔助判斷是否存在內(nèi)存泄漏問題
腳本使用了 1GB 的內(nèi)存限制作為示例,實(shí)際使用時可根據(jù)系統(tǒng)情況調(diào)整。監(jiān)控間隔和時長也可以根據(jù)需要修改,以獲得更精確的內(nèi)存使用數(shù)據(jù)。
3.2內(nèi)存限制與 OOM 處理流程
(1)分配攔截機(jī)制:當(dāng)進(jìn)程向系統(tǒng)申請內(nèi)存時,內(nèi)核會立即啟動嚴(yán)格的檢查機(jī)制。內(nèi)核會檢查memory.limit_in_bytes所設(shè)定的內(nèi)存限制值,這個值就像是一道 “內(nèi)存警戒線”,一旦進(jìn)程申請的內(nèi)存量加上當(dāng)前已使用的內(nèi)存量超過了這條警戒線,就會觸發(fā)__memcgroup_oom_kill函數(shù)。這個函數(shù)就像是一個 “內(nèi)存警察”,專門負(fù)責(zé)處理內(nèi)存超額的情況 。
__memcgroup_oom_kill函數(shù)會在控制組內(nèi)仔細(xì)選擇消耗內(nèi)存最多的進(jìn)程,將其作為 OOM(Out-Of-Memory)終止的目標(biāo)。這就像是在一個班級里,如果資源不夠了,老師會先讓那些占用資源最多的學(xué)生 “讓出資源”,以保證整個班級(系統(tǒng))的正常運(yùn)轉(zhuǎn)。在實(shí)際的服務(wù)器環(huán)境中,可能會有多個進(jìn)程同時運(yùn)行在一個控制組內(nèi),當(dāng)內(nèi)存不足時,終止那些占用內(nèi)存最多的進(jìn)程,可以迅速釋放大量內(nèi)存,避免整個系統(tǒng)因為內(nèi)存耗盡而崩潰,保證其他重要進(jìn)程能夠繼續(xù)運(yùn)行 。
(2)OOM 策略配置:內(nèi)存 cgroup 允許用戶通過memory.oom_control 文件來靈活調(diào)整 OOM 處理策略。當(dāng)memory.oom_control的值被設(shè)置為 0 時,默認(rèn)啟用OOM Killer機(jī)制。在這種情況下,一旦內(nèi)存使用超出限制,OOM Killer 就會按照前面提到的方式,果斷選擇并終止占用內(nèi)存最多的進(jìn)程,以恢復(fù)系統(tǒng)的內(nèi)存平衡 。這是一種比較激進(jìn)的策略,雖然可能會導(dǎo)致某些進(jìn)程的突然終止,但可以快速解決內(nèi)存不足的問題,保證系統(tǒng)的穩(wěn)定性 。
而當(dāng)memory.oom_control的值被設(shè)置為 1 時,OOM Killer 機(jī)制將被禁用。此時,如果進(jìn)程遇到內(nèi)存不足的情況,它不會被直接終止,而是會進(jìn)入阻塞等待狀態(tài),就像一個人在排隊等待資源,直到有足夠的內(nèi)存可用或者超時。這種策略在一些對進(jìn)程連續(xù)性要求較高的場景中非常有用,比如數(shù)據(jù)庫服務(wù),不希望因為內(nèi)存不足而突然終止進(jìn)程,導(dǎo)致數(shù)據(jù)丟失或不一致 。但是,這種策略也存在一定的風(fēng)險,如果內(nèi)存緊張的情況持續(xù)時間過長,可能會導(dǎo)致整個系統(tǒng)的性能下降,甚至出現(xiàn)死鎖等問題,因為多個進(jìn)程都在等待內(nèi)存資源,而內(nèi)存資源又一直無法滿足它們的需求 。
3.3層級化資源管理
(1)繼承與覆蓋規(guī)則:內(nèi)存 cgroup 的層級化資源管理基于一套清晰的繼承與覆蓋規(guī)則。子控制組在創(chuàng)建時,會默認(rèn)繼承父組的內(nèi)存配置,這就像孩子會繼承父母的一些特征一樣。父組設(shè)置了內(nèi)存使用的軟限制和硬限制,子控制組在沒有額外設(shè)置的情況下,會自動遵循這些限制 。這種繼承機(jī)制大大簡化了資源管理的復(fù)雜度,對于一個擁有多個子系統(tǒng)的大型應(yīng)用,只需要在父控制組中設(shè)置好整體的內(nèi)存策略,各個子系統(tǒng)對應(yīng)的子控制組就會自動遵循,確保整個應(yīng)用的內(nèi)存使用符合統(tǒng)一的規(guī)范 。
不過,子控制組也具有一定的自主性,它可以根據(jù)自身的特殊需求,單獨(dú)設(shè)置更嚴(yán)格的內(nèi)存限制。比如,在一個微服務(wù)架構(gòu)中,某個微服務(wù)可能對內(nèi)存的需求比較特殊,雖然父控制組為所有微服務(wù)設(shè)置了一個通用的內(nèi)存上限,但這個微服務(wù)所在的子控制組可以進(jìn)一步降低這個上限,以確保該微服務(wù)在內(nèi)存使用上更加保守,避免因為內(nèi)存使用不當(dāng)而影響整個系統(tǒng)的穩(wěn)定性 。需要注意的是,子組內(nèi)存上限絕對不得超過父組,這是層級化資源管理的一個基本原則,就像在一個公司中,部門的資源配額不能超過整個公司分配給它的總配額,否則會導(dǎo)致資源分配的混亂 。通過這種繼承與覆蓋的規(guī)則,內(nèi)存 cgroup 形成了一個清晰的樹狀資源配額體系,從根控制組開始,一層一層地向下傳遞和細(xì)化內(nèi)存配置,實(shí)現(xiàn)了對系統(tǒng)內(nèi)存資源的高效管理 。
(2)跨層級統(tǒng)計:memory.use_hierarchy參數(shù)在內(nèi)存 cgroup 的跨層級統(tǒng)計中起著關(guān)鍵作用,它就像是一個 “統(tǒng)計開關(guān)”,控制是否聚合子組內(nèi)存使用量。當(dāng)memory.use_hierarchy被設(shè)置為 1 時,系統(tǒng)會自動聚合子組的內(nèi)存使用量,將其納入到父組的統(tǒng)計中 。這在多級資源監(jiān)控的場景中非常實(shí)用,比如在一個大型的 Kubernetes 集群中,集群可以看作是一個根控制組,下面包含多個節(jié)點(diǎn),每個節(jié)點(diǎn)又可以看作是一個子控制組,節(jié)點(diǎn)上運(yùn)行的容器則是更下一層的子控制組 。通過啟用memory.use_hierarchy,管理員可以從集群層面快速了解整個集群的內(nèi)存使用情況,包括各個節(jié)點(diǎn)以及節(jié)點(diǎn)上容器的內(nèi)存使用總和,方便進(jìn)行整體的資源評估和管理 。
在容器編排系統(tǒng)中,通過查看聚合后的內(nèi)存使用數(shù)據(jù),管理員可以直觀地了解到整個集群的內(nèi)存負(fù)載情況,判斷是否需要進(jìn)行節(jié)點(diǎn)的擴(kuò)容或縮容操作。如果發(fā)現(xiàn)某個節(jié)點(diǎn)的內(nèi)存使用量過高,且已經(jīng)接近或超過了其配額,可以考慮將部分容器遷移到其他內(nèi)存資源較為充裕的節(jié)點(diǎn)上,以實(shí)現(xiàn)集群內(nèi)存資源的均衡分配 。而當(dāng)memory.use_hierarchy被設(shè)置為 0 時,系統(tǒng)將只統(tǒng)計當(dāng)前控制組自身的內(nèi)存使用量,不包含子組的內(nèi)存使用情況 。這種設(shè)置在一些需要單獨(dú)關(guān)注某個控制組內(nèi)存使用的場景中很有用,比如在調(diào)試某個特定的容器時,只關(guān)心這個容器自身的內(nèi)存使用情況,而不希望受到其他容器的干擾 。
四、內(nèi)存 cgroup 與其他子系統(tǒng)的協(xié)同工作
4.1cgroup 與 CPU 子系統(tǒng)的資源協(xié)同
在復(fù)雜的系統(tǒng)環(huán)境中,內(nèi)存 cgroup 很少單獨(dú)工作,它需要與其他子系統(tǒng)緊密協(xié)作,才能實(shí)現(xiàn)系統(tǒng)資源的高效利用和穩(wěn)定運(yùn)行。
內(nèi)存限制常配合 CPU 配額(cpu.cfs_quota_us)使用,這在很多場景下都非常關(guān)鍵。以容器化微服務(wù)資源隔離為例,在一個容器集群中,每個微服務(wù)都以容器的形式運(yùn)行,并且被分配到了不同的控制組中。通過設(shè)置memory.limit_in_bytes來限制每個容器的內(nèi)存使用上限,同時配合cpu.cfs_quota_us來限制容器可以使用的 CPU 時間,這樣可以確保計算密集型任務(wù)在內(nèi)存受限的同時不會占用過多 CPU 資源。假設(shè)一個數(shù)據(jù)分析微服務(wù),它在處理大量數(shù)據(jù)時需要消耗較多的 CPU 資源,但如果沒有限制,它可能會把所有 CPU 資源都占用,導(dǎo)致其他微服務(wù)無法正常工作。通過設(shè)置合理的cpu.cfs_quota_us,比如每 100ms 周期內(nèi)最多使用 50ms CPU 時間,就可以保證這個數(shù)據(jù)分析微服務(wù)在使用適量 CPU 資源的同時,不會影響其他微服務(wù)的正常運(yùn)行 。
通過 cpuset 子系統(tǒng)將內(nèi)存控制組內(nèi)的進(jìn)程綁定到特定 CPU 核心 / 內(nèi)存節(jié)點(diǎn),是提升系統(tǒng)性能的重要手段。在多核心 CPU 和 NUMA(Non-Uniform Memory Access,非統(tǒng)一內(nèi)存訪問)架構(gòu)的系統(tǒng)中,不同 CPU 核心訪問內(nèi)存的速度可能會有很大差異。將內(nèi)存控制組內(nèi)的進(jìn)程綁定到特定 CPU 核心和內(nèi)存節(jié)點(diǎn),可以減少跨 NUMA 節(jié)點(diǎn)訪問延遲,提升數(shù)據(jù)局部性 。例如,在一個大型數(shù)據(jù)庫系統(tǒng)中,將數(shù)據(jù)庫相關(guān)的進(jìn)程綁定到特定的 CPU 核心和內(nèi)存節(jié)點(diǎn),這些進(jìn)程在訪問內(nèi)存時,可以直接訪問本地內(nèi)存節(jié)點(diǎn),而不需要跨節(jié)點(diǎn)訪問,大大提高了內(nèi)存訪問速度,從而提升了數(shù)據(jù)庫系統(tǒng)的整體性能 。在實(shí)際操作中,可以通過修改cpuset.cpus和cpuset.mems文件來實(shí)現(xiàn)進(jìn)程的綁定。比如echo 0-3 > /sys/fs/cgroup/cpuset/myapp/cpuset.cpus表示將myapp控制組內(nèi)的進(jìn)程綁定到 CPU 核心 0 到 3 上,echo 0 > /sys/fs/cgroup/cpuset/myapp/cpuset.mems表示將進(jìn)程綁定到內(nèi)存節(jié)點(diǎn) 0 上 。
cgroup 內(nèi)存子系統(tǒng)與 CPU 子系統(tǒng)協(xié)同工作的示例如下:
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 配置控制組路徑
CGROUP_BASE="/sys/fs/cgroup"
APP_GROUP="my_app_group"
# 各子系統(tǒng)控制組路徑
MEM_GROUP="$CGROUP_BASE/memory/$APP_GROUP"
CPU_GROUP="$CGROUP_BASE/cpu/$APP_GROUP"
CPUSET_GROUP="$CGROUP_BASE/cpuset/$APP_GROUP"
# 清理函數(shù)
cleanup() {
# 終止測試進(jìn)程
if [ -n "$TEST_PID" ]; then
kill $TEST_PID 2>/dev/null
echo -e "\n已終止測試進(jìn)程"
fi
# 刪除控制組
if [ -d "$MEM_GROUP" ]; then
rmdir $MEM_GROUP
fi
if [ -d "$CPU_GROUP" ]; then
rmdir $CPU_GROUP
fi
if [ -d "$CPUSET_GROUP" ]; then
rmdir $CPUSET_GROUP
fi
echo "已清理控制組資源"
}
# 檢查并掛載必要的cgroup子系統(tǒng)
mount_cgroups() {
for subsys in memory cpu cpuset; do
mount_point="$CGROUP_BASE/$subsys"
if ! mountpoint -q $mount_point; then
echo "掛載$subsys子系統(tǒng)到$mount_point..."
mkdir -p $mount_point
mount -t cgroup -o $subsys $subsys $mount_point || {
echo "$subsys子系統(tǒng)掛載失敗"
exit 1
}
fi
done
}
# 創(chuàng)建控制組
create_cgroups() {
echo "創(chuàng)建控制組..."
mkdir -p $MEM_GROUP $CPU_GROUP $CPUSET_GROUP || {
echo "創(chuàng)建控制組失敗"
exit 1
}
}
# 配置內(nèi)存限制
configure_memory() {
echo "配置內(nèi)存限制為512MB..."
echo 536870912 > $MEM_GROUP/memory.limit_in_bytes # 512MB
}
# 配置CPU配額
configure_cpu() {
echo "配置CPU配額:每100ms周期內(nèi)最多使用50ms..."
echo 100000 > $CPU_GROUP/cpu.cfs_period_us # 周期100ms
echo 50000 > $CPU_GROUP/cpu.cfs_quota_us # 配額50ms
}
# 配置CPU和內(nèi)存節(jié)點(diǎn)綁定
configure_cpuset() {
# 獲取系統(tǒng)CPU核心數(shù)
CPU_CORES=$(grep -c ^processor /proc/cpuinfo)
MAX_CPU=$((CPU_CORES - 1))
# 如果CPU核心數(shù)足夠,綁定到0-3核心,否則綁定到可用核心
if [ $CPU_CORES -ge 4 ]; then
CPU_LIST="0-3"
else
CPU_LIST="0-$MAX_CPU"
fi
echo "將進(jìn)程綁定到CPU核心: $CPU_LIST"
echo $CPU_LIST > $CPUSET_GROUP/cpuset.cpus
# 綁定到內(nèi)存節(jié)點(diǎn)0
echo "將進(jìn)程綁定到內(nèi)存節(jié)點(diǎn): 0"
echo 0 > $CPUSET_GROUP/cpuset.mems
# 繼承父控制組的任務(wù)文件
echo "$(cat $CGROUP_BASE/cpuset/cpuset.tasks)" > $CPUSET_GROUP/cpuset.tasks
}
# 啟動測試進(jìn)程
start_test_process() {
echo -e "\n啟動CPU和內(nèi)存密集型測試進(jìn)程..."
# 啟動一個同時消耗CPU和內(nèi)存的進(jìn)程
(
# 內(nèi)存消耗部分
data=""
while true; do
data+=$(printf "x%.0s" {1..100000}) # 增加內(nèi)存使用
# CPU消耗部分
for ((i=0; i<10000000; i++)); do :; done # 空循環(huán)消耗CPU
sleep 0.1
done
) &
TEST_PID=$!
echo "測試進(jìn)程PID: $TEST_PID"
# 將進(jìn)程加入所有控制組
echo $TEST_PID > $MEM_GROUP/cgroup.procs
echo $TEST_PID > $CPU_GROUP/cgroup.procs
echo $TEST_PID > $CPUSET_GROUP/cpuset.procs
echo "進(jìn)程已加入控制組"
}
# 顯示配置信息
show_configuration() {
echo -e "\n=== 控制組配置信息 ==="
echo "內(nèi)存限制: $(cat $MEM_GROUP/memory.limit_in_bytes) 字節(jié)"
echo "CPU周期: $(cat $CPU_GROUP/cpu.cfs_period_us) 微秒"
echo "CPU配額: $(cat $CPU_GROUP/cpu.cfs_quota_us) 微秒"
echo "綁定CPU核心: $(cat $CPUSET_GROUP/cpuset.cpus)"
echo "綁定內(nèi)存節(jié)點(diǎn): $(cat $CPUSET_GROUP/cpuset.mems)"
echo -e "\n按任意鍵停止測試并清理資源..."
}
# 主流程
mount_cgroups
create_cgroups
configure_memory
configure_cpu
configure_cpuset
start_test_process
show_configuration
# 等待用戶輸入后清理
read -n 1
# 清理資源
cleanup
echo "演示完成"這個示例模擬了容器化環(huán)境中微服務(wù)的資源隔離場景,確保計算密集型任務(wù)在受限資源下運(yùn)行,同時不會影響其他服務(wù)。在實(shí)際應(yīng)用中,可以根據(jù)具體需求調(diào)整內(nèi)存限制、CPU 配額和 CPU / 內(nèi)存節(jié)點(diǎn)綁定配置。
4.2 cgroup 與設(shè)備子系統(tǒng)的隔離配合
在實(shí)際應(yīng)用中,內(nèi)存 cgroup 與設(shè)備子系統(tǒng)的協(xié)同工作也非常重要,特別是在一些對 IO 性能和資源隔離要求較高的場景中。
對于存儲密集型應(yīng)用,如數(shù)據(jù)庫容器,同時配置 blkio 子系統(tǒng)(限制磁盤 IO)和內(nèi)存 cgroup 是非常必要的。這類應(yīng)用在運(yùn)行過程中,會頻繁地進(jìn)行磁盤讀寫操作,如果不對磁盤 IO 進(jìn)行限制,可能會因為 IO 阻塞導(dǎo)致內(nèi)存泄漏風(fēng)險 。通過設(shè)置 blkio 子系統(tǒng)的參數(shù),如blkio.throttle.read_bps_device和blkio.throttle.write_bps_device,可以限制設(shè)備的讀寫帶寬。例如,設(shè)置echo "sda 10485760" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.read_bps_device表示限制myapp控制組對sda設(shè)備的讀取帶寬為 10MB 每秒 。同時,通過內(nèi)存 cgroup 設(shè)置內(nèi)存限制,如memory.limit_in_bytes,可以確保應(yīng)用在內(nèi)存使用上也受到嚴(yán)格控制,避免因為內(nèi)存使用不當(dāng)而導(dǎo)致系統(tǒng)性能下降或崩潰 。這樣的聯(lián)合限制可以有效地避免因 IO 阻塞導(dǎo)致內(nèi)存泄漏風(fēng)險,保證數(shù)據(jù)庫容器的穩(wěn)定運(yùn)行 。
devices 子系統(tǒng)在實(shí)現(xiàn)完整的容器資源隔離沙箱中發(fā)揮著關(guān)鍵作用。它可以限制控制組內(nèi)進(jìn)程訪問特定設(shè)備,與內(nèi)存限制結(jié)合,為容器提供了全方位的資源隔離 。在容器化環(huán)境中,每個容器都應(yīng)該運(yùn)行在一個相對獨(dú)立的沙箱中,不能隨意訪問宿主機(jī)的設(shè)備資源,否則可能會導(dǎo)致安全問題或資源沖突 。通過 devices 子系統(tǒng),可以精確地控制容器對設(shè)備的訪問權(quán)限。例如,設(shè)置echo 'a *:* rwm' > /sys/fs/cgroup/devices/myapp/devices.allow表示允許myapp控制組內(nèi)的進(jìn)程對所有設(shè)備進(jìn)行讀寫操作,而設(shè)置echo 'c 1:3 rwm' > /sys/fs/cgroup/devices/myapp/devices.allow則表示只允許對字符設(shè)備 1:3 進(jìn)行讀寫操作 。配合內(nèi)存 cgroup 的內(nèi)存限制,如memory.limit_in_bytes,可以確保容器在內(nèi)存使用和設(shè)備訪問上都受到嚴(yán)格控制,實(shí)現(xiàn)完整的容器資源隔離沙箱,提高系統(tǒng)的安全性和穩(wěn)定性 。
cgroup 內(nèi)存子系統(tǒng)與設(shè)備子系統(tǒng)(blkio 和 devices)協(xié)同工作的示例如下:
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 配置控制組路徑
CGROUP_BASE="/sys/fs/cgroup"
APP_GROUP="db_container"
# 各子系統(tǒng)控制組路徑
MEM_GROUP="$CGROUP_BASE/memory/$APP_GROUP"
BLKIO_GROUP="$CGROUP_BASE/blkio/$APP_GROUP"
DEVICES_GROUP="$CGROUP_BASE/devices/$APP_GROUP"
# 存儲設(shè)備(根據(jù)實(shí)際系統(tǒng)調(diào)整,這里使用sda作為示例)
STORAGE_DEVICE="sda"
# 清理函數(shù)
cleanup() {
# 終止測試進(jìn)程
if [ -n "$TEST_PID" ]; then
kill $TEST_PID 2>/dev/null
echo -e "\n已終止測試進(jìn)程"
fi
# 刪除控制組
if [ -d "$MEM_GROUP" ]; then
rmdir $MEM_GROUP
fi
if [ -d "$BLKIO_GROUP" ]; then
rmdir $BLKIO_GROUP
fi
if [ -d "$DEVICES_GROUP" ]; then
rmdir $DEVICES_GROUP
fi
echo "已清理控制組資源"
}
# 檢查并掛載必要的cgroup子系統(tǒng)
mount_cgroups() {
for subsys in memory blkio devices; do
mount_point="$CGROUP_BASE/$subsys"
if ! mountpoint -q $mount_point; then
echo "掛載$subsys子系統(tǒng)到$mount_point..."
mkdir -p $mount_point
mount -t cgroup -o $subsys $subsys $mount_point || {
echo "$subsys子系統(tǒng)掛載失敗"
exit 1
}
fi
done
}
# 創(chuàng)建控制組
create_cgroups() {
echo "創(chuàng)建控制組..."
mkdir -p $MEM_GROUP $BLKIO_GROUP $DEVICES_GROUP || {
echo "創(chuàng)建控制組失敗"
exit 1
}
}
# 配置內(nèi)存限制
configure_memory() {
echo "配置內(nèi)存限制為1GB..."
echo 1073741824 > $MEM_GROUP/memory.limit_in_bytes # 1GB
}
# 配置blkio子系統(tǒng)(磁盤IO限制)
configure_blkio() {
# 限制讀寫帶寬為10MB/s (10*1024*1024 = 10485760 bytes)
echo "配置磁盤IO限制為10MB/s..."
echo "$STORAGE_DEVICE 10485760" > $BLKIO_GROUP/blkio.throttle.read_bps_device
echo "$STORAGE_DEVICE 10485760" > $BLKIO_GROUP/blkio.throttle.write_bps_device
}
# 配置devices子系統(tǒng)(設(shè)備訪問控制)
configure_devices() {
echo "配置設(shè)備訪問權(quán)限..."
# 默認(rèn)拒絕所有設(shè)備訪問
echo "a *:* rwm" > $DEVICES_GROUP/devices.deny
# 允許訪問標(biāo)準(zhǔn)輸入輸出設(shè)備
echo "c 1:3 rwm" > $DEVICES_GROUP/devices.allow # /dev/null, /dev/zero等
echo "c 1:5 rwm" > $DEVICES_GROUP/devices.allow # /dev/tty
# 允許訪問存儲設(shè)備(sda)
echo "b 8:0 rwm" > $DEVICES_GROUP/devices.allow # sda設(shè)備的主從設(shè)備號通常為8:0
# 允許訪問臨時文件系統(tǒng)
echo "c 1:7 rwm" > $DEVICES_GROUP/devices.allow # /dev/full
echo "c 1:8 rwm" > $DEVICES_GROUP/devices.allow # /dev/random
echo "c 1:9 rwm" > $DEVICES_GROUP/devices.allow # /dev/urandom
}
# 啟動測試進(jìn)程(模擬數(shù)據(jù)庫操作)
start_test_process() {
echo -e "\n啟動模擬數(shù)據(jù)庫進(jìn)程..."
# 啟動一個同時消耗內(nèi)存和磁盤IO的進(jìn)程
(
# 創(chuàng)建測試文件
TEST_FILE="/tmp/db_testfile.dat"
# 模擬數(shù)據(jù)庫操作:循環(huán)讀寫文件并消耗內(nèi)存
data=""
while true; do
# 增加內(nèi)存使用
data+=$(printf "x%.0s" {1..100000})
# 模擬磁盤IO操作
dd if=/dev/zero of=$TEST_FILE bs=1M count=10 oflag=direct 2>/dev/null
sync
rm -f $TEST_FILE
sleep 1
done
) &
TEST_PID=$!
echo "測試進(jìn)程PID: $TEST_PID"
# 將進(jìn)程加入所有控制組
echo $TEST_PID > $MEM_GROUP/cgroup.procs
echo $TEST_PID > $BLKIO_GROUP/cgroup.procs
echo $TEST_PID > $DEVICES_GROUP/cgroup.procs
echo "進(jìn)程已加入控制組"
}
# 顯示配置信息
show_configuration() {
echo -e "\n=== 控制組配置信息 ==="
echo "內(nèi)存限制: $(cat $MEM_GROUP/memory.limit_in_bytes) 字節(jié)"
echo "磁盤讀帶寬限制: $(cat $BLKIO_GROUP/blkio.throttle.read_bps_device)"
echo "磁盤寫帶寬限制: $(cat $BLKIO_GROUP/blkio.throttle.write_bps_device)"
echo "允許訪問的設(shè)備: $(cat $DEVICES_GROUP/devices.allow)"
echo -e "\n按任意鍵停止測試并清理資源..."
}
# 主流程
mount_cgroups
create_cgroups
configure_memory
configure_blkio
configure_devices
start_test_process
show_configuration
# 等待用戶輸入后清理
read -n 1
# 清理資源
cleanup
echo "演示完成"這個示例特別適合模擬數(shù)據(jù)庫容器等存儲密集型應(yīng)用的資源隔離場景,通過聯(lián)合配置避免因 IO 阻塞導(dǎo)致的內(nèi)存問題,同時通過設(shè)備訪問控制實(shí)現(xiàn)完整的容器沙箱隔離。實(shí)際使用時,應(yīng)根據(jù)系統(tǒng)的實(shí)際設(shè)備情況調(diào)整存儲設(shè)備參數(shù)和設(shè)備訪問權(quán)限配置。
五、實(shí)踐案例:容器場景下的內(nèi)存 cgroup 應(yīng)用
5.1容器環(huán)境:Docker/Kubernetes 資源管控
(1)Docker 配置映射
在 Docker 容器環(huán)境中,內(nèi)存 Cgroup 扮演著至關(guān)重要的角色,它為容器提供了強(qiáng)大的內(nèi)存管理能力。通過簡單的命令行參數(shù),我們可以輕松地將內(nèi)存限制和交換分區(qū)限制等配置,映射到內(nèi)存 Cgroup 的對應(yīng)參數(shù)上,實(shí)現(xiàn)對容器內(nèi)存使用的精確控制。
當(dāng)我們使用docker run -m 200M命令啟動一個容器時,實(shí)際上是在設(shè)置容器的內(nèi)存限制。這個操作在內(nèi)存 Cgroup 中對應(yīng)的是memory.limit_in_bytes參數(shù),200M 換算成字節(jié)為 209715200,即memory.limit_in_bytes=209715200。這意味著容器內(nèi)所有進(jìn)程的內(nèi)存使用總和不能超過 200MB,一旦超過這個限制,系統(tǒng)會根據(jù) OOM(Out-Of-Memory)機(jī)制采取相應(yīng)措施,如觸發(fā) OOM Killer 殺死容器內(nèi)的進(jìn)程,以釋放內(nèi)存資源,保障容器和宿主機(jī)系統(tǒng)的穩(wěn)定性。
對于交換分區(qū)限制,我們可以使用--memory-swap=300M參數(shù)。在內(nèi)存 Cgroup 中,這對應(yīng)著memory.memsw.limit_in_bytes參數(shù),300M 換算后為 314572800,即memory.memsw.limit_in_bytes=314572800。這個參數(shù)限制了容器可以使用的內(nèi)存和交換分區(qū)的總量,當(dāng)容器的內(nèi)存使用加上交換分區(qū)使用達(dá)到 300MB 時,同樣會觸發(fā) OOM 機(jī)制。這一配置在容器需要大量內(nèi)存但又要防止其過度占用系統(tǒng)資源時非常有用,例如在運(yùn)行一些大數(shù)據(jù)處理任務(wù)的容器時,可以通過設(shè)置合理的交換分區(qū)限制,避免容器因過度使用交換分區(qū)而導(dǎo)致系統(tǒng)性能下降 。
(2) Kubernetes QoS 分級
在 Kubernetes 集群環(huán)境中,內(nèi)存 Cgroup 同樣是實(shí)現(xiàn)資源有效管理和 Pod 隔離的核心機(jī)制。Kubernetes 通過requests和limits聲明內(nèi)存配額,底層則基于內(nèi)存 Cgroup 實(shí)現(xiàn) Pod 級別的內(nèi)存隔離,這種機(jī)制對于保障關(guān)鍵業(yè)務(wù)的資源優(yōu)先級至關(guān)重要。
當(dāng)我們在 Kubernetes 的 Pod 配置文件中定義requests和limits時,例如:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: my-image
resources:
requests:
memory: "512Mi"
limits:
memory: "1Gi"這里requests聲明了容器需要的最小內(nèi)存資源,limits則設(shè)置了容器可使用的最大內(nèi)存資源。Kubernetes 會根據(jù)這些聲明,在內(nèi)存 Cgroup 中為 Pod 創(chuàng)建相應(yīng)的配置,確保每個 Pod 只能在規(guī)定的內(nèi)存范圍內(nèi)運(yùn)行。這種基于內(nèi)存 Cgroup 的資源隔離機(jī)制,使得不同 Pod 之間的內(nèi)存使用不會相互干擾。在一個同時運(yùn)行著在線交易服務(wù)和后臺數(shù)據(jù)分析任務(wù)的 Kubernetes 集群中,在線交易服務(wù)的 Pod 可以設(shè)置較高的內(nèi)存優(yōu)先級,通過合理配置requests和limits,確保在高并發(fā)情況下,交易服務(wù)的 Pod 始終有足夠的內(nèi)存可用,而不會因為數(shù)據(jù)分析任務(wù) Pod 的內(nèi)存使用波動而受到影響,從而保障了關(guān)鍵業(yè)務(wù)的穩(wěn)定性和可靠性 。
5.2微服務(wù)架構(gòu):防止內(nèi)存泄漏擴(kuò)散
在微服務(wù)架構(gòu)的分布式系統(tǒng)中,內(nèi)存泄漏是一個常見且棘手的問題。由于系統(tǒng)由多個獨(dú)立的微服務(wù)組成,每個微服務(wù)都可能在不同的進(jìn)程或容器中運(yùn)行,一旦某個微服務(wù)出現(xiàn)內(nèi)存泄漏,它可能會逐漸耗盡所在節(jié)點(diǎn)的內(nèi)存資源,進(jìn)而影響同一節(jié)點(diǎn)上的其他微服務(wù),甚至引發(fā)整個集群的故障。
一個電商系統(tǒng)的商品搜索微服務(wù),如果存在內(nèi)存泄漏問題,隨著時間的推移,它會不斷占用更多的內(nèi)存。當(dāng)內(nèi)存使用達(dá)到一定程度時,可能會導(dǎo)致該節(jié)點(diǎn)上的其他微服務(wù),如訂單處理微服務(wù)、用戶認(rèn)證微服務(wù)等,因為內(nèi)存不足而無法正常工作。這種級聯(lián)故障會嚴(yán)重影響系統(tǒng)的可用性和用戶體驗,導(dǎo)致訂單處理失敗、用戶無法登錄等問題 。
為了解決這一問題,我們可以利用內(nèi)存 Cgroup 為每個微服務(wù)實(shí)例創(chuàng)建獨(dú)立的 Cgroup 分組,并設(shè)置嚴(yán)格的內(nèi)存限制。通過這種方式,將內(nèi)存泄漏的影響范圍控制在單個微服務(wù)實(shí)例內(nèi),避免故障擴(kuò)散到整個集群。
在 Go 語言編寫的微服務(wù)中,我們可以通過調(diào)用系統(tǒng)接口,結(jié)合內(nèi)存 Cgroup 實(shí)現(xiàn)內(nèi)存限制功能。以下是一個簡單的示例代碼,展示了如何為一個 Go 進(jìn)程設(shè)置內(nèi)存限制:
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// 創(chuàng)建一個新的Cgroup分組
err := os.Mkdir("/sys/fs/cgroup/memory/my_service", 0755)
if err != nil {
fmt.Println("Failed to create cgroup:", err)
return
}
defer os.Remove("/sys/fs/cgroup/memory/my_service")
// 設(shè)置內(nèi)存限制為100MB
limit := int64(100 * 1024 * 1024)
err = ioutil.WriteFile("/sys/fs/cgroup/memory/my_service/memory.limit_in_bytes", []byte(fmt.Sprintf("%d", limit)), 0644)
if err != nil {
fmt.Println("Failed to set memory limit:", err)
return
}
// 將當(dāng)前進(jìn)程添加到Cgroup分組中
pid := os.Getpid()
err = ioutil.WriteFile("/sys/fs/cgroup/memory/my_service/cgroup.procs", []byte(fmt.Sprintf("%d", pid)), 0644)
if err != nil {
fmt.Println("Failed to add process to cgroup:", err)
return
}
// 模擬內(nèi)存申請
var data []byte
for {
data = append(data, make([]byte, 1024*1024)...)
fmt.Println("Memory allocated:", len(data)/1024/1024, "MB")
// 休眠一段時間,避免進(jìn)程瞬間耗盡內(nèi)存
time.Sleep(1 * time.Second)
}
}在這個示例中,我們首先創(chuàng)建了一個名為my_service的 Cgroup 分組,然后設(shè)置其內(nèi)存限制為 100MB。接著,將當(dāng)前進(jìn)程的 PID 寫入cgroup.procs文件,使該進(jìn)程受到內(nèi)存限制的約束。當(dāng)進(jìn)程持續(xù)申請內(nèi)存超過 100MB 的限制時,系統(tǒng)會觸發(fā) OOM(Out-Of-Memory)機(jī)制,自動終止該進(jìn)程,從而避免了內(nèi)存泄漏問題擴(kuò)散到其他微服務(wù),保障了整個微服務(wù)架構(gòu)的穩(wěn)定性 。
5.3多租戶系統(tǒng):用戶級內(nèi)存配額管理
在多租戶系統(tǒng)中,為不同租戶提供獨(dú)立的內(nèi)存配額管理是確保系統(tǒng)資源公平分配和高效利用的關(guān)鍵。通過內(nèi)存 Cgroup,我們可以實(shí)現(xiàn)這一目標(biāo),為每個租戶分配合理的內(nèi)存資源,并對其使用情況進(jìn)行監(jiān)控和審計。
我們可以通過/sys/fs/cgroup/memory目錄下的配置文件,為每個租戶創(chuàng)建獨(dú)立的 Cgroup 分組,并設(shè)置相應(yīng)的內(nèi)存配額。在/sys/fs/cgroup/memory目錄下創(chuàng)建一個以租戶 ID 命名的子目錄,如/sys/fs/cgroup/memory/tenant1,然后在該目錄下設(shè)置memory.limit_in_bytes文件,指定租戶 1 的內(nèi)存配額。假設(shè)我們?yōu)樽鈶?1 分配 512MB 的內(nèi)存,那么可以執(zhí)行以下命令:
echo $((512 * 1024 * 1024)) > /sys/fs/cgroup/memory/tenant1/memory.limit_in_bytes這樣,租戶 1 下的所有進(jìn)程的內(nèi)存使用總和將被限制在 512MB 以內(nèi)。
為了實(shí)現(xiàn)用戶組關(guān)聯(lián)分組,我們可以在/sys/fs/cgroup/memory目錄下創(chuàng)建用戶組目錄,如/sys/fs/cgroup/memory/group1,然后將屬于該用戶組的租戶目錄鏈接到用戶組目錄下。假設(shè)租戶 1 和租戶 2 屬于group1用戶組,我們可以執(zhí)行以下命令:
ln -s /sys/fs/cgroup/memory/tenant1 /sys/fs/cgroup/memory/group1/tenant1
ln -s /sys/fs/cgroup/memory/tenant2 /sys/fs/cgroup/memory/group1/tenant2通過這種方式,我們可以對用戶組進(jìn)行統(tǒng)一的內(nèi)存配額管理和監(jiān)控。當(dāng)某個租戶的內(nèi)存使用超過配額時,系統(tǒng)會根據(jù) OOM 機(jī)制進(jìn)行處理,如終止該租戶下的部分進(jìn)程,以釋放內(nèi)存資源。同時,我們可以通過讀取memory.stat等文件,對每個租戶的內(nèi)存使用情況進(jìn)行詳細(xì)的統(tǒng)計和審計,實(shí)現(xiàn) “超量使用自動限制,資源使用可審計” 的租戶隔離方案,確保多租戶系統(tǒng)的穩(wěn)定性和公平性 。
六、cgroup 版本演進(jìn)與常見問題排查
6.1 v1 與 v2 版本差異
cgroup 歷經(jīng)了 v1 和 v2 兩個主要版本的發(fā)展,每個版本都有其獨(dú)特的特性和適用場景,在內(nèi)存管理等方面存在顯著差異。
特性 | cgroup v1 | cgroup v2 |
層級模型 | 多層級獨(dú)立子系統(tǒng) | 單層級統(tǒng)一資源管理 |
內(nèi)存統(tǒng)計粒度 | 進(jìn)程組級 | 支持進(jìn)程級與組級混合統(tǒng)計 |
OOM 機(jī)制 | 基于控制組整體 | 支持更精細(xì)的進(jìn)程選擇策略 |
主流應(yīng)用場景 | Docker(默認(rèn))、傳統(tǒng)容器 | Kubernetes(推薦)、云原生場景 |
cgroup v1 采用多層級獨(dú)立子系統(tǒng)的架構(gòu),每個資源子系統(tǒng)(如 CPU、內(nèi)存、磁盤 I/O 等)都有自己獨(dú)立的層級結(jié)構(gòu) 。在這種架構(gòu)下,一個進(jìn)程可能在不同的子系統(tǒng)中屬于不同的控制組,這就導(dǎo)致了資源控制邏輯的復(fù)雜性增加,管理難度加大。例如,在管理一個應(yīng)用的資源時,需要分別在 CPU 子系統(tǒng)、內(nèi)存子系統(tǒng)等多個子系統(tǒng)中進(jìn)行配置和管理,配置和管理的一致性難以保證。
而 cgroup v2 則進(jìn)行了重大改進(jìn),采用單層級統(tǒng)一資源管理的架構(gòu),所有的資源子系統(tǒng)共享同一個層級結(jié)構(gòu) 。在 cgroup v2 中,每個進(jìn)程只屬于一個控制組節(jié)點(diǎn),這使得資源管理變得更加簡單和直觀。例如,在管理一個應(yīng)用的資源時,只需要在同一個控制組節(jié)點(diǎn)中對所有資源進(jìn)行統(tǒng)一配置和管理,大大降低了管理成本和出錯的概率。
在內(nèi)存統(tǒng)計粒度方面,cgroup v1 主要是基于進(jìn)程組級別的統(tǒng)計,對于進(jìn)程級別的內(nèi)存使用情況統(tǒng)計不夠精細(xì) 。而 cgroup v2 則支持進(jìn)程級與組級混合統(tǒng)計,這使得管理員可以更精確地了解每個進(jìn)程的內(nèi)存使用情況,為內(nèi)存優(yōu)化提供更詳細(xì)的數(shù)據(jù)支持。例如,在排查內(nèi)存泄漏問題時,cgroup v2 可以更準(zhǔn)確地定位到具體的進(jìn)程,而 cgroup v1 則可能只能定位到進(jìn)程組,難以進(jìn)一步深入排查。
在 OOM 機(jī)制上,cgroup v1 是基于控制組整體來進(jìn)行處理的,當(dāng)控制組內(nèi)的內(nèi)存使用達(dá)到限制時,會觸發(fā) OOM Killer 機(jī)制,選擇控制組內(nèi)占用內(nèi)存最多的進(jìn)程進(jìn)行終止 。而 cgroup v2 支持更精細(xì)的進(jìn)程選擇策略,它可以根據(jù)進(jìn)程的重要性、內(nèi)存使用模式等多個因素來綜合判斷,選擇最合適的進(jìn)程進(jìn)行處理,從而更好地保護(hù)關(guān)鍵業(yè)務(wù)進(jìn)程,減少對業(yè)務(wù)的影響。例如,在一個包含多個微服務(wù)的 Kubernetes 集群中,cgroup v2 可以根據(jù)每個微服務(wù)的業(yè)務(wù)優(yōu)先級,在內(nèi)存不足時優(yōu)先保護(hù)高優(yōu)先級的微服務(wù),避免其因 OOM 而中斷服務(wù) 。
從主流應(yīng)用場景來看,cgroup v1 由于其兼容性較好,目前仍然是 Docker 的默認(rèn)選擇,在傳統(tǒng)容器場景中廣泛應(yīng)用 。而 cgroup v2 則在 Kubernetes 等云原生場景中表現(xiàn)出更好的適應(yīng)性和擴(kuò)展性,被推薦用于這些場景。例如,在 Kubernetes 中,cgroup v2 的統(tǒng)一層級結(jié)構(gòu)和更精細(xì)的資源控制能力,使得它能夠更好地支持 Pod 的資源管理和調(diào)度,提高集群的資源利用率和穩(wěn)定性 。
6.2生產(chǎn)環(huán)境配置建議
在生產(chǎn)環(huán)境中,合理配置內(nèi)存 cgroup 對于系統(tǒng)的穩(wěn)定性和性能至關(guān)重要,以下是一些實(shí)用的配置建議。
- 禁用交換空間:通過執(zhí)行echo 0 > memory.swappiness命令,可以將內(nèi)存交換空間的使用比例設(shè)置為 0,從而避免內(nèi)存限制被交換分區(qū)繞過。交換空間就像是內(nèi)存不夠用時的 “備用倉庫”,但使用交換空間會帶來性能損耗,因為從磁盤讀寫數(shù)據(jù)比從內(nèi)存讀寫數(shù)據(jù)要慢得多。在一些對性能要求極高的場景中,如實(shí)時交易系統(tǒng)、高性能計算集群等,禁用交換空間可以確保內(nèi)存限制的嚴(yán)格生效,避免因為使用交換分區(qū)而導(dǎo)致系統(tǒng)性能下降,保證關(guān)鍵業(yè)務(wù)的高效運(yùn)行 。
- 設(shè)置軟限制:通過memory.soft_limit_in_bytes參數(shù)設(shè)置軟限制,允許進(jìn)程在一定時間內(nèi)臨時超額使用內(nèi)存,這在很多場景下都非常有用。例如,在電商促銷活動期間,訂單處理服務(wù)可能會在短時間內(nèi)接收大量訂單,導(dǎo)致內(nèi)存使用量突然增加。通過設(shè)置軟限制,該服務(wù)可以在業(yè)務(wù)高峰期臨時使用更多內(nèi)存,而不會因為內(nèi)存限制而被 OOM Killer 終止。同時,軟限制需要配合memory.limit_in_bytes硬限制來使用,確保進(jìn)程最終不會無限制地占用內(nèi)存,保證系統(tǒng)的穩(wěn)定性 。
- 監(jiān)控與告警:定期采集memory.usage_in_bytes和memory.failcnt等指標(biāo),結(jié)合 Prometheus 等監(jiān)控工具,可以實(shí)現(xiàn)對內(nèi)存使用情況的實(shí)時監(jiān)控和異常預(yù)警。memory.usage_in_bytes反映了當(dāng)前內(nèi)存的使用量,通過監(jiān)控這個指標(biāo),可以及時發(fā)現(xiàn)內(nèi)存使用量的異常增長,提前采取措施進(jìn)行優(yōu)化。memory.failcnt記錄了內(nèi)存分配失敗的次數(shù),如果這個值不斷增加,說明系統(tǒng)可能存在內(nèi)存不足的問題,需要進(jìn)一步分析和解決。結(jié)合 Prometheus 的告警功能,可以在內(nèi)存使用量接近限制或者內(nèi)存分配失敗次數(shù)達(dá)到一定閾值時,及時發(fā)出告警通知,讓管理員能夠快速響應(yīng),避免系統(tǒng)出現(xiàn)故障 。
- 版本兼容性:在一些系統(tǒng)中,如 CentOS 8 等,默認(rèn)使用 cgroup v1,在使用過程中需要注意與 cgroup v2 子系統(tǒng)的掛載互斥問題。如果同時掛載 cgroup v1 和 cgroup v2 的內(nèi)存子系統(tǒng),可能會導(dǎo)致資源管理的混亂和沖突。在進(jìn)行系統(tǒng)升級或者配置調(diào)整時,需要仔細(xì)評估應(yīng)用對 cgroup 版本的兼容性,確保系統(tǒng)的正常運(yùn)行。如果應(yīng)用對 cgroup v2 有更好的支持,在滿足系統(tǒng)要求的情況下,可以考慮升級到支持 cgroup v2 的系統(tǒng)版本,以獲得更強(qiáng)大的資源管理能力 。
6.3常見問題排查
(1)OOM 未觸發(fā):當(dāng)發(fā)現(xiàn)進(jìn)程組內(nèi)存使用超過限制,但 OOM(Out-Of-Memory)機(jī)制未觸發(fā)時,首先要檢查memory.limit_in_bytes是否正確設(shè)置??梢酝ㄟ^查看/sys/fs/cgroup/memory/your_group/memory.limit_in_bytes文件的內(nèi)容,確認(rèn)限制值是否符合預(yù)期。還要檢查oom_control是否被禁用。如果oom_control文件中的oom_kill_disable字段設(shè)置為 1,表示 OOM Killer 已被禁用。此時,需要將其設(shè)置為 0,以恢復(fù) OOM Killer 的正常功能。可以通過執(zhí)行echo 0 > /sys/fs/cgroup/memory/your_group/oom_control命令來啟用 OOM Killer。
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "錯誤: 請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 檢查是否提供了控制組名稱
if [ $# -ne 1 ]; then
echo "用法: $0 <控制組名稱>"
echo "示例: $0 my_app_group"
exit 1
fi
CGROUP_NAME="$1"
CGROUP_BASE="/sys/fs/cgroup/memory"
CGROUP_PATH="$CGROUP_BASE/$CGROUP_NAME"
# 檢查控制組是否存在
if [ ! -d "$CGROUP_PATH" ]; then
echo "錯誤: 控制組 $CGROUP_NAME 不存在于 $CGROUP_BASE"
exit 1
fi
# 檢查內(nèi)存限制設(shè)置
check_memory_limit() {
echo -e "\n=== 內(nèi)存限制檢查 ==="
LIMIT_FILE="$CGROUP_PATH/memory.limit_in_bytes"
if [ ! -f "$LIMIT_FILE" ]; then
echo "錯誤: 無法找到內(nèi)存限制文件 $LIMIT_FILE"
return 1
fi
LIMIT=$(cat "$LIMIT_FILE")
# 轉(zhuǎn)換為人類可讀格式
if [ $LIMIT -eq $((-1)) ]; then
LIMIT_HUMAN="無限制"
else
LIMIT_HUMAN=$(numfmt --to=iec --suffix=B $LIMIT)
fi
echo "當(dāng)前內(nèi)存限制: $LIMIT 字節(jié) ($LIMIT_HUMAN)"
# 檢查是否設(shè)置了合理的限制(不是無限制)
if [ $LIMIT -eq $((-1)) ]; then
echo "警告: 未設(shè)置內(nèi)存限制,OOM機(jī)制不會觸發(fā)"
return 1
fi
return 0
}
# 檢查當(dāng)前內(nèi)存使用情況
check_memory_usage() {
echo -e "\n=== 內(nèi)存使用檢查 ==="
USAGE=$(cat "$CGROUP_PATH/memory.usage_in_bytes")
PEAK=$(cat "$CGROUP_PATH/memory.max_usage_in_bytes")
USAGE_HUMAN=$(numfmt --to=iec --suffix=B $USAGE)
PEAK_HUMAN=$(numfmt --to=iec --suffix=B $PEAK)
echo "當(dāng)前內(nèi)存使用: $USAGE 字節(jié) ($USAGE_HUMAN)"
echo "峰值內(nèi)存使用: $PEAK 字節(jié) ($PEAK_HUMAN)"
# 與限制比較
if [ $USAGE -ge $LIMIT ] && [ $LIMIT -ne $((-1)) ]; then
echo "警告: 當(dāng)前內(nèi)存使用已超過限制"
fi
}
# 檢查并修復(fù)OOM控制設(shè)置
check_oom_control() {
echo -e "\n=== OOM機(jī)制檢查 ==="
OOM_CONTROL="$CGROUP_PATH/oom_control"
if [ ! -f "$OOM_CONTROL" ]; then
echo "錯誤: 無法找到OOM控制文件 $OOM_CONTROL"
return 1
fi
OOM_DISABLED=$(grep oom_kill_disable "$OOM_CONTROL" | awk '{print $2}')
OOM_SCORE_ADJ=$(grep oom_score_adj "$OOM_CONTROL" | awk '{print $2}')
echo "OOM Killer 狀態(tài): $(if [ $OOM_DISABLED -eq 1 ]; then echo "已禁用"; else echo "已啟用"; fi)"
echo "OOM 分?jǐn)?shù)調(diào)整: $OOM_SCORE_ADJ"
# 如果OOM被禁用,提供啟用選項
if [ $OOM_DISABLED -eq 1 ]; then
read -p "是否啟用OOM Killer? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "啟用OOM Killer..."
echo 0 > "$OOM_CONTROL"
# 驗證設(shè)置
NEW_OOM_DISABLED=$(grep oom_kill_disable "$OOM_CONTROL" | awk '{print $2}')
if [ $NEW_OOM_DISABLED -eq 0 ]; then
echo "OOM Killer 已成功啟用"
else
echo "錯誤: 無法啟用OOM Killer"
return 1
fi
fi
fi
return 0
}
# 顯示控制組中的進(jìn)程
show_processes() {
echo -e "\n=== 控制組中的進(jìn)程 ==="
PROCS_FILE="$CGROUP_PATH/cgroup.procs"
if [ -s "$PROCS_FILE" ]; then
echo "PID 列表:"
cat "$PROCS_FILE"
echo -e "\n進(jìn)程詳情:"
ps -p $(tr '\n' ',' < "$PROCS_FILE" | sed 's/,$//')
else
echo "控制組中沒有運(yùn)行的進(jìn)程"
fi
}
# 主流程
echo "開始檢查控制組: $CGROUP_NAME"
check_memory_limit
check_memory_usage
check_oom_control
show_processes
echo -e "\n=== 檢查完成 ==="
echo "如果內(nèi)存使用超過限制且OOM未觸發(fā),可能的解決方案:"
echo "1. 確保內(nèi)存限制設(shè)置正確(不是無限制)"
echo "2. 確保OOM Killer已啟用(oom_kill_disable=0)"
echo "3. 考慮調(diào)整內(nèi)存限制值以適應(yīng)應(yīng)用需求"
echo "4. 檢查應(yīng)用是否存在內(nèi)存泄漏問題"使用方法:
sudo ./cgroup_oom_check.sh 你的控制組名稱腳本會引導(dǎo)你完成檢查過程,并在發(fā)現(xiàn) OOM Killer 被禁用時提供啟用選項,幫助解決內(nèi)存超限但 OOM 未觸發(fā)的問題。同時提供的診斷信息也能幫助你進(jìn)一步分析系統(tǒng)內(nèi)存使用情況。
(2)內(nèi)存統(tǒng)計異常:如果遇到內(nèi)存統(tǒng)計數(shù)據(jù)異常的情況,比如memory.stat中的某些指標(biāo)與usage_in_bytes不一致,可能是因為內(nèi)存統(tǒng)計中包含了內(nèi)核態(tài)內(nèi)存??梢酝ㄟ^對比memory.stat中的kmem相關(guān)指標(biāo),如memory.kmem.usage_in_bytes,來排查是否存在內(nèi)核態(tài)內(nèi)存泄漏或異常使用的問題。如果發(fā)現(xiàn)memory.kmem.usage_in_bytes持續(xù)增長且沒有合理的業(yè)務(wù)原因,可能需要進(jìn)一步檢查內(nèi)核模塊或驅(qū)動程序,看是否存在內(nèi)存泄漏的情況,可以通過dmesg命令查看內(nèi)核日志,尋找相關(guān)的錯誤信息 。
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "錯誤: 請使用root權(quán)限運(yùn)行此腳本 (sudo)"
exit 1
fi
# 檢查是否提供了控制組名稱
if [ $# -ne 1 ]; then
echo "用法: $0 <控制組名稱>"
echo "示例: $0 my_app_group"
exit 1
fi
CGROUP_NAME="$1"
CGROUP_BASE="/sys/fs/cgroup/memory"
CGROUP_PATH="$CGROUP_BASE/$CGROUP_NAME"
# 檢查控制組是否存在
if [ ! -d "$CGROUP_PATH" ]; then
echo "錯誤: 控制組 $CGROUP_NAME 不存在于 $CGROUP_BASE"
exit 1
fi
# 轉(zhuǎn)換字節(jié)為人類可讀格式
human_readable() {
numfmt --to=iec --suffix=B $1
}
# 顯示主要內(nèi)存統(tǒng)計數(shù)據(jù)
show_memory_stats() {
echo -e "\n=== 主要內(nèi)存統(tǒng)計數(shù)據(jù) ==="
# 基本內(nèi)存使用
USAGE=$(cat "$CGROUP_PATH/memory.usage_in_bytes")
echo "memory.usage_in_bytes: $USAGE ($(human_readable $USAGE))"
# 從stat文件獲取的總使用量
STAT_TOTAL=$(grep total "$CGROUP_PATH/memory.stat" | awk '{print $2}')
echo "memory.stat total: $STAT_TOTAL ($(human_readable $STAT_TOTAL))"
# 對比差異
DIFF=$(( USAGE > STAT_TOTAL ? USAGE - STAT_TOTAL : STAT_TOTAL - USAGE ))
DIFF_PERCENT=$(( DIFF * 100 / (USAGE + 1) )) # +1避免除零
if [ $DIFF_PERCENT -gt 10 ]; then
echo "警告: usage_in_bytes 與 stat total 差異較大 ($(human_readable $DIFF),$DIFF_PERCENT%)"
else
echo "usage_in_bytes 與 stat total 差異在正常范圍內(nèi) ($(human_readable $DIFF),$DIFF_PERCENT%)"
fi
}
# 分析內(nèi)核態(tài)內(nèi)存使用
analyze_kmem_usage() {
echo -e "\n=== 內(nèi)核態(tài)內(nèi)存使用分析 ==="
# 內(nèi)核態(tài)內(nèi)存使用
KMEM_USAGE=$(cat "$CGROUP_PATH/memory.kmem.usage_in_bytes")
echo "內(nèi)核態(tài)內(nèi)存使用: $KMEM_USAGE ($(human_readable $KMEM_USAGE))"
# 內(nèi)核態(tài)內(nèi)存限制
KMEM_LIMIT=$(cat "$CGROUP_PATH/memory.kmem.limit_in_bytes")
KMEM_LIMIT_HUMAN="無限制"
if [ $KMEM_LIMIT -ne $((-1)) ]; then
KMEM_LIMIT_HUMAN=$(human_readable $KMEM_LIMIT)
fi
echo "內(nèi)核態(tài)內(nèi)存限制: $KMEM_LIMIT ($KMEM_LIMIT_HUMAN)"
# 從stat獲取的內(nèi)核相關(guān)內(nèi)存
KMEM_STAT=$(grep kmem "$CGROUP_PATH/memory.stat" | awk '{sum += $2} END {print sum}')
echo "memory.stat 中的內(nèi)核內(nèi)存總和: $KMEM_STAT ($(human_readable $KMEM_STAT))"
# 檢查內(nèi)核內(nèi)存是否占比較高
USAGE=$(cat "$CGROUP_PATH/memory.usage_in_bytes")
if [ $USAGE -gt 0 ]; then
KMEM_PERCENT=$(( KMEM_USAGE * 100 / USAGE ))
echo "內(nèi)核態(tài)內(nèi)存占總內(nèi)存比例: $KMEM_PERCENT%"
if [ $KMEM_PERCENT -gt 30 ]; then
echo "警告: 內(nèi)核態(tài)內(nèi)存占比超過30%,可能存在異常"
fi
fi
}
# 監(jiān)控內(nèi)核態(tài)內(nèi)存變化
monitor_kmem_growth() {
echo -e "\n=== 內(nèi)核態(tài)內(nèi)存增長監(jiān)控 ==="
echo "將監(jiān)控30秒內(nèi)的內(nèi)核態(tài)內(nèi)存變化 (每5秒一次)..."
KMEM_VALUES=()
for i in {1..7}; do
CURRENT=$(cat "$CGROUP_PATH/memory.kmem.usage_in_bytes")
KMEM_VALUES+=($CURRENT)
echo "$(date +%H:%M:%S) - 內(nèi)核態(tài)內(nèi)存: $(human_readable $CURRENT)"
if [ $i -lt 7 ]; then
sleep 5
fi
done
# 檢查是否持續(xù)增長
GROWTH_COUNT=0
for ((i=1; i<${#KMEM_VALUES[@]}; i++)); do
if [ ${KMEM_VALUES[$i]} -gt ${KMEM_VALUES[$i-1]} ]; then
GROWTH_COUNT=$((GROWTH_COUNT + 1))
fi
done
if [ $GROWTH_COUNT -ge 5 ]; then
echo "警告: 內(nèi)核態(tài)內(nèi)存在監(jiān)控期間持續(xù)增長,可能存在內(nèi)存泄漏"
else
echo "內(nèi)核態(tài)內(nèi)存在監(jiān)控期間增長趨勢不明顯"
fi
}
# 查看相關(guān)內(nèi)核日志
check_kernel_logs() {
echo -e "\n=== 內(nèi)核日志檢查 ==="
read -p "是否查看最近的內(nèi)核日志以尋找內(nèi)存相關(guān)錯誤? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "\n最近的內(nèi)核日志 (內(nèi)存相關(guān)):"
dmesg | grep -iE 'out of memory|oom|memory leak|kmem|slab' | tail -20
echo -e "\n如需查看完整日志,請運(yùn)行: dmesg | less"
fi
}
# 主流程
echo "開始檢查控制組內(nèi)存統(tǒng)計: $CGROUP_NAME"
show_memory_stats
analyze_kmem_usage
monitor_kmem_growth
check_kernel_logs
echo -e "\n=== 檢查完成 ==="
echo "排查建議:"
echo "1. 如果內(nèi)核態(tài)內(nèi)存占比過高,檢查是否有異常的內(nèi)核模塊或驅(qū)動"
echo "2. 若發(fā)現(xiàn)持續(xù)增長趨勢,使用dmesg和journalctl查看詳細(xì)內(nèi)核日志"
echo "3. 考慮使用slabtop命令檢查內(nèi)核slab分配情況"
echo "4. 檢查是否有應(yīng)用程序頻繁使用內(nèi)核資源(如大量系統(tǒng)調(diào)用、IO操作)"使用方法:
sudo ./cgroup_memory_stat_check.sh 你的控制組名稱腳本會幫助你判斷是否存在內(nèi)核態(tài)內(nèi)存異常使用的情況,并提供針對性的排查建議,特別適合解決 memory.stat 與 usage_in_bytes 不一致的問題。
(3)進(jìn)程無法遷移:當(dāng)嘗試將進(jìn)程遷移到新的 Cgroup 分組時,如果出現(xiàn)無法遷移的問題,首先要確認(rèn)目標(biāo)分組路徑是否存在。可以通過執(zhí)行l(wèi)s /sys/fs/cgroup/memory/your_target_group命令,檢查目標(biāo)分組目錄是否存在。還要確認(rèn)當(dāng)前用戶是否具備寫入cgroup.procs文件的權(quán)限。如果權(quán)限不足,可以通過修改文件權(quán)限或使用sudo命令來執(zhí)行遷移操作。例如,sudo echo 1234 > /sys/fs/cgroup/memory/your_target_group/cgroup.procs,將 PID 為 1234 的進(jìn)程遷移到目標(biāo)分組中 。
#!/bin/bash
# 確保以root權(quán)限運(yùn)行
if [ "$(id -u)" -ne 0 ]; then
echo "警告: 進(jìn)程遷移可能需要root權(quán)限,建議使用sudo運(yùn)行"
fi
# 檢查參數(shù)是否齊全
if [ $# -ne 3 ]; then
echo "用法: $0 <源控制組> <目標(biāo)控制組> <進(jìn)程PID>"
echo "示例: $0 old_group new_group 1234"
exit 1
fi
SRC_GROUP="$1"
DEST_GROUP="$2"
PID="$3"
CGROUP_BASE="/sys/fs/cgroup/memory"
SRC_PATH="$CGROUP_BASE/$SRC_GROUP"
DEST_PATH="$CGROUP_BASE/$DEST_GROUP"
SRC_PROCS="$SRC_PATH/cgroup.procs"
DEST_PROCS="$DEST_PATH/cgroup.procs"
# 檢查進(jìn)程是否存在
check_process() {
if ! ps -p $PID > /dev/null; then
echo "錯誤: 進(jìn)程PID $PID 不存在"
exit 1
fi
echo "確認(rèn): 進(jìn)程 $PID 存在并正在運(yùn)行"
}
# 檢查源控制組是否存在
check_source_group() {
if [ ! -d "$SRC_PATH" ]; then
echo "錯誤: 源控制組 $SRC_GROUP 不存在于 $CGROUP_BASE"
exit 1
fi
if [ ! -f "$SRC_PROCS" ]; then
echo "錯誤: 源控制組缺少cgroup.procs文件"
exit 1
fi
# 檢查進(jìn)程是否屬于源控制組
if ! grep -q "^$PID$" "$SRC_PROCS"; then
echo "警告: 進(jìn)程 $PID 不在源控制組 $SRC_GROUP 中"
read -p "是否繼續(xù)遷移操作? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
else
echo "確認(rèn): 進(jìn)程 $PID 屬于源控制組 $SRC_GROUP"
fi
}
# 檢查目標(biāo)控制組是否存在
check_dest_group() {
echo -e "\n=== 檢查目標(biāo)控制組 ==="
if [ ! -d "$DEST_PATH" ]; then
echo "錯誤: 目標(biāo)控制組 $DEST_GROUP 不存在"
read -p "是否創(chuàng)建目標(biāo)控制組 $DEST_GROUP? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
mkdir -p "$DEST_PATH"
if [ $? -ne 0 ]; then
echo "錯誤: 無法創(chuàng)建目標(biāo)控制組,可能權(quán)限不足"
exit 1
fi
echo "已創(chuàng)建目標(biāo)控制組: $DEST_PATH"
else
exit 1
fi
else
echo "確認(rèn): 目標(biāo)控制組 $DEST_GROUP 存在"
fi
if [ ! -f "$DEST_PROCS" ]; then
echo "錯誤: 目標(biāo)控制組缺少cgroup.procs文件"
exit 1
fi
}
# 檢查目標(biāo)控制組權(quán)限
check_permissions() {
echo -e "\n=== 檢查權(quán)限 ==="
if [ ! -w "$DEST_PROCS" ]; then
echo "警告: 當(dāng)前用戶沒有寫入目標(biāo)控制組cgroup.procs的權(quán)限"
if [ "$(id -u)" -ne 0 ]; then
echo "建議: 使用sudo提升權(quán)限后重試"
exit 1
else
echo "錯誤: 即使作為root用戶也沒有寫入權(quán)限,檢查文件系統(tǒng)權(quán)限"
exit 1
fi
fi
echo "確認(rèn): 具備寫入目標(biāo)控制組的權(quán)限"
}
# 執(zhí)行遷移操作
migrate_process() {
echo -e "\n=== 執(zhí)行進(jìn)程遷移 ==="
echo "將進(jìn)程 $PID 從 $SRC_GROUP 遷移到 $DEST_GROUP..."
# 執(zhí)行遷移命令
echo $PID > "$DEST_PROCS"
MIGRATE_EXIT_CODE=$?
if [ $MIGRATE_EXIT_CODE -ne 0 ]; then
echo "錯誤: 遷移操作失敗"
# 嘗試使用替代方法
echo "嘗試使用替代方法遷移..."
sudo sh -c "echo $PID > $DEST_PROCS"
if [ $? -ne 0 ]; then
echo "錯誤: 即使使用sudo也無法完成遷移"
exit 1
fi
fi
}
# 驗證遷移結(jié)果
verify_migration() {
echo -e "\n=== 驗證遷移結(jié)果 ==="
if grep -q "^$PID$" "$DEST_PROCS"; then
echo "成功: 進(jìn)程 $PID 已遷移到目標(biāo)控制組 $DEST_GROUP"
# 檢查是否已從源控制組移除
if grep -q "^$PID$" "$SRC_PROCS"; then
echo "注意: 進(jìn)程仍存在于源控制組中,這在某些系統(tǒng)中是正常的"
fi
else
echo "錯誤: 遷移未成功,進(jìn)程不在目標(biāo)控制組中"
exit 1
fi
}
# 主流程
echo "開始進(jìn)程遷移檢查: $PID 從 $SRC_GROUP 到 $DEST_GROUP"
check_process
check_source_group
check_dest_group
check_permissions
migrate_process
verify_migration
echo -e "\n=== 操作完成 ==="
echo "進(jìn)程遷移驗證通過"使用方法:
# 基本用法
sudo ./cgroup_migrate_process.sh 源控制組 目標(biāo)控制組 進(jìn)程PID
# 示例
sudo ./cgroup_migrate_process.sh old_app_group new_app_group 1234腳本會引導(dǎo)你完成整個遷移過程,并在出現(xiàn)問題時提供針對性的解決方案,特別適合解決 "無法遷移進(jìn)程到新 cgroup" 的常見問題。



































