systemd

来自 Arch Linux 中文维基

摘自项目主页

systemd 是一个 Linux 系统基础组件的集合,提供了一个系统和服务管理器,运行为 PID 1 并负责启动其它程序。功能包括:支持并行化任务;同时采用 socket 式与 D-Bus 总线式启用服务;按需启动守护进程(daemon);利用 Linux 的 cgroups 监视进程;支持快照和系统恢复;维护挂载点和自动挂载点;各服务间基于依赖关系进行精密控制。systemd 支持 SysV 和 LSB 初始脚本,可以替代 sysvinit。除此之外,功能还包括日志进程、控制基础系统配置,维护登录用户列表以及系统账户、运行时目录和设置,可以运行容器和虚拟机,可以简单的管理网络配置、网络时间同步、日志转发和名称解析等。

在历史上,systemd中的“服务”(service)被称作守护进程(daemon),它们在后台运行(即没有UI、不与终端交互),等待特定事件的发生并提供服务。例如,Web服务器会等待一个请求以提供相应的页面,SSH服务器会等待登录请求。除了这种提供完整功能的,还有一些守护进程的工作是隐藏在幕后的,如负责向日志文件写入消息的syslogmetalog,确保系统时间准确的ntpd。更多信息详见daemon(7)

注意: Arch Linux 论坛的这篇帖子 详细地解释了 Arch Linux 迁移到 systemd 的原因。

systemctl 基本用法[编辑 | 编辑源代码]

监视和控制 systemd 的主要命令是 systemctl。其用途包括查看系统状态以及管理系统和服务。详见 systemctl(1)

提示:
  • systemctl 参数中添加 -H 用户名@主机名 可以远程控制其他机器。该功能使用 SSH 连接到远程的 systemd 实例。
  • Plasma 用户可以安装 systemctl 图形前端 systemdgenie。安装后可以在系统管理下找到。

使用单元[编辑 | 编辑源代码]

单元(unit)通常包括但不限于:服务(.service)、挂载点(.mount)、设备(.device)和套接字(.socket)。

使用 systemctl 时,通常需要使用单元文件的全名,包括扩展名(例如 sshd.socket)。不过在以下 systemctl 命令中可以使用简写:

  • 如果无扩展名,systemctl 会假定扩展名为 .service。例如,netctlnetctl.service 是等价的。
  • 挂载点会自动转化为相应的 .mount 单元。例如,/home 等价于 home.mount
  • 与挂载点类似,设备会自动转化为相应的 .device 单元,因此 /dev/sda2 等价于 dev-sda2.device

详见 systemd.unit(5)

注意: 有一些单元的名称包含一个 @ 符号(例如:name@string.service ),这意味着它是模板单元 name@.service 的一个 实例,模板单元的实际文件名中不包括 string 部分(如 name@.service)。string 被称作实例标识符,在 systemctl 调用模板单元时,会将其当作一个参数传给模板单元,模板单元会使用这个传入的参数代替模板中的 %i 指示符。在启动单元时,尝试从模板单元实例化之前,systemd 会先检查 name@string.suffix 文件是否存在。如果存在,就直接使用这个文件,而不是模板实例化(不过,这种“碰撞”非常少见)。大多数情况下,包含 @ 标记都意味着这个文件是模板。如果一个模板单元被调用时没有指定实例标识符,该调用通常会失败,除非是在某些特殊的 systemctl命令(如cat)中使用。

因为调用systemctl时默认了 --system 参数,下列命令默认对系统单元进行操作。若要对(调用用户的)用户单元进行操作,则需在非root身份下执行systemctl --user。参看systemd/用户#基础设置以为所有用户启用或禁用单元。

提示:
  • 下面的大部分命令都可以跟多个单元名,详细信息参见 systemctl(1)
  • --now 选项可与 enabledisablemask 同时使用,可使这些动作立即生效,而非重启后生效。
  • 一个软件包可能会为不同的功能提供多个不同的单元。如果你刚安装了软件包,可以通过 pacman -Qql package | grep -Fe .service -e .socket 命令检查这个软件包提供了哪些单元。
