introduction
前情提要: 通过指定一些参数, 另一个进程在一个新的ns下运行, 那么什么是 cgroup 呢? 简单来说, 它能限制进程占用的资源, 非常神秘. cgroup 就是 control group, 控制 + 组, 控制二字表示它用于控制进程的资源占用, 组这个字表示 cgroup 能对好几个进程完成同样的控制
一个 cgroup 定义了一组进程, 通过在这组进程上绑定 Linux Subsystem 的各种参数配置, 能做到将一组 subsystem 控制手段绑定到该 cgroup 中的进程上, 实现了类似"控制"的效果. 总结一下, 某个 cgroup 下辖好几个进程, 同时被绑定了好几个 subsystem, 也就是说一个 cgroup 等于一组进程加一组限制条件. 其中某个 subsystem 可能是 blkio(磁盘IO) / CPU / Memory 控制中的一种
一个 hierarchy 等于 cgroup 组成的树, 对父节点 cgroup 做出的限制会默认带到 子节点cgroup上
裸玩 - 尝试加入不同的cgroup节点
看看一个层级树是怎么被创建出来的, 一个层级树的根节点里面包含那些信息
# 创建一个hierarchy(层级树)的挂载点
$ mkdir cgroup-test
# 即将被挂载上的东西类型为 cgroup
$ mount -t cgroup \
-o none,name=cgroup-test \
cgroup-test \
# 就挂载在下面这个 cgroup-test 文件夹下
./cgroup-test
# 上面的命令让这个文件夹成为一个层级树的挂载点, 默认先挂载上一个根节点 cgroup
# 挂载上层级树(根节点)以后, 默认的系统会把所有进程全都拖到这个层级树的根节点上来
$ ls ./cgroup-test
# 用于确认是否需要继承父节点的 subsystem 配置, 我们在根节点上,因此是0
cgroup.clone_children
# 当前节点中的进程组ID, 因为我们在根节点下, 因此包含了现在系统中的所有进程组ID
cgroup.procs
# 当前节点下的所有进程ID, 一旦把某个进程ID写进来了, 就等价于加入了这个 cgroup 了
# 同样的, 因为我们在根节点下, 因此包含当前系统中所有进程的ID
tasks
向下创建一个子节点, 看看一个子节点里又包含了那些信息
# 我们在根节点目录下, 创建一个子目录, 就相当于创建了一个子节点
$ mkdir cgroup-1 cgroup-2
# 子节点里也要记录meta信息的, 有意思的是, 虽然创建新的层级树会导致
# 把系统里所有进程拖进来, 但是在创建子节点的时候, 默认并不会加入一个进程
$ ls cgroup-1
cgroup.clone_children
cgroup.procs
tasks
子节点默认不包含任何进程, 那我们往这个子节点里拖一个进程看看会怎样
# 把当前进程的ID拖到子节点中的tasks里, 就相当于往子节点里添加了一个进程
$ echo $$ >> cgroup-1/tasks
# 然后检查检查这个进程现在正在被多少个 subsystem 控制着, 我们就能发现
# 这个进程正在被 cgroup-test 的层级树下的 cgroup-1 节点控制着
$ cat /proc/1538/cgroup
12:name=cgroup-test:/cgroup-1
11:memory=....
# 一个进程不能被同一棵树上的不同节点控制着, 像下面这样我们现在把它加到
# cgroup-2里, 它就会自动脱离 cgroup-1
$ echo $$ >> cgroup-1/tasks && cat /proc/1538/cgroup
12:name=cgroup-test:/cgroup-2
11:memory=....
真搞 - 现在我就限制你
我们在把进程往 cgroup-2 里拖的时候, 我们能看到这个进程除了在 cgroup-2 里, 它居然还默认在某个 memory里, 原来, 除了我们自建的 cgroup-test, 系统已经默认帮你创建了一些层级树了, 你看到的memory 就是系统默认的用于控制内存占用的 层级树了.
那...肯定是要拿来玩玩的呀...
# 你看系统已经默认的, 帮你mount(创建了)层级树了
# 它在 /sys/fs/cgroup/memory 下, 走我们进去玩一玩
$ mount | grep memory
cgroup on /sys/fs/cgroup/memory
$ cd /sys/fs/cgroup/memory
# 从根节点上, 创建一个子节点
$ mkdir xiaohan-limit && cd xiaohan-limit
# 限制当前这个进程内存的使用
$ echo $$ >> tasks
$ echo "100m" >> memory.limit_in_bytes
# 最后尝试一下如果我坚持消耗200m内存会发生什么: 直接被干掉
$ stress --vm-bytes 200m
Memory cgroup out of memory: Kill process 4517
stress FAIL: [4516] <-- Worker 4517 got signal 9
原理是啥呢
上面的例子里介绍了内存限制的玩法, 但其实还有cpu/块存储IO之类的, 玩法基本也就是上面那样的, 往cgroup里面添加进程ID, 它的实现我理解, 本质上是给进程上挂钩子, 现在假设某个进程正在运行, 并且这个进程处于内存限制之下 → 进程ID处于这个cgroup下 → 进程挂了这个cgroup的钩子. 现在进程的运行涉及到某项资源, 就会检查一下钩子上的subsystem, 看看有没有这项资源的限制