10 内核控制组 #
内核控制组 (“cgroup”) 是一种内核功能,可用于分配和限制进程的硬件与系统资源。还能以层次树状结构组织进程。
10.1 概述 #
为每个进程刚好分配一个管理 cgroup。cgroup 在层次树状结构中按序列出。您可针对单个进程或针对层次树中的所有分支设置资源限制,例如 CPU、内存、磁盘 I/O 或网络带宽用量。
在 SUSE Linux Enterprise Desktop 上,systemd
使用 cgroup 以分组(systemd
称之为切片)形式组织所有进程。systemd
还提供一个界面用于设置 cgroup 属性。
命令 systemd-cgls
显示层次树状结构。
内核 cgroup API 有两个版本:v1 和 v2。此外,可能有多个 cgroup 层次结构公开不同的 API。从众多可能的组合来看,有两种可行的选择:
统一:包含控制器的 v2 层次结构
混合:不包含控制器的 v2 层次结构;控制器位于 v1 层次结构中(已弃用)
默认模式为统一。有种混合模式可以为需要 v1 的应用程序提供向后兼容性。
您只能设置一种模式。
10.1.1 混合 cgroup 层次结构 #
cgroup v1 已被弃用,在将来的版本中可能会将其去除。
要启用混合控制组层次结构,请将 systemd.unified_cgroup_hierarchy=0
作为内核命令行参数追加到 GRUB 2 引导加载程序。有关配置 GRUB 2 的更多细节,请参见第 18 章 “引导加载程序 GRUB 2”。
10.2 资源统计 #
可以通过将进程组织到不同的 cgroup 来获取每个 cgroup 的资源消耗数据。
统计进程较小,但开销并非为零,它所造成的影响取决于工作负载。对一个单元激活统计也会对同一切片中的所有单元、该切片的所有父切片,以及包含在这些父切片中的单元隐式激活统计。
可以使用诸如 MemoryAccounting=
的指令为每个单元设置统计,或者在 /etc/systemd/system.conf
中使用 DefaultMemoryAccounting=
指令为所有单元全局设置统计。有关可用指令的详尽列表,请参见 man
systemd.resource-control
。
10.3 设置资源限制 #
请注意,资源消耗隐式取决于工作负载的执行环境(例如,库/内核中数据结构的大小、实用程序的派生行为、计算效率)。因此,如果环境发生变化,建议您(重新)校准您的限制。
可以使用 systemctl
set-property
命令设置 cgroup 的限制。语法是:
#
systemctl set-property [--runtime] NAME PROPERTY1=VALUE [PROPERTY2=VALUE]
配置的值会立即应用。或者,也使用 --runtime
选项,使新值在重引导后不会保留。
请将 NAME 替换为 systemd
服务、范围或切片名称。
有关完整的属性列表和更多细节,请参见 man
systemd.resource-control
。
10.4 使用 TasksMax
防止派生炸弹 #
systemd
支持为每个叶单元配置任务计数限制,或配置各个切片的聚合限制。上游 systemd
随附了用于限制每个单元中的任务数量的默认值(内核全局限制的 15%,运行 /usr/sbin/sysctl kernel.pid_max
可查看总限制)。每个用户的切片限制为内核限制的 33%。但是,对于 SUSE Linux Enterprise Desktop,情况并非如此。
10.4.1 查找当前的默认 TasksMax
值 #
显然,在实践中,单一默认值并不能适用于所有用例。SUSE Linux Enterprise Desktop 随附了两个自定义配置用于覆盖系统单元和用户片的上游默认值,并将两者都设置为 infinity
。/usr/lib/systemd/system.conf.d/__25-defaults-SLE.conf
包含以下行:
[Manager] DefaultTasksMax=infinity
/usr/lib/systemd/system/user-.slice.d/25-defaults-SLE.conf
包含以下行:
[Slice] TasksMax=infinity
使用 systemctl
校验 DefaultTasksMax 值:
>
systemctl show --property DefaultTasksMax
DefaultTasksMax=infinity
infinity
表示没有限制。并不要求一定要更改默认值,但设置某些限制可能有助于防止失控进程导致系统崩溃。
10.4.2 覆盖 DefaultTasksMax
值 #
通过创建新的覆盖文件 /etc/systemd/system.conf.d/90-system-tasksmax.conf
更改全局 DefaultTasksMax
值,并写入下面几行以便为每个系统单元设置新的默认任务数限制(256 个):
[Manager] DefaultTasksMax=256
加载新设置,然后校验设置是否已更改:
>
sudo
systemctl daemon-reload
>
systemctl show --property DefaultTasksMax
DefaultTasksMax=256
根据您的需要调整此默认值。可根据需要在单个服务上设置不同的限制。本示例适用于 MariaDB。首先检查当前活动值:
>
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。
>
sudo
systemctl set-property mariadb.service TasksMax=8192
>
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 └─50-TasksMax.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 set-property
应用新限制,并创建一个可持久保存的插入式文件 /etc/systemd/system/mariadb.service.d/50-TasksMax.conf
,其中仅包含您要应用于现有单元文件的更改。值不一定必须是 8192,但应该是适合您工作负载的限制。
10.4.3 针对用户的默认 TasksMax
限制 #
针对用户的默认限制应较高,因为用户会话需要更多的资源。通过创建新文件(例如 /etc/systemd/system/user-.slice.d/40-user-taskmask.conf
)为任何用户设置您自己的默认值。以下示例设置的默认值为 16284:
[Slice] TasksMax=16284
请参见 第 19.5.3 节 “手动创建插入式文件” 来了解插入式文件所需的数字前缀。
然后重新加载 systemd 以加载新值,并校验更改:
>
sudo
systemctl daemon-reload
>
systemctl show --property TasksMax user-1000.slice
TasksMax=16284
如何知道要使用哪些值?这因您的工作负载、系统资源和其他资源配置而异。如果 TasksMax
值太小,您可能会看到诸如无法派生(资源暂时不可用)、无法创建用于处理新连接的线程,以及错误:函数调用“fork”失败并返回错误代码 11“资源暂时不可用”等错误消息。
有关在 systemd 中配置系统资源的详细信息,请参见 systemd.resource-control (5)
。
10.5 使用 cgroup 进行 I/O 控制 #
本节介绍如何使用 Linux 内核的块 I/O 控制器来指定 I/O 操作的优先级或限制此类操作。本节将利用 systemd 提供的方法来配置 cgroup,并讨论在处理按比例 I/O 控制时可能存在的陷阱。
10.5.1 先决条件 #
以下小节介绍了在设计和配置系统时必须提前采取的措施,因为在运行时期间无法更改这方面的设置。
10.5.1.1 文件系统 #
应该使用 cgroup 写回感知的文件系统(否则无法计算写回费用)。建议的 SUSE Linux Enterprise Desktop 文件系统增加了对下述上游版本的支持:
Btrfs (v4.3)
Ext4 (v4.3)
XFS (v5.3)
从 SUSE Linux Enterprise Desktop 15 SP3 开始,可以使用任何命名文件系统。
10.5.1.2 块 I/O 调度程序 #
限制策略在堆栈中的更高级别实施,因此不需要进行任何额外的调整。按比例 I/O 控制策略有两种不同的实施方式:BFQ 控制器和基于成本的模型。本节介绍 BFQ 控制器。要对特定的设备按比例实施该策略,我们必须确保 BFQ 是所选的调度程序。检查当前调度程序:
>
cat /sys/class/block/sda/queue/scheduler
mq-deadline kyber bfq [none]
将调度程序切换到 BFQ:
#
echo bfq > /sys/class/block/sda/queue/scheduler
必须指定磁盘设备(而不是分区)。设置此属性的最佳方式是创建特定于设备的 udev 规则。SUSE Linux Enterprise Desktop 随附的 udev 规则已经为旋转式磁盘驱动器启用了 BFQ。
10.5.1.3 Cgroup 层次结构布局 #
一般情况下,所有任务都驻留在根 cgroup 中,它们会相互竞争。将任务分发到 cgroup 树中时,竞争只在同级 cgroup 之间发生。这一点适用于按比例 I/O 控制;限制会按层次结构聚合所有后代的吞吐量(参见下图)。
r `- a IOWeight=100 `- [c] IOWeight=300 `- d IOWeight=100 `- [b] IOWeight=200
I/O 仅源自 cgroup c 和 b。即使 c 的权重更高,也会将其视为优先级更低,因为它与 b 之间存在级别竞争。
10.5.2 配置控制数量 #
可将这些值永久应用于(长时间运行的)服务。
>
sudo
systemctl set-property fast.service IOWeight=400
>
sudo
systemctl set-property slow.service IOWeight=50
>
sudo
systemctl set-property throttled.service IOReadBandwidthMax="/dev/sda 1M"
或者,可将 I/O 控制应用于单个命令,例如:
>
sudo
systemd-run --scope -p IOWeight=400 high_prioritized_command
>
sudo
systemd-run --scope -p IOWeight=50 low_prioritized_command
>
sudo
systemd-run --scope -p IOReadBandwidthMax="/dev/sda 1M" dd if=/dev/sda of=/dev/null bs=1M count=10
10.5.3 I/O 控制行为和设置预期 #
以下列表项目描述了 I/O 控制行为,以及不同条件下的预期结果。
I/O 控制最适合用于直接 I/O 操作(绕过页面缓存),实际 I/O 与调用方解耦的情况(通常会通过页面缓存写回)可能以各种方式表现出来。例如,I/O 控制发生延迟,甚至监测不到 I/O 控制(例如,在极少出现突发或竞争性工作负载碰巧永不“相遇”、同时提交 I/O 以及带宽饱和的情况下)。出于这些原因,最终的 I/O 吞吐量比率并不严格遵守配置权重的比率。
systemd 执行配置权重的缩放(以根据较窄的 BFQ 权重范围进行调整),因此最终的吞吐量比率也不相同。
除了全局 sysctl 句柄之外,写回活动还取决于脏页的数量(
vm.dirty_background_ratio
和vm.dirty_ratio
)。当脏页限制分布在 cgroup 之间时,各个 cgroup 的内存限制就会发挥作用,从而可能影响相关 cgroup 的 I/O 强度。并非所有存储区都是相同的。I/O 控制发生在 I/O 调度程序层,这会对堆叠在不执行实际调度的存储区上的设备的设置产生影响。以跨越多个物理设备、MD RAID 甚至 Btrfs RAID 的设备映射器逻辑卷为例。对此类设置进行 I/O 控制可能颇有难度。
不存在可用于对读取和写入按比例控制 I/O 的单个设置。
按比例 I/O 控制只是能够彼此交互的策略之一(但负责任的资源设计可能会避免这种交互)。
I/O 设备带宽不是 I/O 路径中唯一的共享资源。这涉及到全局文件系统结构,当 I/O 控制旨在保证特定的带宽时,就要考虑到此因素;但这种控制不能保证带宽,甚至可能导致优先级反转(优先的 cgroup 会等待较慢 cgroup 的事务)。
到目前为止,我们一直都在讨论文件系统数据的显式 I/O,但换入和换出也可以控制。不过,如果出现这种需求,系统会指出内存置备不当(或内存限制)。
10.5.4 用户会话中的资源控制 #
为了在用户会话中应用 cgroup 资源控制,必须将控制器委派给 systemd
的用户实例。SUSE Linux Enterprise Desktop 随附的 systemd
默认配置不委派任何控制器。
您可以使用插入式文件来更改委托控制器集。例如,/etc/systemd/system/user@.service.d/60-delegate.conf
会向所有用户添加控制器,而 /etc/systemd/system/user@uid.service.d/60-delegate.conf
仅向特定用户添加控制器。文件的内容应如下所示:
[Service] Delegate=pids memory
必须通知 systemd
实例和受影响的用户实例,以重新加载新配置。
>
sudo
systemctl daemon-reload
>
systemctl --user daemon-reexec
或者,受影响的用户可以注销后再登录,而不是应用第二行来重新启动其用户实例。
10.6 更多信息 #
内核文档(软件包
kernel-source
):/usr/src/linux/Documentation/admin-guide/cgroup-v1
中的文件,以及文件/usr/src/linux/Documentation/admin-guide/cgroup-v2.rst
。man systemd.resource-control
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 年)。