动作 命令 注释
分析系统状态
显示系统状态 systemctl status
列出正在运行的单元 systemctl
systemctl list-units
列出失败的单元 systemctl --failed
列出已安装的单元1 systemctl list-unit-files
显示特定PID对应进程的状态 systemctl status pid cgroup slice,内存占用,父进程
检查单元状态
显示单元的手册页 systemctl help 单元 如果单元支持
显示单元的状态 systemctl status 单元 包括其是否在运行
检查单元是否配置为自动启动(enabled) systemctl is-enabled 单元
启动(start)、重新启动和重新加载单元
立即启动单元 以root身份执行systemctl start 单元
立即停止单元 以root身份执行systemctl stop 单元
重新启动单元 以root身份执行systemctl restart 单元
重新加载单元及其配置 以root身份执行systemctl reload 单元
重新加载 systemd 配置2 以root身份执行systemctl daemon-reload 扫描单元的变动
启用(enable)单元
启用单元:开机时自动启动该单元 以root身份执行systemctl enable 单元
启用单元,并立即启动 以root身份执行systemctl enable --now 单元
取消开机自动启动单元 以root身份执行systemctl disable 单元
重新启用单元3 以root身份执行systemctl reenable 单元 先取消启用,再启用
屏蔽单元
屏蔽单元,使其无法启动4 以root身份执行systemctl mask 单元
取消屏蔽单元 以root身份执行systemctl unmask 单元
  1. systemd.unit(5) § UNIT FILE LOAD PATH 中描述了查找单元文件的路径。
  2. 这不会要求已改变的单元重新加载自己的配置(见重新加载动作)。
  3. 可在单元的[Install]节发生变更(在上一次启用后)时使用。
  4. 既不能手动启动,也无法作为依赖启动。因此屏蔽单元是危险的。 查看已屏蔽的单元:
    # systemctl list-unit-files --state=masked

电源管理[编辑 | 编辑源代码]

安装 polkit 后才能以普通用户身份使用电源管理。如果你正登录在一个本地的 systemd-logind 用户会话,且当前没有其它活动的会话,那么以下命令无需 root 权限即可执行。否则(例如,当前有另一个用户登录在某个 tty),systemd 将会自动请求输入 root 密码。

动作 命令
重启 systemctl reboot
关机 systemctl poweroff
待机 systemctl suspend
休眠 systemctl hibernate
进入混合休眠模式(同时休眠到硬盘并待机) systemctl hybrid-sleep

编写单元文件[编辑 | 编辑源代码]

systemd 单元文件的语法来源于XDG 桌面项配置的.desktop文件,最初的源头则是Microsoft Windows的.ini文件。单元文件可以从多个地方加载,systemctl show --property=UnitPath 可以显示加载目录。主要的加载目录为(按优先级从低到高排列):

  • /usr/lib/systemd/system/ :软件包安装的单元
  • /etc/systemd/system/ :系统管理员安装的单元
注意:
  • systemd 运行在用户模式下时,使用的加载路径是完全不同的。
  • systemd 单元名仅能包含 ASCII 字符,下划线和点号以及有特殊意义的字符('@', '-')。其它字符需要用C风格的"\x2d" 替换或使用对应的预定义语法(“@”,“-”)。参阅 systemd.unit(5)systemd-escape(1)

单元文件的语法,可以参考系统已经安装的单元,也可以参考 systemd.service(5) § EXAMPLES

提示:在单元文件中以#开头的行将被视作注释,但#仅能在一行开头使用。不要在 systemd 的参数后面使用行末注释,否则该单元将不能正常启动。

处理依赖关系[编辑 | 编辑源代码]

使用 systemd 时,可通过正确编写单元配置文件来解决单元间的依赖关系。典型的情况是,单元A要求单元BA启动之前运行。在此情况下,可向单元A配置文件中的 [Unit] 段添加 Requires=BAfter=B 即可。若此依赖关系是可选的,可添加 Wants=BAfter=B。请注意 Wants=Requires= 并不暗含 After= ,即如果 After=选项没有指定,这两个单元将被并行启动。

依赖关系通常被用在服务(service)而不是目标(target)上。例如,network.target 一般会被某个配置网络接口的服务引入作为依赖。所以,若要让自定义单元在系统到达network.target后再启动, 将自定义的单元排在该类配置网络接口的服务之后即可,因为该类服务启动时 network.target 一定已经启动。

