Systemd 的服务管理模型

Systemd 是 CoreOS 采用的服务进程和系统资源管理工具。它最初的设计目标主要是克服传统 Linux 主流启动程序 SysV-init 的固有设计缺陷,提高系统的启动速度。 Systemd 的设计思路借鉴了 Mac OSX 系统的启动程序 Launchd,相比其他的 SysV-init 替代方案,例如 Ubuntu 的 Upstart,Systemd 的设计更加前卫。

事实上,Systemd 是一系列系统工具的集合,其作用也远远不仅是启动操作系统,它还接管了后台服务启动、结束、状态查询,以及日志归档、设备管理、电源管理、定时任务等许多指责,并支持通过特定事件(如插入特定 USB 设备)和特定端口数据触发的 On-demand 任务。在 CoreOS 的世界里,几乎所有与应用程序打交道的工作,都会交由 Systemd 进行管理,包括运行在容器中的服务。

Systemd的设计理念

  • 尽可能启动更少的进程
  • 尽可能将更多的进程并行启动
  • 采用 CGroup 跟踪和管理进程的生命周期
  • 统一管理服务日志

Systemd的服务管理

Unit

Unit 是 Systemd 管理服务的基本单元,可以认为每个服务就是一个 Unit,并使用一个 Unit 文件定义。在 Unit 文件中需要包含相应服务的描述、属性以及需要运行的命令。

在 CoreOS 中运行服务的命令通常是一系列的容器操作,而将具体的服务进程封装在容器中。

Target

Target 是 Systemd 中用于指定服务启动组的方式,相当于 SysV-init 中的“运行级别”。每次系统启动时都会运行与当前系统具有相同级别 Target 关联的所有服务,如果服务不需要跟随系统自动启动,则完全可以忽略这个 Target 的内容。

通常来说,大多数 Linux 用户平时使用的都是“多用户模式”这个级别,对应的 Target 值为 “multi-user.target”,它有一个等效的可用值是“default.target”。

Hello Systemd

在 CoreOS 中,用户的自定义服务配置文件通常放在 /etc/systemd/system目录中。进入这个目录,新建一个名为“hello.service”的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=Hello Systemd
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello Systemd; sleep 1; done"
ExecStop=/usr/bin/docker stop busybox1
ExecStopPost=/usr/bin/docker rm busybox1
[Install]
WantedBy=multi-user.target

这里需要注意两个地方:

  1. ExecStart 属性只能包含一条主要命令,而在这个属性的前后可以分别使用 ExecStartPre 和 ExecStartPost 指定更多的辅助命令;ExecStop同理。
  2. 有些辅助命令会加上一个减号(-),表示忽略这些命令的出错。

日志管理

Systemd 通过其标准日志服务 Journald 将其管理的所有后台进程打印到 std:out(即控制台)的输出重定向到了日志文件。

日志文件是二进制格式的,必须使用 Journald 提供的配套程序 journalctl 来查看。

Journalctl

Journalctl 的使用非常简单,默认不带任何参数时会输出系统和所有后台进程的混合日志,可以通过一些参数选择性地过滤所需的内容。

  • –dmesg:内核输出的日志
  • –system:各类系统服务的控制台输出
  • –unit xxx.service:用户指定的服务日志

高级用法:

  • –follow:实时跟踪日志输出
  • –since和–unit:指定显示的日志时间区间(e.g. –since “2015-07-20 12:00:00” –until “2015-07-20 12:30:00”),–since=today可以直接打印当天输出的日志。
  • –reverse:反向输出日志
  • –lines:查看最新的N行日志(e.g. –lines n)
  • –output:改变输出日志的格式(e.g. –output json-pretty)

另外,除了使用 –unit 参数指定 Unit 名称外,还可以指定程序名称和指定进程 PID。

  • $ journalctl /usr/lib/systemd/systemd
  • $ journalctl _PID=1

日志大小限制

默认日志最大限制为所在文件系统容量的 10%。即:如果/var/log/journal存储在 50GB 的根分区中,那么日志最多存储 5GB 数据。

可以通过修改 /etc/systemd/journald.conf中的 SystemMaxUse来指定该最大限制。比如SystemMaxUse=50M

