文章目录
cgroup概述
- Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制;
- 可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;
- 不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ;
- 针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ;
- Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置的资源参数限制,还受到父Cgroup 设置的资源限制 。
cgroups 功能及核心概念
cgroups 主要提供了如下功能
- 资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
- 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
- 审计:计算控制组的资源使用情况。
- 控制:控制进程的挂起或恢复。
cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。
-
子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
-
控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
-
层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。
cgroups 的三个核心概念中,子系统是最核心的概念,因为子系统是真正实现某类资源的限制的基础。
Linux内核代码中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];
};
cgroup子系统
- cpu 子系统,主要限制进程的 cpu 使用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的memory 使用量。
- blkio 子系统,可以限制进程的块设备 io。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- net_prio — 这个子系统用来设计网络流量的优先级
- freezer 子系统,可以挂起或者恢复cgroups 中的进程。
- ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace
- hugetlb —这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。
当前系统已挂载的cgroup信息
# 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)
cpu子系统
- cpu.shares: 可出让的能获得 CPU 使用时间的相对值。
- cpu.cfs_period_us:cfs_period_us 用来配置时间周期长度,单位为 us(微秒)。
- cpu.cfs_quota_us:cfs_quota_us 用来配置当前 Cgroup 在 cfs_period_us 时间内最多能使用的 CPU 时间数,单位为 us(微秒)。
- cpu.stat : Cgroup 内的进程使用的 CPU 时间统计。
- nr_periods : 经过 cpu.cfs_period_us 的时间周期数量。
- nr_throttled : 在经过的周期内,有多少次因为进程在指定的时间周期内用光了配额时间而受到限制。
- throttled_time : Cgroup 中的进程被限制使用 CPU 的总用时,单位是 ns(纳秒)。
cpu子系统练习
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%
内存子系统
- memory.usage_in_bytes : cgroup 下进程使用的内存,包含 cgroup 及其子 cgroup 下的进程使用的内存
- memory.max_usage_in_bytes : cgroup 下进程使用内存的最大值,包含子 cgroup 的内存使用量。
- memory.limit_in_bytes : 设置 Cgroup 下进程最多能使用的内存。如果设置为 -1,表示对该 cgroup 的内存使用不做限制。
- memory.oom_control : 设置是否在 Cgroup 中使用 OOM(Out of Memory)Killer,默认为使用。当属于该 cgroup 的进程使用的内存超过最大的限定值时,会立刻被 OOM Killer 处理。
memory子系统练习
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
删除cgroup
直接删除创建的文件夹即可
rmdir /sys/fs/cgroup/memory/memorydemo/
docker如何使用cgroup
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 提供的。