服务(service)类型[编辑 | 编辑源代码]

编写自定义的服务(service)文件时,可以选择几种不同的服务启动方式。启动方式可通过配置文件 [Service] 段中的 Type= 参数进行设置。

  • Type=simple :(默认值) systemd认为该服务将立即启动且服务进程不会fork。Do not use this type if other services need to be ordered on this service, unless it is socket activated.
  • Type=forking :systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于典型的守护进程,除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
  • Type=oneshot :这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes使得systemd在服务进程退出之后仍然认为服务处于启用状态,这对于改变系统状态(如挂载分区)的单元尤其适用。
  • Type=notify :与 Type=simple 相同,但约定服务会在就绪后向systemd发送一个信号以通知systemd。这一通知的参考实现由 libsystemd-daemon.so 提供。
  • Type=dbus :若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
  • Type=idlesystemd会等待所有活动任务都完成后再执行服务进程。其他行为与 Type=simple 类似。

type 的更多解释可以参考 systemd.service(5) § OPTIONS

修改现存单元文件[编辑 | 编辑源代码]

为了避免和 pacman 冲突,不应该直接编辑软件包提供的文件。有两种方法可以不改动原始文件就做到修改单元文件:创建一个优先级更高的本地单元文件以覆盖原有的单元文件或创建一个片段(drop-in snippets),应用到原始单元文件之上。两种方法都需要在修改后重新加载单元:用 systemctl edit 编辑单元(会自动重载单元)或通过下面命令重新加载单元:

# systemctl daemon-reload
提示:
  • systemd-delta 命令用来查看哪些单元文件被覆盖、扩增,哪些被修改。
  • 使用 systemctl cat unit 可以查看单元的内容和所有相关的片段。

替换单元文件[编辑 | 编辑源代码]

要替换 /usr/lib/systemd/system/unit, 创建文件 /etc/systemd/system/unit重新启用单元以更新符号链接:

# systemctl reenable unit

或者运行:

# systemctl edit --full unit

这将会在默认编辑器中打开 /etc/systemd/system/unit。如果文件不存在,可以将软件包安装的版本复制到这里,在编辑完成之后,会自动加载新版本。

注意: 即使 Pacman 更新了新的单元文件,软件包中的版本也不会被使用,所以这个方式会增加系统维护的难度,推荐使用下面一种方法。

附加配置片段[编辑 | 编辑源代码]

要为单元文件/usr/lib/systemd/system/unit附加配置片段,先创建名为 /etc/systemd/system/unit.d/ 的目录,然后放入 *.conf 文件,其中可以添加或覆盖参数。这里设置的参数优先级高于原来的单元文件。systemd会解析这些参数并将这些文件应用到原单元文件上。

要附加配置片段,最简单的方法是执行:

# systemctl edit unit --drop-in=drop_in_name

这将会在编辑器中打开文件 /etc/systemd/system/unit.d/drop_in_name.conf,编辑完成之后自动加载。若省略--drop-in=选项,systemd将使用默认文件名:override.conf

注意:
  • 附加配置片段中的键(配置项)仍必须置于相应的节(section)下。
  • 并不是所有参数都会被子配置文件覆盖。例如要修改 Conflicts= 就必须替换原始文件

重置到软件包的版本[编辑 | 编辑源代码]

要回退使用systemctl edit对单元进行的所有变更,执行:

# systemctl revert unit

示例[编辑 | 编辑源代码]

如果想添加一个额外的依赖,创建如下文件即可:

/etc/systemd/system/<unit>.d/customdependency.conf
[Unit]
Requires=<新依赖>
After=<新依赖>

要修改一个单元的 ExecStart 命令,创建下面文件:

/etc/systemd/system/unit.d/customexec.conf
[Service]
ExecStart=
ExecStart=<新命令>

修改 ExecStart 前必须将其置空,参见 [1] 。所有可能多次赋值的变量都需要这个操作,例如定时器的 OnCalendar

下面是自动重启服务的一个例子:

/etc/systemd/system/unit.d/restart.conf
[Service]
Restart=always
RestartSec=30

目标(target)[编辑 | 编辑源代码]