服务的生命周期

  1. 当一个新的 Unit 文件被放入 /etc/systemd/system//usr/lib/systemd/system/目录中时,它是不会被 Systemd 识别到的;($ systemctl list-unit-files)
  2. systemctl start|enable 激活;($ systemctl list-units )
  3. 使用 systemctl start 命令可以启动指定的服务,启动时会一次执行定义在 Unit 文件中的 ExecStartPre、ExecStart、ExecStartPost命令
  4. 使用 ststmectl stop 命令可以结束指定的服务,结束时会依次执行定义在 Unit 文件中的 ExecStopPre、ExecStop、ExecStopPost命令;
  5. systemctl restart 命令相当于先结束指定的服务,然后立即重新启动它;
  6. systemctl kill命令会立即杀死服务,而不会执行 Unit 中指定的结束命令;
  7. systemctl enable xxx.service 设置开机启动;
  8. systmectl disable xxx.service 取消开机启动;
  9. systemctl daemon-reload 重新加载 Unit 文件;
  10. systemctl reset-failed 移除已经被标记为丢失的 Unit 文件。

服务的Unit文件

服务的 Unit 文件可以分为三个配置区段,其中 Unit 段和 Install 段是所有 Unit 文件通用的,用于配置服务(或其他系统资源)的描述、依赖和随系统启动方式,而 Service 段则是服务类型的 Unit 文件(后缀为.service)特有的,用于定义服务的具体管理和操作方法。

配置区段的常用参数

  • Unit
    • Description: 一段描述这个 Unit 文件的文字。
    • Documentation: 指定服务的文档,可以是一个或多个文档的路径。
    • Requires: 依赖的其他 Unit 列表,依赖服务启动失败会导致这个服务被终止。
    • Wants: 与 Requires 相似,但不考虑依赖服务是否启动成功。
    • After: 与 Requires 相似,但是在列出的服务都启动完成后才会启动当前服务。
    • Before: 与 After 相反。
    • BindsTo: 与 Requires 相似,但是关联性更强。
    • Part Of: 这是一个 BindTo 作用的子集,仅在列出的任何模块失败或重启时,终止或重启当前服务,而不会随列出的模块的启动而启动。
    • OnFailure: 当这个模块启动失败时,就会自动启动列出的每个模块。
    • Conflicts: 与这个模块有冲突的模块,如果列出的模块中有已经在运行的,这个服务就不能启动;反之亦然。
  • Install
    • WantedBy: 和前面的 Wants 相似,只是后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。
    • RequiredBy: 和前面的 Requires 相似,只是后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。
    • Also: 当这个服务被 enable/disable 时,将自动 enable/disable 后面列出的每个模块。
  • Service
    • Type: 服务的类型,常用的有 simple 和 forking。
    • RemainAfterExix: 值为 true 或 false(也可以写为 yes 或 no),默认为 false。当配置值为 true 时,Systemd 只会负责启动服务进程,之后即便服务进程退出了,Systemd也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出,燃火等待消息按需启动的特许类型服务使用的。
    • ExecStart: 这个参数是疾呼每个”.service”文件都会有的,指定服务启动的主要命令,在每个配置文件中只能使用一次。
    • ExecStartPre: 指定在启动执行 ExecStart 命令前的准备工作,在同一个配置文件中可以有多个。
    • ExecStartPost: 指定在启动执行 ExecStart 命令后的首位工作,在同一个配置文件中可以有多个。
    • TimeoutStartSec: 启动服务时的超时时间。Docker 第一次运行时可能会需要从网络上下载服务的镜像文件,因此造成比较严重的延时,建议将值指定为 0,从而关闭检测。
    • ExecStop: 停止服务所需要执行的主要命令,在每个配置文件中只能有一个。
    • ExecStopPost: 指定在 ExecStop 命令执行后的收尾工作,在同一个配置文件中可以有多个。
    • TimeoutStopSec: 停止服务时的超时时间。
    • Restart: 这个值用语指定在什么情况下需要重启服务进程。常用的值有 no、on-success、on-failure、on-abnormal、on-abort 和 always。默认值为 no。如下表格表示分别在哪些情况下,服务会被重新启动。
    • RestartSec: 如果服务需要被重启,这个参数的值为服务被重启前的等待秒数。
    • ExecReload: 重新加载服务所需执行的主要命令。
    • Environment: 为服务添加环境变量。
    • EnviromentFile: 指定加载一个包含服务所需的环境变量列表的文件,文件中的每一行都是一个环境变量的定义。
    • Nice: 服务的进程优先级,值越小优先级越高,默认为0。其中-20 为最高优先级,19为最低优先级。
    • WorkingDirectory: 指定服务的工作目录。
    • RootDirectory: 指定服务进程的根目录。如果配置了这个参数,服务将无法访问指定目录以外的任何文件。
    • User: 指定运行服务的用户,会影响服务对本地文件系统的访问权限。
    • Group: 指定运行服务的用户组,会影响服务对本地文件系统的访问权限。
    • MountFlags: 之歌值其实是服务的 Mount Namespace 的配置,会影响服务进程上下文中挂载点的信息,即服务是否会继承主机上已有的挂载点,以及如果服务运行时执行了挂载或卸载设备的操作,是否会真实地在主机上产生效果。可选值是 shared、slava 或 private。
    • LimitCPU/LimitSTACK/LimitNOFILE/LimitNPROC 等,限制特定服务可用的系统资源量。

