systemd 如何与 cgroup 交互进行。

本文介绍的 cgroup 基于 v1 版本。

systemd

对于一个操作系统而言,仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将操作系统初始化为可操作的状态,而当今绝大多数 Linux 发行版中我们熟悉的 systemd 充当了 init 系统。

systemd 是 linux 系统中最新的初始化系统(init),它主要的设计目标是克服前任 sysvinit 固有的缺点,提高系统的启动速度。虽然日常工作中,我们对 systemd 的了解只停留在 systemctl start/stop / 中,但实际上 systemd 的功能非常强大,体系极其复杂,提供了 cgroups 管理、自动挂载、日志服务等大量基础功能。

Unit

在系统初始化时 systemd 需要处理大量的工作,如挂载文件系统、启动 ssh 服务等等。为了便于管理这些步骤,配置单元 unit 被引入到 systemd 中。

当前 systemd 支持以下 12 种不同的 unit:

  • service:表示 systemd 管理的一个后台服务进程,如 docker,是最常用的 unit 类型。
  • target:表示对其他 unit 的逻辑分组,用户可以通过 target 控制一组 unit。systemd 预定义了大量的 target ,如 multi-user.target。
  • scope:通过 systemd-run 或者调用 systemd 接口创建的外部进程。
  • slice:表示一组 scope/service,systemd 通过 slice 映射 cgroup Hierarchy 中的一个 node。
  • socket: 封装一个套接字,每一个 socket unit 都有一个相应的 service unit。
  • device:封装 Linux 设备。
  • mount:封装文件系统的一个挂载点,systemd 将对挂载点进行监控和管理,如启动自动 mount 等。
  • automount:类似 mount unit,当挂载点被访问时,systemd 自动执行挂载行为。
  • swap:类似 mount unit,但管理的是 swap。
  • timer: 定时任务配置,替代 crond
  • snapshot:snapshot unit 管理了一组配置单元,保存了系统当前的运行状态。
  • path:系统中的一个文件或目录。

每个 unit 都有一个对应的配置文件,通常情况文件名为 “xxxx.<unit-type>",用户可以将这些配置文件放在 /etc/systemd/ 和 /usr/lib/systemd/ 中的 system 目录中。

PS:关于 unit 配置文件搜索路径的介绍,可以参考这篇博客

以下是 systemd man 提供参考文档。

用户可以在 Linux 环境通过 man 命令打开对应说明文档,比如通过 man systemd.timer 我们可以查询 timer unit 的具体配置信息。

CGroup

为了描述 cgroup 和 systemd 的关系,我们简单回顾一下 cgroup 中的几个概念:

  • Task:表示系统中的进程,cgroup 对 task 对其 cpu、mem 等资源进行限制。
  • Subsystem:子系统,表示一个资源调度控制器,cgroup 支持 cpu、memory 等 subsystem。
  • Controller Group:控制组,对一种、多种资源设置限制,是 group 进行资源控制的基本单位。Task 可以加入到某个控制组中或一个控制组迁移到另一个。
  • Hierarchy:由一系 Controller Group 组成的树状结构。

上述抽象中,Hierarchy 是一个让人困惑的概念,不少中文博客中将其翻译成“层级”。Redhat CGroup 手册 中将 cgroup 和 Linux Process 进行了对比,借此我们能对 CGroup 的组织架构有更加感性的认识。

在 Linux 进程模型中,每一个进程都是由 init 进程派生的,并且还可能派生出自己的子进程,子进程从父进程继承了环境变量以及其他属性。所有进程构成了一棵 “进程树”,根节点是 init 进程,其他进程都是树中的一个节点。cgroup 的组织架构和 Linux 进程模型类似,同样存在一棵 “cgroup 树”,树中的每个节点是 Controller Group(控制组),子控制组继承了父节点的属性,我们将系统中的“cgroup 树”称为 Hierarchy 。在 cgroup v1 中,系统中可能存在多个 Hierarchy ,每个 Hierarchy 绑定了一个或者多个 Subsystem ,用户可以在这些 Hierarchy 中创建控制组,并将 task 加入的控制组中实现资源限制。

由于 cgroup 是多 Hierarchy 结构,为了简化实现 cgroup v1 中存在以下关系限制(参考):

  • Hierarchy 可以关联多个 subsystems;
  • 单个 subsystem 可以关联到多个 Hierarchy,前提条件是这些 Hierarchy 只关联了这一个 subsystem;
  • Task 可以同时处于不同 Hierarchy,但是不能同时处于同一个 Hierarchy 的不同控制组,对于任何新创建的 Hierarchy,所有 task 都处于它的默认控制组中;
  • 子进程和父进程处于同一个控制组中,但用户可以随时将它移动到其他控制组;

systemd 和 cgroup

当 Linux 的 init 系统发展到 systemd 之后,systemd 与 cgroups 发生了融合。systemd 在通过 cgroup 的能力实现了针对 service.unit 和 scope.unit 的资源管理。

在系统的开机阶段,systemd 会为系统创建以下默认 Hierarchy:

默认 Hierarchy 全部被挂载在 /sys/fs/cgroup 目录下。其中除了 systemd 以外,其他 Hierarchy 均关联一种或者多种 subsystem。systemd 是一个特殊的 Hierarchy ,它没有挂载任何 subsystem 。

由于 cgroup 基于树型结构管理,systemd 为了能够方便的融入这个体系中,提出了 slice.unit 的概念。每个 slice.unit 在 /sys/fs/cgroup/systemd 均有一个对应的节点,并且可以添加任意数量 service.unit 和 scope.unit ,每个 unit 和 scope 都是 /sys/fs/cgroup/systemd 中的一个叶子节点。

通过 systemd slice 所有 unit 组成了一棵树,当用户需要对某个 unit 进行资源限制时,systemd 会将 /sys/fs/cgroup/systemd 的树形结构负责到挂载了对应 subsystem 的默认 Hierarchy 中,并修改对应的参数。

默认情况下,systemd 创建了以下 slice,其中 -.slice 是节点,其他 slice 均继承 -.slice,并且管理了不同类型的进程:

  • -.slice:根
  • user.slice:用户 session 进程
  • system.slice:系统 service 和 scope 进程
  • machine.slice:虚拟机进程

用户可以通过在一下目录中创建自定义的 slice:

  • /etc/systemd/system
  • /run/systemd/system
  • /usr/lib/systemd/system

以创建以下结构为例,用户可以使用 systemd-run 命令快速将 top -b 进程放入指定的 slice 位置中:

1
2
3
4
# 创建 parent.slice
systemd-run --unit=parent --slice=parent top -b
# 创建 children.slice,他是 parent.slice 的子节点
systemd-run --unit=children --slice=parent-children top -b

当然我们也可以通过配置文件的方式创建 slice,参考系统默认创建的 system.slice,配置如下:

1
2
3
4
5
6
7
8
9
# /usr/lib/systemd/system/system.slice

[Unit]
Description=System Slice
Documentation=man:systemd.special(7)
DefaultDependencies=no
Before=slices.target
Wants=-.slice
After=-.slice

参考