systemd使用目标来通过依赖关系将多个单元组织起来。目标还是系统的标准化同步点(确保系统处于特定状态)。目标runlevels的设计目的相似,但两者也有些许不同。每一个目标都以名称而不是数字标识,用以达成特定的目的。多个目标可以同时被激活。有的目标继承另一个目标的所有服务,同时向其中增添一些服务。有些systemd目标模仿了常见的SystemVinit runlevels,所以仍可用熟悉的telinit RUNLEVEL命令切换目标

获取当前目标[编辑 | 编辑源代码]

使用systemd的命令而非runlevel

$ systemctl list-units --type=target

创建自定义目标[编辑 | 编辑源代码]

在sysvinit中有明确定义的运行级别(如:0、1、3、5、6)与 systemd 中特定的目标存在一一对应的关系。然而,对于用户自定义运行级别(2、4)却没有。如需要同样功能,建议你以原有运行级别所对应的 systemd 目标为基础,新建一个/etc/systemd/system/<目标名>.target(可参考/usr/lib/systemd/system/graphical.target), 然后创建目录/etc/systemd/system/<目标名>.wants,并向其中加入需启用的服务链接(指向/usr/lib/systemd/system/中的对应文件)。

“SysV运行级别”与“systemd目标”对照表[编辑 | 编辑源代码]

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

切换当前运行目标[编辑 | 编辑源代码]

systemd中,目标通过“目标单元”访问。通过如下命令切换:

# systemctl isolate graphical.target

该命令仅更改当前运行目标,对下次启动无影响。这等价于sysvinit中的telinit 3telinit 5命令。

更改开机默认启动目标[编辑 | 编辑源代码]

开机启动的目标是default.target,默认符号链接到graphical.target(大致相当于原来的运行级别5)。

systemctl 检查当前的默认启动目标:

# systemctl get-default

systemctl修改default.target符号链接以变更开机默认启动目标:

# systemctl set-default multi-user.target
Removed /etc/systemd/system/default.target.
Created symlink /etc/systemd/system/default.target -> /usr/lib/systemd/system/multi-user.target.

另一方法是向bootloader添加内核参数

  • systemd.unit=multi-user.target (大致相当于运行级别3)
  • systemd.unit=rescue.target (大致相当于运行级别1)

默认目标顺序[编辑 | 编辑源代码]

Systemd 根据下面顺序选择 default.target

  1. 上面的内核参数
  2. /etc/systemd/system/default.target 软链接
  3. /usr/lib/systemd/system/default.target 软链接

systemd组件[编辑 | 编辑源代码]

systemd的部分组件如下:

systemd.mount - 挂载[编辑 | 编辑源代码]

systemd负责挂载/etc/fstab中指定的分区和文件系统。systemd-fstab-generator(8)/etc/fstab中的所有条目翻译为systemd单元。该过程将在系统启动或系统管理器的配置被重新加载时执行。

systemd扩展了通常fstab的用法并提供了更多挂载选项。这些挂载选项可影响挂载点单元的依赖关系。例如,它们可以确保某一挂载操作只会在连接网络或另一分区挂载后进行。systemd的特定挂载选项(大多以x-systemd.开头)的完整列表见systemd.mount(5) § FSTAB

automounting是这些挂载选项的一个例子。它意味着资源只会在请求时挂载,而非在系统启动时自动挂载,详见fstab#通过 systemd 自动挂载

GPT分区自动挂载[编辑 | 编辑源代码]

在UEFI启动的系统上,当特定条件满足时,systemd-gpt-auto-generator(8)会根据Discoverable Partitions Specification自动挂载GPT分区。因此,可在fstab中省略自动挂载的分区。此外,若根分区已被自动挂载,可省去内核命令行中的root=

使用GPT分区自动挂载的先决条件有:

  • 引导加载程序必须设置LoaderDevicePartUUIDEFI变量,以便识别使用的EFI系统分区。systemd-bootsystemd-stub(7)GRUB (使用grub-mkconfig生成grub.cfg),rEFInd(默认未启用)支持该特性。若使用自定义的grub.cfg,则需要加载bli模块。 可通过执行bootctl并检查Boot loader sets ESP information的状态进行确认,或在用Unified kernel image启动时检查Stub sets ESP information的状态。
  • 根分区与EFI系统分区(ESP)必须在同一块物理硬盘上。其它自动挂载的分区必须与根分区在同一块物理硬盘上。这基本上意味着所有自动挂载分区必须和ESP分区在同一块物理硬盘上。
  • 若需要/efi挂载点,则必须手动创建它。否则systemd-gpt-auto-generator将使用/boot作为挂载点。
