跳到内容跳到页面导航:上一页 [access key p]/下一页 [access key n]
documentation.suse.com / SUSE Linux Enterprise Server 文档 / 系统分析和微调指南 / 资源管理 / 内核控制组
适用范围 SUSE Linux Enterprise Server 15 SP2

9 内核控制组

内核控制组 (cgroup) 是一种内核功能,可用于指派和限制进程的硬件与系统资源。还能以层次树状结构组织进程。

9.1 概述

为每个进程刚好指派一个管理 cgroup。cgroup 在层次树状结构中按序列出。您可针对单个进程或针对层次树状结构中的整条分支设置资源限制,例如 CPU、内存、磁盘 I/O 或网络带宽使用率。

SUSE Linux Enterprise Server 上,systemd 使用 cgroup 以分组(systemd 称之为切片)形式组织所有进程。systemd 还提供一个界面用于设置 cgroup 属性。

命令 systemd-cgls 显示层次树状结构。

本章仅提供概述。有关更多细节,请参见所列出的参考资料。

9.2 设置资源限制

注意
注意:隐式资源消耗

请注意,资源消耗隐式取决于工作负载的执行环境(例如,库/内核中数据结构的大小、实用程序的派生行为、计算效率)。因此,如果环境发生变化,建议您(重新)校准您的限制。

可以使用 systemctl set-property 命令设置 cgroup 的限制。语法是:

root # systemctl set-property [--runtime] NAME PROPERTY1=VALUE [PROPERTY2=VALUE]

(可选)使用 --runtime 选项。如果使用此选项,下一次重引导后已设置的限制不会保留。

请将 NAME 替换为 systemd 服务片、范围、套接字、装入或交换名称。请将属性替换为以下一项或多项:

CPUAccounting=[yes|no]

打开 CPU 使用率统计。此属性接受 yesno 作为参数。

示例:

root # systemctl set-property user.slice CPUAccounting=yes
CPUQuota=PERCENTAGE

将 CPU 时间指派给进程。其值是后跟 % 作为后缀的百分比。此属性暗含 CPUAccounting=yes

示例:

root # systemctl set-property user.slice CPUQuota=50%
MemoryAccounting=[yes|no]

打开内存使用率统计。此属性接受 yesno 作为参数。

示例:

root # systemctl set-property user.slice MemoryAccounting=yes
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) 访问权限。命令接受设备节点说明符,以及以空格分隔的 rwm 列表。

示例:

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.3 使用 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.3.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.3.2 覆盖 DefaultTasksMax 值

通过创建新的覆盖文件 /etc/systemd/system.conf.d/90-system-tasksmax.conf 更改全局 DefaultTasksMax 值,并写入下面几行以便为每个系统单元设置新的默认任务数限制(256 个):

[Manager]
DefaultTasksMax=256

装载新设置,然后校验设置是否已更改:

tux > sudo systemctl daemon-reload
tux > 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=8192

tux > 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.3.3 针对用户的默认 TasksMax 限制

针对用户的默认限制应相当高,因为用户会话需要更多的资源。通过创建新文件(例如 /etc/systemd/system/user-.slice.d/user-taskmask.conf),针对用户设置您自己的默认值。以下示例设置的默认值为 16284:

[Slice]
TasksMax=16284

然后重新装载 systemd 以装载新值,并校验更改:

tux > sudo systemctl daemon-reload

tux > systemctl show --property TasksMax user-.slice
TasksMax=16284

tux > systemctl show --property TasksMax user-1000.slice
TasksMax=16284

如何知道要使用哪些值?根据您的工作负载、系统资源和其他资源配置而定。如果 TasksMax 值太小,您会看到诸如无法派生(资源暂时不可用)无法创建用于处理新连接的线程,以及错误:函数调用“fork”失败并出现错误代码 11“资源暂时不可用”等错误消息。

有关在 systemd 中配置系统资源的详细信息,请参见 systemd.resource-control (5)

9.4 使用比例权重策略控制 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-cgroup
host1:~ # cd /io-cgroup
host1:~ # yes this is a test file | head -c 537MB > file1.txt
host1:~ # yes this is a test file | head -c 537MB > file2.txt

要运行示例,请打开三个命令外壳。两个外壳用于读取器进程,一个外壳用于运行控制 I/O 的步骤。在示例中已标记每个命令提示,以指示它是代表一个读取器进程还是 I/O。

9.4.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.4.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.5 更多信息

  • 内核文档(软件包 kernel-source):/usr/src/linux/Documentation/admin-guide/cgroup-v1 中的文件,以及文件 /usr/src/linux/Documentation/admin-guide/cgroup-v2.rst

  • http://lwn.net/Articles/604609/ — Brown, Neil:Control Groups Series(控制组系列,发布于 2014 年,包含 7 个部分)。

  • http://lwn.net/Articles/243795/ — Corbet, Jonathan:Controlling memory use in containers(控制容器中的内存使用,发布于 2007 年)。

  • http://lwn.net/Articles/236038/ — Corbet, Jonathan:Process containers(进程容器,发布于 2007 年)。