【Linux】资源控制机制 — cgroups 详解
文章目录
- 一、什么是 Cgroups(Control Groups)
- 二、功能 原理
- 2.1 功能
- 2.2 主要子系统(Controller):
- 2.3 Cgroups 的工作原理:
- 三、基础相关知识
- 3.1 pidstat
- 3.2 stress
- 四、实操
- 4.1 cgroups 信息查看
- 4.2 使用 cgroups 对内存进行控制
- 4.3 使用 cgroups 对 CPU 进行控制
一、什么是 Cgroups(Control Groups)
Cgroups(控制组)是 Linux 内核提供的一项功能,旨在对一组进程进行资源分配、限制、优先级调整和监控。
通过 Cgroups,系统管理员可以将进程组织成多个组,并对这些组的资源使用情况进行控制和管理。
Cgroups 允许用户对 CPU、内存、磁盘 I/O、网络带宽等资源进行细粒度的管理,能够限制、优先分配或监控这些资源,确保系统资源在多个进程或容器之间合理分配。
二、功能 原理
2.1 功能
-
资源限制:
- 可以限制进程组使用的 CPU 时间、内存、磁盘 I/O 带宽等。例如,限制某些进程组使用不超过一定的 CPU 时间或者内存大小。
-
资源优先级分配:
- 可以为进程组设置不同的优先级,以确保某些关键进程获得更多的资源。例如,可以为高优先级的进程分配更多的 CPU 时间片,或限制低优先级进程的资源使用。
-
资源监控:
- 可以监控和跟踪 Cgroups 内的进程的资源使用情况,提供例如 CPU 使用率、内存消耗、I/O 活动等统计信息。
-
资源隔离:
- Cgroups 提供进程之间的资源隔离,确保一个进程组不会消耗过多的系统资源,从而影响到其他进程组的正常运行。
-
动态调整:
- 可以在运行时动态地调整 Cgroups 中进程的资源配额。例如,管理员可以在不重启系统的情况下修改进程组的资源限制。
2.2 主要子系统(Controller):
Cgroups 提供了多个“控制器”(controllers)来管理不同类型的资源。每个控制器负责管理某一类资源的使用。常见的 Cgroups 控制器包括:
-
CPU(cpu、cpuacct):
- 用于限制和跟踪进程组的 CPU 使用。可以限制一个进程组的 CPU 时间,或者为不同进程组分配不同的 CPU 时间比例。
- cpu:限制 CPU 使用。
- cpuacct:记录进程的 CPU 使用情况。
-
内存(memory):
- 用于限制进程组的内存使用。可以设置内存的软限制(soft limit)和硬限制(hard limit),超过限制时会触发 OOM(Out Of Memory)杀手。
- 该控制器可以确保每个进程组不会使用超过其分配的内存量,防止某些进程组耗尽整个系统的内存。
-
磁盘 I/O(blkio):
- 用于限制和控制进程组的磁盘 I/O 带宽。可以限制某个进程组的磁盘读取和写入速度。
- 例如,可以限制一个进程组每秒读取或写入的字节数,防止某个进程对磁盘的过度访问影响到其他进程的磁盘 I/O。
-
网络(net_cls、net_prio):
- 用于管理和限制进程组的网络带宽使用。
- net_cls:为进程组分配不同的网络类别,允许在网络中应用不同的 QoS(Quality of Service)策略。
- net_prio:设置进程的网络优先级。
-
任务和进程数量限制(pids):
- 用于限制进程组内的进程数量。例如,可以为某个进程组设置一个最大进程数,防止进程组创建过多的进程导致系统资源耗尽。
-
CPU亲和性(cpuset):
- 允许用户指定某个进程组运行在特定的 CPU 核心上,从而提高 CPU 使用效率并减少 CPU 上的上下文切换。
-
控制组的统一管理(cgroup):
- 用于管理所有的 Cgroup 设置,并提供统一的接口来创建、销毁、查询 Cgroups。
2.3 Cgroups 的工作原理:
-
创建控制组:在 Linux 中,Cgroups 是通过在文件系统(如
/sys/fs/cgroup/
)中创建目录来表示的,每个目录对应一个控制组。每个进程通过将其 PID 加入到某个 Cgroup 目录下,来加入该 Cgroup。 -
资源限制:每个 Cgroup 目录下都会有一些文件,这些文件对应于不同资源的限制。管理员可以通过写入这些文件来控制资源。例如,通过
memory.limit_in_bytes
文件可以设置内存的限制,cpu.cfs_quota_us
可以限制 CPU 使用的时间。 -
进程加入 Cgroup:可以通过将进程的 PID 写入到 Cgroup 目录的
tasks
文件中,将该进程加入到相应的 Cgroup 中。之后,进程的资源使用将受到该 Cgroup 配置的限制。
三、基础相关知识
3.1 pidstat
- 概述:
pidstat
是 sysstat
的一个命令,用于监控全部或指定进程的 CPU、内存、线程、设备IO 等系统资源的占用情况。
pidstat
第一次采样显示自系统启动开始的各项统计信息,后续采样显示自上次运行命令后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。
-
语法:
pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]
-
参数:
-u
:默认参数,显示各进程的 CPU 使用统计-r
:显示各进程的内存使用统计-d
:显示各进程的 IO 使用情况-p
:指定进程号,ALL 表示所有进程-C
:指定命令-l
:显示命令名和所有参数
3.2 stress
stress
是 Linux 的一个压力测试工具,可以对 CPU、 Memory、 IO、磁盘进行压力测试。
- 语法:
stress [OPTION [ARG]]
- 参数:
选项 | 描述 |
---|---|
-c, --cpu N | 产生 N 个进程,每个进程都循环调用 sqrt 函数产生 CPU 压力。 |
-i, --io N | 产生 N 个进程,每个进程循环调用 sync 将内存缓冲区内容写到磁盘上,产生 IO 压力。 |
--vm-bytes B | 指定分配内存的大小。 |
-m, --vm N | 产生 N 个进程,每个进程循环调用 malloc/free 函数分配和释放内存。 |
--vm-keep | 一直占用内存,区别于不断的释放和重新分配内存(默认是不断释放并重新分配内存)。 |
-d, --hdd N | 产生 N 个不断执行 write 和 unlink 函数的进程(创建文件,写入内容,删除文件)。 |
--hdd-bytes B | 指定文件大小。 |
-t, --timeout N | 在 N 秒后结束程序。 |
-q, --quiet | 程序在运行的过程中不输出信息。 |
四、实操
4.1 cgroups 信息查看
- cgroups 版本查看:
wqy@wqy-virtual-machine:~$ cat /proc/filesystems | grep cgroup
nodev cgroup
nodev cgroup2
- cgroups 子系统查看:
wqy@wqy-virtual-machine:~$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 0 162 1
cpu 0 162 1
cpuacct 0 162 1
blkio 0 162 1
memory 0 162 1
devices 0 162 1
freezer 0 162 1
net_cls 0 162 1
perf_event 0 162 1
net_prio 0 162 1
hugetlb 0 162 1
pids 0 162 1
rdma 0 162 1
misc 0 162 1
- cgroups 挂载信息查看:
执行指令后可以看到:默认存储位置为 /sys/fs/cgroup
wqy@wqy-virtual-machine:~$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
- 查看一个进程上的
cgroup
限制
以当前的shell进程为例,执行指令进行查看:
wqy@wqy-virtual-machine:~$ cat /proc/$$/cgroup
0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-02da5c30-f3a4-4bdf-94e1-27362546d1a7.scope
4.2 使用 cgroups 对内存进行控制
1. 创建内存的 cgroup
首先,我们进入到 cgroup 的内存控制目录 /sys/fs/cgroup/memory
,然后创建一个新的目录 test_memory
。
wqy@virtual-machine:/sys/fs/cgroup/memory$ mkdir test_memory
wqy@virtual-machine:/sys/fs/cgroup/memory$ ll
total 0
dr-xr-xr-x 7 root root 0 Mar 10 14:13 ./
drwxr-xr-x 15 root root 380 Mar 10 14:13 ../
-rw-r--r-- 1 root root 0 Mar 12 14:13 cgroup.clone_children
--w--w--w- 1 root root 0 Mar 12 14:13 cgroup.event_control
-rw-r--r-- 1 root root 0 Mar 12 14:13 cgroup.procs
-r--r--r-- 1 root root 0 Mar 12 14:13 cgroup.sane_behavior
drwxr-xr-x 2 root root 0 Mar 12 14:13 docker/
drwxr-xr-x 2 root root 0 Mar 12 14:13 init.scope/
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.failcnt
--w------- 1 root root 0 Mar 12 14:13 memory.force_empty
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.tcp.failcnt-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.numa_stat
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.oom_control
---------- 1 root root 0 Mar 12 14:13 memory.pressure_level
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.stat
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.swappiness
-r--r--r-- 1 root root 0 Mar 12 14:13 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:13 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Mar 12 14:13 notify_on_release
-rw-r--r-- 1 root root 0 Mar 12 14:13 release_agent
drwxr-xr-x 95 root root 0 Mar 12 13:22 system.slice/
-rw-r--r-- 1 root root 0 Mar 12 14:13 tasks
drwxr-xr-x 2 root root 0 Mar 12 14:14 test_memory/
drwxr-xr-x 3 root root 0 Mar 12 13:58 user.slice/
2. 查看 test_memory
目录中的文件
在 test_memory
目录下,内存限制相关的配置文件已经自动创建。
wqy@virtual-machine:/sys/fs/cgroup/memory/test_memory$ ll
total 0
drwxr-xr-x 2 root root 0 Mar 12 14:14 ./
dr-xr-xr-x 7 root root 0 Mar 10 14:13 ../
-rw-r--r-- 1 root root 0 Mar 12 14:14 cgroup.clone_children
--w--w--w- 1 root root 0 Mar 12 14:14 cgroup.event_control
-rw-r--r-- 1 root root 0 Mar 12 14:14 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.failcnt
--w------- 1 root root 0 Mar 12 14:14 memory.force_empty
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.numa_stat
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.oom_control
---------- 1 root root 0 Mar 12 14:14 memory.pressure_level
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.stat
-rw-r--r-- 1 root root 0 Mar 12 14:14 memory.swappiness
-r--r--r-- 1 root root 0 Mar 12 14:14 memory.usage_in_bytes
3. 配置 cgroup 的策略为最大使用 20MB 内存
接下来,将 memory.limit_in_bytes
文件的内容设置为 20M
(即 20971520 字节)。
wqy@virtual-machine:/sys/fs/cgroup/memory$ expr 20 \* 1024 \* 1024
20971520
wqy@virtual-machine:/sys/fs/cgroup/memory$ cat test_memory/memory.limit_in_bytes
9223372036854771712
wqy@virtual-machine:/sys/fs/cgroup/memory$ echo "20971520" > test_memory/memory.limit_in_bytes
wqy@virtual-machine:/sys/fs/cgroup/memory$ cat test_memory/memory.limit_in_bytes
20971520
4. 启动一个消耗内存的进程
我们启动一个内存消耗较大的进程,每个进程占用 50MB 内存。
wqy@virtual-machine:/sys/fs/cgroup$ stress -m 1 --vm-bytes 50M
stress: info: [62106] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
5. 使用 pidstat 查看进程状态
打开一个新的 Shell 窗口 B,并使用 pidstat 来查看进程的内存使用情况。红色部分显示的是进程的 PID。
wqy@virtual-machine:/sys/fs/cgroup/memory# pidstat -r -C stress -p ALL 1 10000
Linux 5.4.0-100-generic (139-159-150-152) 03/12/2023
_x86_64_ (1 CPU)
02:47:01 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
02:47:02 PM 0 62517 0.00 0.00 3856 988 0.05 stress
02:47:02 PM 0 62518 476597.03 0.00 55060 15156 0.75 stress
02:47:02 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
02:47:03 PM 0 62517 0.00 0.00 3856 988 0.05 stress
02:47:03 PM 0 62518 483459.00 0.00 55060 3540 0.17 stress
02:47:03 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
02:47:04 PM 0 62517 0.00 0.00 3856 988 0.05 stress
02:47:04 PM 0 62518 489336.00 0.00 55060 15156 0.75 stress
- 打开一个新的
shell C
窗口,将进程 id 移动到我们的cgroup
策略
cd /sys/fs/cgroup/memory
echo 62518 >> test_memory/tasks
- 进程无法申请到足够的内存,随后进程退出。
4.3 使用 cgroups 对 CPU 进行控制
1. 创建内存的 cgroup
很简单,我们进入到 cgroup 的内存控制目录 /sys/fs/cgroup/cpu
,创建目录 test_cpu
。可以看到系统会自动为我们创建 cgroup 的 CPU 策略。
wqy@virtual-machine:/sys/fs/cgroup/cpu$ cd /sys/fs/cgroup/cpu
wqy@virtual-machine:/sys/fs/cgroup/cpu$ mkdir test_cpu
wqy@virtual-machine:/sys/fs/cgroup/cpu$ ll test_cpu
total 0
drwxr-xr-x 2 root root 0 Mar 12 14:54 ./
dr-xr-xr-x 9 root root 0 Mar 10 14:13 ../
-rw-r--r-- 1 root root 0 Mar 12 14:54 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 12 14:54 cgroup.procs
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.stat
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_all
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Mar 12 14:54 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpu.shares
-r--r--r-- 1 root root 0 Mar 12 14:54 cpu.stat
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpu.uclamp.max
-rw-r--r-- 1 root root 0 Mar 12 14:54 cpu.uclamp.min
-rw-r--r-- 1 root root 0 Mar 12 14:54 notify_on_release
-rw-r--r-- 1 root root 0 Mar 12 14:54 tasks
2. 使用 stress
模拟一个任务,CPU 使用率为 100%
打开新的 Shell 窗口(B 窗口),运行以下命令:
wqy@virtual-machine:/sys/fs/cgroup$ stress -c 1
stress: info: [62576] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
3. 监控 CPU 使用率
可以看到 CPU 的使用率为 100%:
wqy@virtual-machine:/sys/fs/cgroup/memory$ pidstat -u -C stress -p ALL 1 10000
Linux 5.4.0-100-generic (wqy@virtual-machine) 03/12/2023 _x86_64_ (1 CPU)
02:59:38 PM UID PID %usr %system %guest %wait %CPU CPU Command
02:59:39 PM 0 62576 0.00 0.00 0.00 0.00 0.00 0 stress
02:59:39 PM 0 62577 99.01 0.00 0.00 0.99 99.01 0 stress
4. 设置 cgroup
的 CPU 使用率为 30%
打开新的 Shell 窗口(C 窗口),通过以下计算和命令限制 CPU 使用率:
公式:cfs_quota_us / cfs_period_us
cfs_period_us
表示一个 CPU 带宽,默认值为 100000(100ms)。cfs_quota_us
表示 Cgroup 可以使用的 CPU 带宽,设置为 30000,即限制 CPU 使用率为 30%。
wqy@virtual-machine:/sys/fs/cgroup/cpu$ cd /sys/fs/cgroup/cpu
wqy@virtual-machine:/sys/fs/cgroup/cpu$ echo 30000 > test_cpu/cpu.cfs_quota_us
5. 将进程 PID 添加到任务列表
获取任务 PID 为 62577,将其加入 test_cpu/tasks
进行控制:
wqy@virtual-machine:/sys/fs/cgroup/cpu$ echo 62577 > test_cpu/tasks
6. 观察 CPU 使用率变化
在 B 窗口中可以看到,监控的 CPU 使用率从 100% 降低到 30%。