警告: 若使用GPT分区自动挂载,在现有系统上创建/efi时需小心行事。在下次启动时,/efi将作为默认挂载点,这可能导致/boot目录为空,使系统陷入不稳定状态。若出现这种情况,将需要执行重新安装内核、处理器微码并重新生成initramfs等操作。
提示:某个分区的自动挂载可通过如下两种方式关闭:修改分区的类型GUID或设置分区属性的63"do not automount"位,详见gdisk#Prevent GPT partition automounting
/var[编辑 | 编辑源代码]

要自动挂载/var分区,分区的PARTUUID必须与以machine ID为密钥,分区类型UUID(4d21b016-b534-45c2-a9fb-5c16e091fd2d)为消息的SHA256 HMAC哈希计算结果相同。可通过如下命令得到符合要求的PARTUUID:

$ systemd-id128 -u --app-specific=4d21b016-b534-45c2-a9fb-5c16e091fd2d machine-id
注意: systemd-id128(1)/etc/machine-id读取machine ID,因此必须在系统安装后才能得到符合要求的PARTUUID。

systemd-sysvcompat[编辑 | 编辑源代码]

systemd-sysvcompat(由base需要)的主要工作是提供传统的Linux init可执行文件。在由systemd控制的系统上,init只是一个到systemd可执行文件的符号链接。

此外,该包还提供了SysVinit用户可能需要的功能的4个快捷方式:halt(8)poweroff(8)reboot(8)shutdown(8)。这4个命令都是到systemctl的符号链接,其行为受systemd控制。因此,在#电源管理中提到的规则对它们同样适用。

在由systemd控制的系统上,可通过init=内核启动参数取消对System V的兼容性支持。详见/bin/init is in systemd-sysvcompat ?

systemd-tmpfiles - 临时文件[编辑 | 编辑源代码]

systemd-tmpfiles创建,删除并清理临时文件/文件夹。systemd-tmpfiles读取/etc/tmpfiles.d//usr/lib/tmpfiles.d/中的配置文件(前者比后者优先级高)以确定执行什么操作。

这些配置文件通常与服务文件一同提供,并以/usr/lib/tmpfiles.d/<程序>.conf风格命名。例如,Samba守护进程需要/run/samba目录存在且权限设置正确,因此samba中附带了如下配置:

/usr/lib/tmpfiles.d/samba.conf
D /run/samba 0755 root root

配置文件也可能用于在启动时向特定文件中写入值。例如,要禁止系统从USB设备唤醒。在之前可使用/etc/rc.local在启动时执行echo USBE > /proc/acpi/wakeup,而现在可以这么做:

/etc/tmpfiles.d/disable-usb-wake.conf
#    Path                  Mode UID  GID  Age Argument
w    /proc/acpi/wakeup     -    -    -    -   USBE

详见systemd-tmpfiles(8)tmpfiles.d(5)

注意: 若要向/sys中的配置文件写入值,可能不能使用该方法。因为systemd-tmpfiles-setup服务可能在相关模块加载前运行。在这种情况下,可先通过modinfo <模块名>检查该模块是否可通过设定模块参数来修改相应的选项。若有对应的模块参数,可通过/etc/modprobe.d目录中的配置文件设定相应参数以达到修改选项的目的。否则,将只能通过udev规则来在设备被识别时设定相应属性。

小技巧[编辑 | 编辑源代码]

GUI配置工具[编辑 | 编辑源代码]

  • systemadm — 用于systemd单元的图形化浏览器。可显示单元列表,并能按类型筛选。
https://cgit.freedesktop.org/systemd/systemd-ui/ || systemd-ui
  • SystemdGenie — 基于KDE的systemd管理工具。
https://invent.kde.org/system/systemdgenie || systemdgenie

在网络已连接后再启动某服务[编辑 | 编辑源代码]

