探索cgroup2
LI Rui

在之前学习Docker的时候了解到了cgroup机制,利用它可以在Linux中对单个或多个进程能够使用的CPU、内存等资源进行精细化控制,美团技术团队15年写的Linux资源管理之cgroups简介介绍了基础概念,推荐阅读。在最近准备实践cgroup控制内存占用时,发现目前内核(版本5.10)早已使用cgroup2,其网站为cgroup2,Linux源码内的参考手册:cgroup-v2.rst

(在Manjaro上面查看根cgroup时,发现已经变为cgroup2)

创建cgroup

下面让我们来实践一下如何使用cgroup2去限制我们编写程序对系统各类资源的占用。在/sys/fs/cgroup/目录下,保存着根cgroup的信息,当前所有进程都在这个默认的组内,从cgroup.procs文件内,我们可以发现目前的全部PID:

通过下面的命令,我们能够创建一个名为cg1的分组,并把PID为2345的进程添加到这个组内(一个进程同时只能在一个组内):

1
2
mkdir /sys/fs/cgroup/cg1
echo 2345 > /sys/fs/cgroup/cg1/cgroup.procs

设计资源控制

在每个cgroup内都可以找到两个文件:

文件名 描述
cgroup.controllers 表示可以使用的资源控制,根cgroup内包含全部可用的资源控制,而子cgroup可以使用的控制继承自其父组的cgroup.subtree_control文件。
cgroup.subtree_control 表示当前已经启用的资源控制,其内容会继承到子cgroup的cgroup.controllers中。

因此其层级可视化如下:

(图片来源自cgroup2

我们可以向cgroup.subtree_control文件内写入内容来控制启用的资源控制,+代表启用控制,-代表不启用控制,如:

1
echo '+cpu +memory -io' > /sys/fs/cgroup/cg1/cgroup.subtree_control

最终cgroup.subtree_control的内容为“cpu memory”。

查看PSI(压力指标)

PSI网站描述,这是新的Linux资源压力测试指标,我们可以在/proc/pressure目录下面看到三个文件:cpu、io、memory。

avg开头的项代表前多少秒的平均水平,totol代表累计的毫秒数。

指标的定义

some表示一个或多个进程由于资源不足而被延迟的时间百分比。比如60秒内,由于缺少内存资源,Task A能够顺利运行,而Task B等待了30秒内存,因此some avg60的值为50%。

full表示所有进程都被延迟的时间百分比。沿用上面的例子,如果Task A在Task B等待的这30秒内,也因资源不足等待了10秒钟,因此A和B有共同等待的10秒钟,full avg60的值就为10 / 60 * 100% = 16.66%。

让我们回到主题,之所以介绍PSI是因为PSI也为cgroup提供了接口。在cgroup的文件夹内,我们可以从cpu.pressureio.pressurememory.pressure中查看组内进程的PSI。

实践内存限制

假如有下面的一个Rust程序:

Cargo.toml:

1
2
3
...
[dependencies]
procfs = "0.11"

main.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use procfs::process::Process;

fn main() {
let me = Process::myself().unwrap();
println!("PID: {}", me.pid);

let page_size = procfs::page_size().unwrap() as u64;
println!("Memory page size: {}", page_size);

println!("Total virtual memory used: {} kB", me.stat.vsize / 1024);
println!("Total resident set: {} pages ({} kB)", me.stat.rss, me.stat.rss as u64 * page_size / 1024);

let mut bad_vec: Vec<u64> = Vec::new();
let mut count: u64 = 0;

loop {
bad_vec.push(count);
count += 1;
if count % 100000 == 0 {
let me = Process::myself().unwrap();
println!("Current bad_vec size: {}", bad_vec.len());
println!("Total virtual memory used: {} kB", me.stat.vsize / 1024);
println!("Total resident set: {} pages ({} kB)", me.stat.rss, me.stat.rss as u64 * page_size / 1024);
}
}
}

程序会不断向Vec内添加元素并打印内存占用,直到我们手动终止。有什么办法去阻止这疯狂的程序霸占我们的内存呢?

/sys/fs/cgroup/cg1文件夹内,我们可以看到诸多控制参数,其中有个memory.max文件。它定义了内存使用的硬限制,如果进程达到了限制且无法减小占用,系统就会因Out Of Memory终止进程。

我们首先打开一个终端,由于我们知道cgroup中的进程派生出来的进程也会在同一个cgroup中,因此我们可以将该终端的PID加进cg1中。(以下提示符前的数字表示执行顺序)

终端1(Rust项目文件夹):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1$ echo $$
2580
3$ cat /proc/self/cgroup
0::/cg1
5$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/cgroup`
PID: 5147
Memory page size: 4096
Total virtual memory used: 4404 kB
Total resident set: 263 pages (1052 kB)
Current bad_vec size: 100000
Total virtual memory used: 5432 kB
Total resident set: 813 pages (3252 kB)
...
Total virtual memory used: 69944 kB
Total resident set: 12728 pages (50912 kB)
zsh: killed cargo run

终端2(root /sys/fs/cgroup/cg1):

1
2
2# echo 2580 > cgroup.procs
4# echo 50M > memory.max

我们可以看到cgroup成功阻止了这个邪恶程序!

总结

cgroup为Linux提供了限制进程资源的功能,在虚拟化中非常有用。v2在原来的基础上更加易用,还提供了PSI接口。

  • 本文标题:探索cgroup2
  • 本文作者:LI Rui
  • 创建时间:2021-11-10 16:20:48
  • 本文链接:https://www.lirui.tech/post/2021/93beb48da724.html
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-SA 许可协议。转载请注明出处!