cgroups 主要提供了如下功能
cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。
子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。
cgroups 的三个核心概念中,子系统是最核心的概念,因为子系统是真正实现某类资源的限制的基础。
进程数据结构
struct task_struct
{
#ifdef CONFIG_CGROUPS
// 设置这个进程属于哪个css_set
struct css_set __rcu *cgroups;
// cg_list是用于将所有同属于一个css_set的task连成一起
struct list_head cg_list;
#endif
}
css_set 是 cgroup_subsys_state 对象的集合数据结构
struct css_set {
/*
* Set of subsystem states, one for each subsystem. This array is
* immutable after creation apart from the init_css_set during
* subsystem registration (at boot time).
*/
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
};
# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
1、在cgroup cpu子系统下创建目录
cd /sys/fs/cgroup/cpu
mkdir cpudemo
执行完上述命令后,我们查看一下我们新创建的目录下发生了什么?
# ls -l /sys/fs/cgroup/cpu/cpudemo
total 0
-rw-r--r--. 1 root root 0 Oct 7 16:09 cgroup.clone_children
--w--w--w-. 1 root root 0 Oct 7 16:09 cgroup.event_control
-rw-r--r--. 1 root root 0 Oct 7 16:09 cgroup.procs
-r--r--r--. 1 root root 0 Oct 7 16:09 cpuacct.stat
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpuacct.usage
-r--r--r--. 1 root root 0 Oct 7 16:09 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Oct 7 16:09 cpu.shares
-r--r--r--. 1 root root 0 Oct 7 16:09 cpu.stat
-rw-r--r--. 1 root root 0 Oct 7 16:09 notify_on_release
-rw-r--r--. 1 root root 0 Oct 7 16:09 tasks
由上可以看到我们新建的目录下被自动创建了很多文件,其中 cpu.cfs_quota_us 文件代表在某一个阶段限制的 CPU 时间总量,单位为微秒。例如,我们想限制某个进程最多使用 1 核 CPU,就在这个文件里写入 100000(100000 代表限制 1 个核) ,tasks 文件中写入进程的 ID 即可(如果要限制多个进程 ID,在 tasks 文件中用换行符分隔即可)。
2、创建进程,加入cgroup
使用以下命令将 shell 进程加入 cgroup 中:
# cd /sys/fs/cgroup/cpu/mydocker
# echo $$ > tasks
查看一下 tasks 文件内容:
# cat tasks
4924
5008
3、执行 CPU 耗时任务,验证 cgroup 是否可以限制 cpu 使用时间
执行完上述命令后,我们新打开一个 shell 窗口,使用 top -p 命令查看当前 cpu 使用率,-p 参数后面跟进程 ID,我这里是 4924
# top -p 4924
top - 16:19:45 up 10:27, 2 users, load average: 1.67, 0.50, 0.21
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 34.2 us, 12.8 sy, 0.0 ni, 52.5 id, 0.0 wa, 0.0 hi, 0.5 si, 0.0 st
KiB Mem : 3880256 total, 2901232 free, 478524 used, 500500 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 3158040 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4924 root 20 0 15920 2864 1796 R 98.3 0.1 1:04.15 bash
此时,该进程消耗完一个cpu
4、限制进程使用cpu为0.5核
# top -p 4924
top - 16:19:45 up 10:27, 2 users, load average: 1.67, 0.50, 0.21
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 34.2 us, 12.8 sy, 0.0 ni, 52.5 id, 0.0 wa, 0.0 hi, 0.5 si, 0.0 st
KiB Mem : 3880256 total, 2901232 free, 478524 used, 500500 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 3158040 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4924 root 20 0 15920 2864 1796 R 50 0.1 1:04.15 bash
此时,该进程使用的cpu使用率为50%
1、memory子系统目录中创建目录结构
cd /sys/fs/cgroup/memory
mkdir memorydemo # 创建该目录后,会生成一些控制文件
限制该cgroup使用内存为1G
echo 1073741824 > memory.limit_in_bytes
2、创建进程,加入 cgroup
把当前 shell 进程 ID 写入 tasks 文件内:
复制代码
# cd /sys/fs/cgroup/memory/memorydemo
# echo $$ > tasks
3、使用内存压测工具,模拟内存溢出
memtester压测,限制1500M内存导致进程被Kill
# memtester 1500 1
memtester version 4.5.0 (64-bit)
Copyright (C) 2001-2020 Charles Cazabon.
Licensed under the GNU General Public License version 2 (only).
pagesize is 4096
pagesizemask is 0xfffffffffffff000
want 1500MB (1572864000 bytes)
got 1500MB (1572864000 bytes), trying mlock ...Killed
直接删除创建的文件夹即可
rmdir /sys/fs/cgroup/memory/memorydemo/
1、使用如下命令创建一个容器
docker run -itd -m=1g openjdk:8u252-jdk
上述命令创建并启动了一个容器,并且限制内存为 1G。然后我们进入cgroups内存子系统的目录,使用 ls 命令查看一下该目录下的内容:
# ls -l /sys/fs/cgroup/memory
total 0
-rw-r--r--. 1 root root 0 Oct 18 15:56 cgroup.clone_children
--w--w--w-. 1 root root 0 Oct 18 15:56 cgroup.event_control
-rw-r--r--. 1 root root 0 Oct 18 15:56 cgroup.procs
-r--r--r--. 1 root root 0 Oct 18 15:56 cgroup.sane_behavior
drwxr-xr-x. 3 root root 0 Oct 19 10:10 docker
......
通过上面输出可以看到,该目录下有一个 docker 目录,该目录正是 Docker 在内存子系统下创建的。我们进入到 docker 目录下查看一下相关内容:
# cd /sys/fs/cgroup/memory/docker/
# ls
895f62af62c33b36aee78e8454a557b3d2f125084f8127333b2f37c1e4c49a9e
......
可以看到 docker 的目录下有一个一串随机 ID 的目录,该目录即为我们上面创建的容器的 ID。然后我们进入该目录,查看一下该容器的 memory.limit_in_bytes 文件的内容。
# cd 895f62af62c33b36aee78e8454a557b3d2f125084f8127333b2f37c1e4c49a9e/
# cat memory.limit_in_bytes
1073741824
内存限制正好为1Gib
写在最后: cgroups 不仅可以实现资源的限制,还可以为我们统计资源的使用情况,容器监控系统的数据来源也是 cgroups 提供的。