Systemd 中用于兼容 SysV-init 运行级别的目标 Unit 文件

SysV-init 运行级别 Systemd 运行目标 注释
0 runlevel0.target, poweroff.target 关闭系统
1 runlevel1.target, rescue.target 单用户模式
2 runlevel2.target, multi-user.target 用于定义/域特定运行级别。默认等同于级别 3
3 runlevel3.target, multi-user.target 多用户,无图形界面,用户可以通过终端或网络登录
4 runlevel4.target, multi-user.target 用于定义/域特定运行级别。默认等同于级别 3
5 runlevel5.target, graphical.target 多用户,图形界面,通常为所有运行级别 3 的服务并启动图形界面服务
6 runlevel6.target, reboot.target 重启
emergency emergency.target 急救模式(Emergency shell)

Service 段的 Restart 属性

服务退出原因 no always on-failure on-abnormal on-abort on-success
正常退出
异常退出
启动/停止超时
被异常杀死

Unit 文件占位符

在 Unit 文件中,有时会需要使用到一些与运行环境有关的信息,例如节点 ID、运行服务的用户等。这些信息可以使用占位符来表示,然后在实际运行时被动态地替换为实际的值。

占位符 作用
%n 完整的 Unit 文件名,包括 .service 后缀名
%p Unit 模版文件名中在 @ 符号之前的部分,不包括 @ 符号
%i Unit 模版文件名中在 @ 符号之后的部分,不包括 @ 符号和 .service 后缀名
%t 存放系统运行时文件的目录,通常都是 “/run”
%u 运行服务的用户们,如果 Unit 文件中没有指定,则默认为 root
%U 运行服务的用户 ID
%h 运行服务的用户 Home 目录,即 ${HOME} 环境变量的值
%s 运行服务的用户默认 Shell 类型,即 ${SHELL} 环境变量
%m 实际运行的节点的 Machine ID,对于运行位置敏感的服务比较有用
%b Boot ID,这是一个随机数值,每个节点各不相同,并且每次节点重启时都会改变
%H 实际运行节点的主机名
%v 内核版本,即 “uname -r”命令输出的内容
%% 在 Unit 模版文件中表示一个普通的百分号

Unit 模版

在现实中国年,往往有一些应用需要被复制多份运行,Systemd 定义了一种特殊的服务 Unit 文件,称之为 “Unit 模版”。

Unit 模版文件的写法与普通的服务 Unit 文件几本相同,不过 Unit 模版的文件名是以 @ 符号结尾的。通过模版启动服务实例时,需要在其文件名的 @ 字符后面添加一个参数字符串。

apache@.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=My Advanced Service Template
After=etcd.service docker.service
[Service]
TimeoutStartSec=0
ExecStartpre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStart=/usr/bin/docker run --name apache%i -p %i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/%H:%i running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/docker rm apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/%H:%i
[Install]
WantedBy=multi-user.target

模版服务的启动也与普通的托管于 Systemd 的服务大致相同。

$ sudo systemctl start apache@8080.service