Cgroup 内存使用的监测手段

语言: CN / TW / HK

cgroup 可以对内存的使用进行限制,以 v1 为例,有一个控制 hard limit 的 "memory.limit_in_bytes" ,和一个控制 soft limit 的 "memory.soft_limit_in_bytes" 。hard 的限制是不可以逾越的,而 soft limit 的意思是:当系统整体内存还够的时候,允许 cgroup 超过设定的 soft 限制,但当系统内存不足需要回收时,超出 soft 的部分,将被 push 压回去。

不管是触碰 hard 还是 soft 限制,cgroup 中进程的运行都可能受到影响(被 OOM 或者暂停),而如果能够监测一个 cgroup 内存使用的变化,一是可以在达到 hard/soft limit 之前给出提醒,以便用户态采取必要的措施,二是据此也可以辅助确定多大的 limit 设置比较合适。

"memory.usage_in_bytes" 就是这样一个记录 cgroup 内存占用信息的参数,但反复地读取它的值肯定不是个办法,我们需要一种类似 poll 的机制,而 "/sys/fs/cgroup/memory" 目录下的 "cgroup.event_control" 接口(仅针对 v1 版本),正可以做到这一点。

实验

对此,内核源码还提供了一个现成的示例文件 "tools/cgroup/cgroup_event_listener.c",直接 "make" 命令编译后,用 stress-ng 的镜像启动一个不断申请内存,上限为 1GiB 的容器:

docker run --memory 1G -it --rm alexeiled/stress-ng --brk 2 --stack 2 --bigheap 2

并设置内存使用超过 1070000000 字节时发出告警:

./cgroup_event_listener /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes 1070000000

随后就可以看到终端不断地打印出:

/sys/fs/cgroup/memory/docker/<container-id> 1070000000: crossed

接下来,把 "usage_in_bytes" 的监测值提升到 1080000000 字节,再基于同样的方法测试,终端不再有输出。因为 1024*1024*1024 = 1073741824,面对 1GiB 的限制,cgroup 的内存使用可能超过 1070000000,而不可能超过 1080000000。

探析

这说明 "cgroup_event_listener" 起到了预想的监测目的。以 5.17.0 版本为例,来看下代码层面的具体实现。

首先是创建一个用于事件通知的 eventfd,加上要监测的变量及参数,一起写入 "cgroup.event_control" ,然后通过循环读取 eventfd,来判断是否有事件发生。

在此过程中,还用到了 access() 访问被监测变量的路径,以检测 cgroup 是否仍存在,若终止 stress-ng 容器,终端将打印 "The cgroup seems to have removed"。

以上的这个示例只设定了一个 threshold,事实上,该机制支持对一个 cgroup 的内存使用设定多个通知档位, 其主要原理就是每个 event 对应一个等待队列,不断地 wait & wake。

而 event 的触发,是在给 memcg 分配内存的时候,检查该 cgroup 有没有超过设定的某个 threshold,其中的一条调用链如下(实现在 "mm/memcontrol.c"):

charge_memcg() --> memcg_check_events() --> mem_cgroup_threshold()

参考:

  • 内核文档 Documentation/cgroup-v1/memory.rst

原创文章,转载请注明出处。