如果需要将某服务延迟到网络已连接后再启动, 直接在 .service 文件中包含以下依赖项:

/etc/systemd/system/<单元名>.service
[Unit]
...
Wants=network-online.target
After=network-online.target
...

要使network-online.target正确反映网络状态,必须启用所使用的网络管理器的网络等待服务。

  • 若使用NetworkManager, NetworkManager-wait-online.service应该与NetworkManager.service一同启用。可通过systemctl is-enabled NetworkManager-wait-online.service进行检查。若服务没用启用,重新启用NetworkManager.service
  • 若使用netctl, 启用netctl-wait-online.service (除非使用了netctl-auto; 详见FS#75836)。
  • 若使用systemd-networkd, systemd-networkd-wait-online.service应该与systemd-networkd.service一同启用。可通过systemctl is-enabled systemd-networkd-wait-online.service进行检查。若服务没用启用,重新启用systemd-networkd.service

如果需要更为详细的解释,请查看网络配置同步点中的讨论。

若某服务需要执行DNS查询,其应该被排在nss-lookup.target后:

/etc/systemd/system/<单元名>.service
[Unit]
...
Wants=network-online.target
After=network-online.target nss-lookup.target
...

详见systemd.special(7) § Special Passive System Units.

nss-lookup.target发挥作用,必须有一个单元通过Wants=nss-lookup.target引入nss-lookup.target,并通过Before=nss-lookup.target排在nss-lookup.target之前。一般情况下,上述配置由本地DNS解析器完成。

检查哪一个正在运行的单元引入了nss-lookup.target

$ systemctl list-dependencies --reverse nss-lookup.target

默认启用新安装的单元[编辑 | 编辑源代码]

Arch Linux附带的/usr/lib/systemd/system-preset/99-default.preset包含disable *,这会导致默认情况下的systemctl preset是禁用所有新安装的的单元。因此某个新包安装后, 用户必须手动启用新单元。

若上述行为不符合预期, 创建一个从/etc/systemd/system-preset/99-default.preset/dev/null的符号链接来覆盖该配置文件,这会导致 systemctl preset无视单元类型直接启用所有单元,除非systemctl preset的配置目录中有文件另有声明。用户单元不受影响。详见systemd.preset(5)

注意: 若某个软件包含有多个互相冲突的单元,默认启用所有单元可能会出现问题。systemctl preset原本旨在供发行版或系统管理员使用。在有两个冲突单元将被同时启用的情况下,应明确在systemctl preset的配置文件中禁用其中一个单元,详见systemd.preset(5)

应用程序环境沙盒[编辑 | 编辑源代码]

可通过一个单元文件创建一个沙盒以在加固的虚拟环境中隔离应用程序及其进程。systemd充分利用namespaces,一系列允许/拒绝capabilitiescgroups,以通过可拓展的执行环境设置(systemd.exec(5))将进程禁锢在容器中。

将现有systemd单元文件进行沙盒化加固通常需要结合使用stracestderrjournalctl(1)等进行大量试验。因此,最好先在上游文档中搜索已完成的测试以作为试验的基础。

首先,最好通过以下命令获得特定单元可能的加固选项作为基础:

$ systemd-analyze security <单元>

关于如何使用 systemd 部署沙盒的一些示例:

通知失败的单元[编辑 | 编辑源代码]

本文或本章节可能需要合并到systemd/Timers#MAILTO

附注: 相同话题,不同解决方案(在 Talk:Systemd 中讨论)

要在服务故障时发出通知,可在相应服务的单元文件中添加OnFailure=设置,这可通过#附加配置片段完成。若要在每一个服务的单元文件中添加该设置,可使用一个顶层附加配置片段,详见systemd.unit(5)

为服务单元创建顶层附加配置片段:

/etc/systemd/system/service.d/toplevel-override.conf
[Unit]
OnFailure=failure-notification@%n

这将向每个服务单元文件中添加OnFailure=failure-notification@%n。若单元甲失败,会启动failure-notification@单元甲来递送相关通知(或执行其它配置的动作)。

创建failure-notification@模板单元:

/etc/systemd/system/failure-notification@.service
[Unit]
Description=Send a notification about a failed systemd unit
After=network.target

[Service]
Type=simple
ExecStart=/path/to/failure-notification.sh %i

接下来可创建failure-notification.sh脚本,定义执行的操作或发送通知的方式(mail,gotify,xmpp等) 。%i将被替换为失败单元的名称并作为参数被传递给failure-notification.sh脚本。

为防止failure-notification@.service的实例启动失败时引发failure-notification@.service实例的递归性启动,在failure-notification@.service单元的配置目录下创建一个与顶层附加配置片段文件名相同的空的附加配置片段(该空的附加配置片段比全局附加配置片段优先级更高):

# mkdir -p /etc/systemd/system/failure-notification@.service.d
# touch /etc/systemd/system/failure-notification@.service.d/toplevel-override.conf

疑难解答[编辑 | 编辑源代码]

寻找失败的单元[编辑 | 编辑源代码]

要寻找失败的systemd单元,执行:

$ systemctl --state=failed

可通过它们的日志查找失败的原因。详见systemd/Journal#过滤输出

诊断系统启动问题[编辑 | 编辑源代码]

systemd有许多用于调试系统启动过程中的问题的选项。要捕获在systemd接管启动流程前的日志,参阅boot debugging。还可参阅freedesktop.org的系统调试文档

诊断一个服务[编辑 | 编辑源代码]

如果某个 systemd 服务的工作状况不合预期或想了解发生了什么的更多信息,将SYSTEMD_LOG_LEVEL环境变量的值设为debug。例如,要以调试模式运行systemd-networkd守护进程:

在此服务的配置文件中加入如下配置片段

[Service]
Environment=SYSTEMD_LOG_LEVEL=debug

或者手动设置该环境变量以达到相同效果:

# SYSTEMD_LOG_LEVEL=debug /lib/systemd/systemd-networkd

之后,重新启动systemd-networkd并以-f/--follow启动journalctl观察该单元的日志。

关机/重启十分缓慢[编辑 | 编辑源代码]

如果关机十分缓慢(甚至跟死机了一样),很可能是某个拒不退出的服务在作怪。对于每个服务systemd会等待一段时间,然后再尝试杀死它。要确定是否受到该问题影响,详见systemd wiki中的这篇文章

一个常见的原因是存在一个搁置的关机或挂起进程。要确认是否为这种情况,执行下列命令中的任何一个并观察有无类似的输出:

# systemctl poweroff
Failed to power off system via logind: There's already a shutdown or sleep operation in progress
# systemctl list-jobs
  JOB UNIT                    TYPE  STATE
...
21593 systemd-suspend.service start running
21592 suspend.target          start waiting
..

该问题的解决方法是取消这些关机/挂起工作:

# systemctl cancel
# systemctl stop systemd-suspend.service

并再次尝试关机或重启。

一些短命进程似乎没有产生任何日志输出[编辑 | 编辑源代码]

对于短命的服务进程(启动后迅速失败),若以root身份执行journalctl -u <单元名>可能看不到任何输出,此时可转而使用进程的PID。例如,若systemd-modules-load.service单元失败,通过systemctl status systemd-modules-load得到的对应进程PID为123,以root身份执行journalctl -b _PID=123或许就能得到日志输出。上述问题的原因是日志元数据(如_SYSTEMD_UNIT_COMM)以异步方式被采集,且依赖于/proc中相应进程的目录。修复该问题需要修改内核以通过socket提供需要的数据(类似于SCM_CREDENTIALS)。简而言之,该问题是一个bug,只需记住由于systemd的设计,在启动后迅速失败的单元可能不会打印任何输出到系统日志中。

systemd-tmpfiles-setup.service在系统启动时失败[编辑 | 编辑源代码]

systemd 219开始,/usr/lib/tmpfiles.d/systemd.conf/var/log/journal下的目录指定了ACL选项。因此存放日志的文件系统必须启用ACL支持。

如何在存放/var/log/journal的文件系统上启用ACL的说明见Access Control Lists#启用 ACL

在远程主机上关闭救援(emergency)模式[编辑 | 编辑源代码]

当救援模式被触发时,机器不会连接网络,因此对于托管于Azure或Google Cloud的远程主机,或许需要关闭救援模式。

要关闭救援模式,屏蔽emergency.serviceemergency.target

参见[编辑 | 编辑源代码]