9 内核控制组 #
内核控制组 (“cgroup”) 是一种内核功能,可用于指派和限制进程的硬件与系统资源。还能以层次树状结构组织进程。
9.1 概述 #
为每个进程刚好指派一个管理 cgroup。cgroup 在层次树状结构中按序列出。您可针对单个进程或针对层次树状结构中的整条分支设置资源限制,例如 CPU、内存、磁盘 I/O 或网络带宽用量。
在 SUSE Linux Enterprise Server 上,systemd
使用 cgroup 以分组(systemd
称之为切片)形式组织所有进程。systemd
还提供一个界面用于设置 cgroup 属性。
命令 systemd-cgls
显示层次树状结构。
本章仅提供概述。有关更多细节,请参见所列出的参考资料。
9.2 资源统计 #
可以通过将各进程放置在不同的 cgroup 中来获取每个 cgroup 的特定资源消耗信息。
统计进程相对较小,但开销并非为零,它所造成的影响取决于工作负载。请注意,对一个单元启用统计也会对直接包含在同一切片中的所有单元、该切片的所有父切片,以及直接包含在这些父切片中的单元启用统计。因此,统计成本并不独属于一个单元。
可以使用诸如 MemoryAccounting=
的指令为每个单元设置统计,或者在 /etc/systemd/system.conf
中使用 DefaultMemoryAccounting=
指令为所有单元全局设置统计。有关可用指令的详尽列表,请参见 systemd.resource-control (5)
。
9.3 设置资源限制 #
请注意,资源消耗隐式取决于工作负载的执行环境(例如,库/内核中数据结构的大小、实用程序的派生行为、计算效率)。因此,如果环境发生变化,建议您(重新)校准您的限制。
可以使用 systemctl set-property
命令设置 cgroup
的限制。语法是:
root #
systemctl set-property [--runtime] NAME PROPERTY1=VALUE [PROPERTY2=VALUE]
(可选)使用 --runtime
选项。如果使用此选项,下一次重引导后已设置的限制不会保留。
请将 NAME 替换为 systemd
服务片、范围、套接字、挂载或交换名称。请将属性替换为以下一项或多项:
CPUQuota=
PERCENTAGE将 CPU 时间指派给进程。其值是后跟
%
作为后缀的百分比。此属性暗含CPUAccounting=yes
。示例:
root #
systemctl set-property user.slice CPUQuota=50%
MemoryLow=
BYTES进程中低于此限制的未用内存不会回收以作其他用途。为 BYTES 使用后缀 K、M、G 或 T。此属性暗含
MemoryAccounting=yes
。示例:
root #
systemctl set-property nginx.service MemoryLow=512M
注意:统一控制组层次结构仅当使用了统一控制组层次结构时此设置才可用,并会禁用
MemoryLimit=
。要启用统一控制组层次结构,请将systemd.unified_cgroup_hierarchy=1
作为内核命令行参数追加到 GRUB 2 引导加载程序。有关配置 GRUB 2 的更多细节,请参见第 14 章 “引导加载程序 GRUB 2”。MemoryHigh=
BYTES如果使用超出此限制的更多内存,将会主动抢占进程的内存。为 BYTES 使用后缀 K、M、G 或 T。此属性暗含
MemoryAccounting=yes
。例如:root #
systemctl set-property nginx.service MemoryHigh=2G
注意:统一控制组层次结构仅当使用了统一控制组层次结构时此设置才可用,并会禁用
MemoryLimit=
。要启用统一控制组层次结构,请将systemd.unified_cgroup_hierarchy=1
作为内核命令行参数追加到 GRUB 2 引导加载程序。有关配置 GRUB 2 的更多细节,请参见第 14 章 “引导加载程序 GRUB 2”。MemoryMax=
BYTES设置已用内存的最大限制。如果使用的内存超出允许值,将会终止进程。为 BYTES 使用后缀 K、M、G 或 T。此属性暗含
MemoryAccounting=yes
。示例:
root #
systemctl set-property nginx.service MemoryMax=4G
DeviceAllow=
允许 read (
r
)、write (w
) 和 mknod (m
) 访问权限。命令接受设备节点说明符,以及以空格分隔的r
、w
或m
列表。示例:
root #
systemctl set-property system.slice DeviceAllow="/dev/sdb1 r"
DevicePolicy=
[auto|closed|strict]
设置为
strict
时,仅允许访问DeviceAllow
中所列的设备。closed
额外允许访问标准伪设备,包括/dev/null
、/dev/zero
、/dev/full
、/dev/random
和/dev/urandom
。如果DeviceAllow
中未定义特定的规则,auto
允许访问所有设备。auto
是默认设置。
有关更多细节和完整属性列表,请参见 man systemd.resource-control
。
9.4 使用 TasksMax
防止派生炸弹 #
systemd
228 附带值为 512 的 DefaultTasksMax
限制。即任何系统单元可一次性创建的进程数上限为 512 个。旧版没有默认限制。此限制的目的是通过防止失控的进程创建过多的派生项,或者滋生大量的线程而导致系统资源耗尽,以此提高安全性。
但您很快会发现,单一默认值并不能适用于所有用例。尤其是当 CPU 和 RAM 等其他资源不受限制时,512 限制值还是不够小,无法防止失控的进程导致系统崩溃;而对于创建大量线程的进程(例如数据库)而言,该值又不够大。在 systemd
234 中,默认值已更改为 15%,即限制为 4915 个任务(值为 32768 的内核限制的 15%;请参见 cat /proc/sys/kernel/pid_max
)。此默认值已经过编译,可在配置文件中更改。编译的默认值记录在 /etc/systemd/system.conf
中。您可以编辑此文件以覆盖默认值,我们还将在下面的章节中介绍其他方法。
9.4.1 查找当前的默认 TasksMax
值 #
SUSE Linux Enterprise Server 随附了两个自定义配置用于覆盖系统单元和用户片的上游默认值,并将两者都设置为 infinity
。/usr/lib/systemd/system.conf.d/20-suse-defaults.conf
包含下面几行:
[Manager] DefaultTasksMax=infinity
/usr/lib/systemd/system/user-.slice.d/20-suse-defaults.conf
包含下面几行:
[Slice] TasksMax=infinity
infinity
表示没有限制。并不要求一定要更改默认值,但设置一些限制可能有助于防止失控进程导致系统崩溃。
9.4.2 覆盖 DefaultTasksMax
值 #
通过创建新的覆盖文件 /etc/systemd/system.conf.d/90-system-tasksmax.conf
更改全局 DefaultTasksMax
值,并写入下面几行以便为每个系统单元设置新的默认任务数限制(256 个):
[Manager] DefaultTasksMax=256
装载新设置,然后校验设置是否已更改:
tux >
sudo
systemctl daemon-reloadtux >
systemctl show --property DefaultTasksMax DefaultTasksMax=256
根据您的需要调整此默认值。可根据需要在单个服务上设置更高的限制。本示例适用于 MariaDB。首先检查当前活动值:
tux >
systemctl status mariadb.service
● mariadb.service - MariaDB database server
Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset>
Active: active (running) since Tue 2020-05-26 14:15:03 PDT; 27min ago
Docs: man:mysqld(8)
https://mariadb.com/kb/en/library/systemd/
Main PID: 11845 (mysqld)
Status: "Taking your SQL requests now..."
Tasks: 30 (limit: 256)
CGroup: /system.slice/mariadb.service
└─11845 /usr/sbin/mysqld --defaults-file=/etc/my.cnf --user=mysql
Tasks 行显示 MariaDB 中当前有 30 个任务正在运行,而默认上限为 256,这对于数据库而言并不足够。以下示例演示如何将 MariaDB 的限制提高至 8192。使用 systemctl edit
创建新的覆盖文件,然后输入新值:
tux >
sudo
systemctl edit mariadb.service [Service] TasksMax=8192tux >
systemctl status mariadb.service ● mariadb.service - MariaDB database server Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disab> Drop-In: /etc/systemd/system/mariadb.service.d └─override.conf Active: active (running) since Tue 2020-06-02 17:57:48 PDT; 7min ago Docs: man:mysqld(8) https://mariadb.com/kb/en/library/systemd/ Process: 3446 ExecStartPre=/usr/lib/mysql/mysql-systemd-helper upgrade (code=exited, sta> Process: 3440 ExecStartPre=/usr/lib/mysql/mysql-systemd-helper install (code=exited, sta> Main PID: 3452 (mysqld) Status: "Taking your SQL requests now..." Tasks: 30 (limit: 8192) CGroup: /system.slice/mariadb.service └─3452 /usr/sbin/mysqld --defaults-file=/etc/my.cnf --user=mysql
systemctl edit
创建覆盖文件 /etc/systemd/system/mariadb.service.d/override.conf
,其中仅包含您要应用到现有单元文件的更改。值不一定必须是 8192,但应该是适合您工作负载的限制。
9.4.3 针对用户的默认 TasksMax
限制 #
针对用户的默认限制应相当高,因为用户会话需要更多的资源。通过创建新文件(例如 /etc/systemd/system/user-.slice.d/user-taskmask.conf
),针对用户设置您自己的默认值。以下示例设置的默认值为 16284:
[Slice] TasksMax=16284
然后重新装载 systemd 以装载新值,并校验更改:
tux >
sudo
systemctl daemon-reloadtux >
systemctl show --property TasksMax user-.slice TasksMax=16284tux >
systemctl show --property TasksMax user-1000.slice TasksMax=16284
如何知道要使用哪些值?根据您的工作负载、系统资源和其他资源配置而定。如果 TasksMax
值太小,您会看到诸如无法派生(资源暂时不可用)、无法创建用于处理新连接的线程,以及错误:函数调用“fork”失败,错误代码 11“资源暂时不可用”等错误消息。
有关在 systemd 中配置系统资源的详细信息,请参见 systemd.resource-control (5)
。
9.5 使用比例权重策略控制 I/O #
本节介绍如何使用 Linux 内核的块 I/O 控制器来指定 I/O 操作的优先级。cgroup blkio 子系统可以控制和监视对块设备上 I/O 的访问。包含 cgroup 子系统参数的状态对象以 cgroup 虚拟文件系统(也称为伪文件系统)内部的伪文件表示。
本节中的示例说明,将值写入其中一些伪文件如何能够限制访问或带宽,以及从其中一些伪文件读取值如何能够提供有关 I/O 操作的信息。示例是针对 cgroup-v1 和 cgroup-v2 提供的。
您需要一个测试目录,包含两个文件分别用于测试性能和更改后的设置。使用 yes
命令可以快速创建所填充内容均为文本的测试文件。以下示例命令创建了一个测试目录,然后在其中填充了两个 537 MB 大小的文本文件:
host1:~ #
mkdir /io-cgrouphost1:~ #
cd /io-cgrouphost1:~ #
yes this is a test file | head -c 537MB > file1.txthost1:~ #
yes this is a test file | head -c 537MB > file2.txt
要运行示例,请打开三个命令外壳。两个外壳用于读取器进程,一个外壳用于运行控制 I/O 的步骤。在示例中已标记每个命令提示,以指示它是代表一个读取器进程还是 I/O。
9.5.1 使用 cgroup-v1 #
使用以下比例权重策略文件可为某个读取器进程授予比访问同一磁盘的其他读取器进程更高的 I/O 操作优先级。
blkio.weight
(仅在 4.20 版本及以下且具有旧式块层的内核中可用,使用 CFQ I/O 调度程序时可以使用)blkio.bfq.weight
(在 5.0 及更高版本且具有 blk-mq 的内核中可用,使用 BFQ I/O 调度程序时可以使用)
要进行测试,请使用 file2.txt
运行单个读取器进程(示例中会从某个 SSD 读取),但不控制其 I/O:
[io-controller] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[io-controller] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5251 131072+0 records in 131072+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 1.33049 s, 404 MB/s
现在,运行一个从同一磁盘读取的后台进程:
[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1.txt of=/dev/null bs=4k 5220 ...[reader2] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5251 131072+0 records in 131072+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 2.61592 s, 205 MB/s
每个进程获得一半的吞吐量来执行 I/O 操作。接下来,设置两个控制组(每个进程一个),校验是否使用了 BFQ,并为 reader2 设置不同的权重:
[io-controller] host1:/io-cgroup #
cd /sys/fs/cgroup/blkio/[io-controller] host1:/sys/fs/cgroup/blkio/ #
mkdir reader1[io-controller] host1:/sys/fs/cgroup/blkio/ #
mkdir reader2[io-controller] host1:/sys/fs/cgroup/blkio/ #
echo 5220 > reader1/cgroup.procs[io-controller] host1:/sys/fs/cgroup/blkio/ #
echo 5251 > reader2/cgroup.procs[io-controller] host1:/sys/fs/cgroup/blkio/ #
cat /sys/block/sda/queue/scheduler mq-deadline kyber [bfq] none[io-controller] host1:/sys/fs/cgroup/blkio/ #
cat reader1/blkio.bfq.weight 100[io-controller] host1:/sys/fs/cgroup/blkio/ #
echo 200 > reader2/blkio.bfq.weight[io-controller] host1:/sys/fs/cgroup/blkio/ #
cat reader2/blkio.bfq.weight 200
使用这些设置且 reader1 在后台运行的情况下,reader2 应获得比先前更高的吞吐量:
[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1.txt of=/dev/null bs=4k 5220 ...[reader2] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5251 131072+0 records in 131072+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 2.06604 s, 260 MB/s
比例权重的提高使得 reader2 的吞吐量增加。现在,再次将其权重提高一倍:
[io-controller] host1:/sys/fs/cgroup/blkio/ #
cat reader1/blkio.bfq.weight 100[io-controller] host1:/sys/fs/cgroup/blkio/ #
echo 400 > reader2/blkio.bfq.weight[io-controller] host1:/sys/fs/cgroup/blkio/ #
cat reader2/blkio.bfq.weight 400
这导致 reader2 的吞吐量再一次增加:
[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1.txt of=/dev/null bs=4k 5220 ...[reader2] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5251 131072+0 records in 131072+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 1.69026 s, 318 MB/s
9.5.2 使用 cgroup-v2 #
首先,请按本章开头所述设置您的测试环境。
然后,像使用 cgroup-v1 时那样,确保块 IO 控制器处于非活动状态。为此,请使用内核参数 cgroup_no_v1=blkio
进行引导。校验是否使用了此参数并且 IO 控制器 (cgroup-v2) 可用:
[io-controller] host1:/io-cgroup #
cat /proc/cmdline BOOT_IMAGE=... cgroup_no_v1=blkio ...[io-controller] host1:/io-cgroup #
cat /sys/fs/cgroup/unified/cgroup.controllers io
接下来启用 IO 控制器:
[io-controller] host1:/io-cgroup #
cd /sys/fs/cgroup/unified/[io-controller] host1:/sys/fs/cgroup/unified #
echo '+io' > cgroup.subtree_control[io-controller] host1:/sys/fs/cgroup/unified #
cat cgroup.subtree_control io
现在运行所有测试步骤,与 cgroup-v1 的步骤类似。请注意部分目录存在差异。使用 file2.txt 运行单个读取器进程(示例中会从某个 SSD 读取),但不控制其 I/O:
[io-controller] host1:/sys/fs/cgroup/unified #
cd -[io-controller] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[io-controller] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5633 [...]
运行一个从同一磁盘读取的后台进程,并注意吞吐量值:
[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1.txt of=/dev/null bs=4k 5633 [...][reader2] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 5703 [...]
每个进程获得一半的吞吐量来执行 I/O 操作。设置两个控制组(每个进程一个),校验 BFQ 是否为活动的调度程序,并为 reader2 设置不同的权重:
[io-controller] host1:/io-cgroup #
cd -[io-controller] host1:/sys/fs/cgroup/unified #
mkdir reader1[io-controller] host1:/sys/fs/cgroup/unified #
mkdir reader2[io-controller] host1:/sys/fs/cgroup/unified #
echo 5633 > reader1/cgroup.procs[io-controller] host1:/sys/fs/cgroup/unified #
echo 5703 > reader2/cgroup.procs[io-controller] host1:/sys/fs/cgroup/unified #
cat /sys/block/sda/queue/scheduler mq-deadline kyber [bfq] none[io-controller] host1:/sys/fs/cgroup/unified #
cat reader1/io.bfq.weight default 100[io-controller] host1:/sys/fs/cgroup/unified #
echo 200 > reader2/io.bfq.weight[io-controller] host1:/sys/fs/cgroup/unified #
cat reader2/io.bfq.weight default 200
使用新设置测试吞吐量。reader2 应显示吞吐量增加。
[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1 of=/dev/null bs=4k 5633 [...][reader2] host1:/io-cgroup #
echo $$; dd if=file2 of=/dev/null bs=4k count=131072 5703 [...]
尝试再次将 reader2 的权重提高一倍,然后测试新设置:
[reader2] host1:/io-cgroup #
echo 400 > reader1/blkio.bfq.weight[reader2] host1:/io-cgroup #
cat reader2/blkio.bfq.weight 400[reader1] host1:/io-cgroup #
sync; echo 3 > /proc/sys/vm/drop_caches[reader1] host1:/io-cgroup #
echo $$; dd if=file1.txt of=/dev/null bs=4k [...][reader2] host1:/io-cgroup #
echo $$; dd if=file2.txt of=/dev/null bs=4k count=131072 [...]
9.6 更多信息 #
内核文档(软件包
kernel-source
):/usr/src/linux/Documentation/admin-guide/cgroup-v1
中的文件,以及文件/usr/src/linux/Documentation/admin-guide/cgroup-v2.rst
。https://lwn.net/Articles/604609/ — Brown, Neil:Control Groups Series(控制组系列,发布于 2014 年,包含 7 个部分)。
https://lwn.net/Articles/243795/ — Corbet, Jonathan:Controlling memory use in containers(控制容器中的内存使用,发布于 2007 年)。
https://lwn.net/Articles/236038/ — Corbet, Jonathan:Process containers(进程容器,发布于 2007 年)。