Systemd 简介
Systemd 是一系列工具的集合,其作用也远远不仅是启动操作系统,它还接管了后台服务、结束、状态查询,以及日志归档、设备管理、电源管理、定时任务等许多职责,并支持通过特定事件(如插入特定 USB 设备)和特定端口数据触发的 On-demand(按需)任务。
根据 Linux 惯例,字母 d
是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。
Systemd 的后台服务还有一个特殊的身份——它是系统中 PID 值为 1 的进程。
1 | # pstree |
Systemd 的功能和特性
1、更少的进程。
Systemd 提供了 服务按需启动 的能力,使得特定的服务只有在真正被请求时才启动。
2、允许更多的进程并行启动。
在 SysV-init 时代,将每个服务项目编号依次执行启动脚本。Ubuntu 的 Upstart 解决了没有直接依赖的启动之间的并行启动。而 Systemd 通过 Socket 缓存、DBus 缓存和建立临时挂载点等方法进一步解决了启动进程之间的依赖,做到了所有系统服务并发启动。对于用户自定义的服务,Systemd 允许配置其启动依赖项目,从而确保服务按必要的顺序运行。
3、使用 CGroup 跟踪、监控和管理进程的生命周期。
在 Systemd 之间的主流应用管理服务都是使用 进程树 来跟踪应用的继承关系的,而进程的父子关系很容易通过 两次 fork 的方法脱离。
而 Systemd 则提供通过 CGroup 跟踪进程关系,引补了这个缺漏。通过 CGroup 不仅能够实现服务之间访问隔离,限制特定应用程序对系统资源的访问配额,还能更精确地管理服务的生命周期。
4、同时采用基于 socket 与 D-Bus 总线式激活服务。
基于 socket 的激活机制中,socket 与服务程序分离,并且 socket 由 Systemd 代为监听,服务程序不用马上启动,当 systemd 探测到有其他进程访问这个套接字的时候,才临时按需激活对应服务程序。所以,任何一个依赖于这个服务的其他服务想启动的话,Systemd 只需要把套接字监听起来,其他服务就会都认为依赖的这个服务已经启动了,但实际上服务程序并没有启动,从而实现了服务的并行启动。
在 Linux 上使用 D-Bus 完成进程间通信的服务,可以在第一次被访问的时候,按需激活。
5、基于device的激活机制。
在 Linux 上支持基于 device 激活的系统服务,可以在特定类型的硬件接入到系统上来的时候被激活。比如当机器插入 U 盘时,系统就会把挂载服务激活起来,这个服务就能实现硬件设备的挂载等操作。
6、基于 path 的激活机制。
基于文件的路径进行激活,某个文件路径,变得可用或里面有新文件时,比如某个目录下有文件出现,或者文件发生了变动,这时候就能激活某个服务程序,让某个服务程序工作起来。Systemd 可以监控着管理员所指定的文件路径,一旦文件或目录发生了变化就立即激活相关的服务。
7、支持快照和系统恢复。
Systemd 能够保存各个 unit 的当前状态信息到持久存储设备(如硬盘)中,必要时可以自动载入。随后启动时还可以从保存快照的状态开始继续向后运行。比如我们将机器关机了,所有的服务就停止了,下一次启动的时候只能从头开始。很多服务在内存中会运行是会有很多临时数据,比如客户端对服务端的访问状态信息,这些数据在服务停止后就消失了。如果我们用了快照,可以在下次重启机器之前,先让 Systemd 把每一个运行的 unit 的当前状态信息和相关数据都保存到磁盘上,在重启完机器后再把快照重新载入,这样,所有的临时数据都不会丢失。
8、向后兼容 sysv init 脚本。
Systemd 只支持有限的运行级别,不支持早先的 0-6 的 runlevel,Systemd 对这些级别的模拟是有限的。Systemd 是不支持级别的,它也用不到级别,只是出于兼容的目的,而设计了一些 target 的 unit ,用来映射和实现级别。
另外, Systemd 并不是完全兼容 sysv 的 int 脚本, Systemd 服务由 systemctl 命令固定不变来管理的,这个命令固定不变,不可进行扩展。如果服务不是由 Systemd 启动的,那么 systemctl 无法与之通信。Systemd 不会从任何用户那里继承环境变量,因此建议在使用时都用绝对路径。每一个 unit 的操作都会受到 5 分钟超时时间限制,如果任何一个服务在 5 分钟内未能正常执行,都会在超时后退出运行。
9、统一管理服务日志。
Systemd 是一系列工具的集合, 包括了一个专用的系统日志管理服务:Journald 。这个服务的设计初衷是克服现有 Syslog 服务的日志内容易伪造和日志格式不统一等缺点,Journald 用 二进制格式 保存所有的日志信息,因而日志内容很难被手工伪造。Journald 还提供了一个 journalctl
命令来查看日志信息,这样就使得不同服务输出的日志具有相同的排版格式, 便于数据的二次处理。
除此之外,功能还包括控制基础系统配置,设置并维护登陆用户列表以及系统账户、运行时目录,可以运行容器和虚拟机,可以简单地管理网络配置、网络时间同步、日志转发和名称解析等。
Systemd 架构
相比于之前的版本,Systemd 最关键的特性是:
延迟启动某些服务和系统功能,等到需要他们的时候才开启
完全并行启动
上图是 Systemd 的架构图。
Systemd 的 Unit 文件
Systemd 可以管理所有系统资源,不同的资源统称为 Unit(单元)。
在 Systemd 的生态圈中,Unit 文件统一了过去各种不同系统资源配置格式,例如服务的启/停、定时任务、设备自动挂载、网络配置、虚拟内存配置等。而 Systemd 通过不同的文件后缀来区分这些配置文件。
Systemd 支持的 12 种 Unit 文件类型
.automount
:用于控制自动挂载文件系统,相当于 SysV-init 的 autofs 服务.device
:对于/dev
目录下的设备,主要用于定义设备之间的依赖关系.mount
:定义系统结构层次中的一个挂载点,可以替代过去的/etc/fstab
配置文件.path
:用于监控指定目录或文件的变化,并触发其它 Unit 运行.scope
:这种 Unit 文件不是用户创建的,而是 Systemd 运行时产生的,描述一些系统服务的分组信息.service
:封装守护进程的启动、停止、重启和重载操作,是最常见的一种 Unit 文件.slice
:用于表示一个 CGroup 的树,通常用户不会自己创建这样的 Unit 文件.snapshot
:用于表示一个由systemctl snapshot
命令创建的 Systemd Units 运行状态快照.socket
:监控来自于系统或网络的数据消息,用于实现基于数据自动触发服务启动.swap
:定义一个用户做虚拟内存的交换分区.target
:用于对 Unit 文件进行逻辑分组,引导其它 Unit 的执行。它替代了 SysV-init 运行级别的作用,并提供更灵活的基于特定设备事件的启动方式.timer
:用于配置在特定时间触发的任务,替代了 Crontab 的功能
Systemd 相关的目录
Unit 文件按照 Systemd 约定,应该被放置指定的三个系统目录之一中。这三个目录是有优先级的,如下所示,越靠上的优先级越高。因此,在三个目录中有同名文件的时候,只有优先级最高的目录里的那个文件会被使用。
/etc/systemd/system
:用户或系统在本地配置的 unit 文件。/run/systemd/system
:运行时生成的的 unit 文件。/usr/lib/systemd/system
:系统或第三方软件包安装时生成的 unit 文件。
Systemd 默认从目录 /etc/systemd/system/
读取配置文件。但是,这个目录下存放的大部分文件都是符号链接,指向目录 /usr/lib/systemd/system/
,真正的配置文件存放在那个目录。
可以通过下面的指令查询配置目录:
1 | pkg-config systemd --variable=systemdsystemunitdir #单元目录 |
Unit 和 Target
Unit 是 Systemd 管理系统资源的基本单元,可以认为每个系统资源就是一个 Unit ,并使用一个 Unit 文件定义。在 Unit 文件中需要包含相应服务的描述、属性以及需要运行的命令。
Target 是 Systemd 中用于指定系统资源启动组的方式,相当于 SysV-init 中的运行级别。
简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。从这个意义上说,Target 这个概念类似于 ”状态点”,启动某个 Target 就好比启动到某种状态。
Systemd Service Unit
Unit 文件结构
1 | # /usr/lib/systemd/system/chronyd.service |
Systemd 服务的 Unit 文件可以分为三个配置区段:
Unit 和 Install 段:所有 Unit 文件通用,用于配置服务(或其它系统资源)的描述、依赖和随系统启动的方式。
Service 段:服务(Service)类型的 Unit 文件(后缀为 .service)特有的,用于定义服务的具体管理和操作方法。
Unit 段
这个区段包含的选项,与单元的类型无关,属于通用的配置。
1 | Description= |
描述这个 Unit 文件的信息。用于 UI 中紧跟单元名称之后要显示的简要描述。 例如 “The Apache HTTP Server” 就是一个好例子。 不好的例子: “high-performance light-weight HTTP server”(太通用) 或 “Apache2”(信息太少)。
1 | Documentation= |
当前单元的文档,也可以设置一个空格分隔的文档列表。这些文档是对此单元的详细说明, 可接受 “http://“, “https://“, “file:”, “info:”, “man:” 五种URI类型。这些URI应该按照相关性由高到低列出。 比如,将解释该单元作用的文档放在第一个, 最好再紧跟单元的配置说明, 最后再附上其他文档。 可以多次使用此选项, 依次向文档列表尾部添加新文档。 但是,如果为此选项设置一个空字符串, 那么表示清空先前已存在的列表。
1 | Requires= |
设置此单元所必须依赖的其他单元的列表,当此单元被启动时,所有这里列出的其他单元也将被启动。 如果此处列出的某个单元被停用或启动失败,那么此单元将被停用。 想要添加多个单元,可以多次使用此选项,也可以设置一个空格分隔的单元列表。 注意,此选项并不影响单元之间的启动或停止顺序。 要想调整单元之间的启动或停止顺序,请使用 After=
或 Before=
选项。 例如,在 foo.service
中设置了 Requires=bar.service
, 但是并未使用 After=
或 Before=
设定两者的启动顺序, 那么,当需要启动 foo.service
的时候,这两个单元会被并行地同时启动。 建议使用 Wants=
代替 Requires=
来设置单元之间的非致命依赖关系, 从而有助于获得更好的健壮性,特别是在某些单元启动失败的时候。
注意,设置了此依赖并不意味着当本单元处于运行状态时,被依赖的其他单元也必须总是处于运行状态。 例如:
(1) 失败的条件检查(例如后文的 ConditionPathExists=
, ConditionPathIsSymbolicLink=
, …)只会导致被依赖的单元跳过启动,而不会导致被依赖的单元启动失败(也就是进入”failed
“状态)。
(2) 某些被依赖的单元可能会自己主动停止(例如有的服务进程可能会主动干净的退出、有的设备可能被用户拔出), 而不会导致本单元自身也跟着一起停止。 要想达到这样的效果,可以同时联合使用 BindsTo=
与 After=
依赖,这样就可以确保:在被依赖的其他单元没有处于运行状态时, 本单元自身永远不会启动成功。
1 | Requisite= |
与 Requires=
类似。 不同之处在于:当单元启动时,这里列出的单元必须已经全部处于启动成功的状态, 否则,将不会启动此单元(也就是直接返回此单元启动失败的消息), 并且同时也不会启动那些尚未成功启动的单元。
1 | Wants= |
此选项是 Requires=
的弱化版。 当此单元被启动时,所有这里列出的其他单元只是尽可能被启动。 但是,即使某些单元不存在或者未能启动成功, 也不会影响此单元的启动。 推荐使用此选项来设置单元之间的依赖关系。
注意,此种依赖关系也可以在单元文件之外通过向 .wants/
目录中添加软连接来设置。
1 | BindsTo= |
与 Requires=
类似,但是依赖性更强: 如果这里列出的任意一个单元停止运行或者崩溃,那么也会连带导致该单元自身被停止。 这就意味着该单元可能因为这里列出的任意一个单元的主动退出、某个设备被拔出、某个挂载点被卸载, 而被强行停止。
如果将某个被依赖的单元同时放到 After=
与 BindsTo=
选项中,那么效果将会更加强烈:被依赖的单元必须严格的先于本单元启动成功之后, 本单元才能开始启动。这就意味着,不但在被依赖的单元意外停止时,该单元必须停止, 而且在被依赖的单元由于条件检查失败(例如后文的 ConditionPathExists=
, ConditionPathIsSymbolicLink=
, …)而被跳过时, 该单元也将无法启动。正因为如此,在很多场景下,需要同时使用 BindsTo=
与 After=
选项。
1 | PartOf= |
与 Requires=
类似, 不同之处在于:仅作用于单元的停止或重启。 其含义是,当停止或重启这里列出的某个单元时, 也会同时停止或重启该单元自身。 注意,这个依赖是单向的, 该单元自身的停止或重启并不影响这里列出的单元。
可以认为是一个 BindTo
作用的子集,仅在列出的任务模块失败或重启时,终止或重启当前服务,而不会随列出模板的启动而启动。
1 | Conflicts= |
指定单元之间的冲突关系。 接受一个空格分隔的单元列表,表明该单元不能与列表中的任何单元共存, 也就是说:
(1) 当此单元启动的时候,列表中的所有单元都将被停止;
(2) 当列表中的某个单元启动的时候,该单元同样也将被停止。
注意,此选项与 After
= 和 Before=
选项没有任何关系。
如果两个相互冲突的单元 A 与 B需要在同一个事务内作为 B 启动, 那么这个事务要么会失败( A 与 B 都是事务中的必要部分[Requires
]), 要么就是必须被修改( A 与B 中至少有一个是事务中的非必要部分)。 在后一种情况下, 将会剔除一个非必要的单元 (若两个都是非必要的单元,则优先剔除 A)。
1 | Before= |
强制指定单元之间的先后顺序,接受一个空格分隔的单元列表。 假定 foo.service
单元包含 Before=bar.service
设置, 那么当两个单元都需要启动的时候, bar.service
将会一直延迟到 foo.service
启动完毕之后再启动。 注意,停止顺序与启动顺序正好相反,也就是说, 只有当 bar.service
完全停止后,才会停止 foo.service
单元。
After=
的含义与 Before=
正好相反。 假定 foo.service
单元包含 After=bar.service
设置, 那么当两个单元都需要启动的时候, foo.service
将会一直延迟到 bar.service
启动完毕之后再启动。 注意,停止顺序与启动顺序正好相反,也就是说, 只有当 foo.service
完全停止后,才会停止 bar.service
单元。
注意,这两个选项仅用于指定先后顺序, 而与 Requires=
, Wants=
, BindsTo=
这些选项没有任何关系。 不过在实践中也经常遇见将某个单元同时设置到 After=
与 Requires=
选项中的情形。 可以多次使用此二选项,以将多个单元添加到列表中。 假定两个单元之间存在先后顺序(无论谁先谁后),并且一个要停止而另一个要启动,那么永远是”先停止后启动”的顺序。 但如果两个单元之间没有先后顺序,那么它们的停止和启动就都是相互独立的,并且是并行的。 对于不同类型的单元来说,判断启动是否已经完成的标准并不完全相同。 特别的是对于设置在 Before=
/After=
中的服务单元来说, 只有在服务单元内配置的所有启动命令全部都已经被调用,并且对于每一个被调用的命令, 要么确认已经调用失败、要么确认已经成功运行的情况下, 才能认为已经完成启动。
1 | OnFailure= |
接受一个空格分隔的单元列表。 当该单元进入失败(“failed
“)状态时, 将会启动列表中的单元。
1 | PropagatesReloadTo= |
接受一个空格分隔的单元列表。 PropagatesReloadTo=
表示 在 reload 该单元时,也同时 reload 所有列表中的单元。 ReloadPropagatedFrom=
表示 在 reload 列表中的某个单元时,也同时 reload 该单元。
1 | IgnoreOnIsolate= |
接受一个布尔值。设为 yes 表示在执行 systemctl isolate ...
命令时,此单元不会被停止。 对于 service, target, socket, timer, path 单元来说,默认值是 false ; 对于 slice, scope, device, swap, mount, automount 单元来说,默认值是 true 。
1 | StopWhenUnneeded= |
如果设为 yes , 那么当此单元不再被任何已启动的单元依赖时, 将会被自动停止。 默认值 no 的含义是, 除非此单元与其他即将启动的单元冲突, 否则即使此单元已不再被任何已启动的单元依赖, 也不会自动停止它。
1 | RefuseManualStart= |
如果设为 yes , 那么此单元将拒绝被手动启动(RefuseManualStart=
) 或拒绝被手动停止(RefuseManualStop=
)。 也就是说, 此单元只能作为其他单元的依赖条件而存在, 只能因为依赖关系而被间接启动或者间接停止, 不能由用户以手动方式直接启动或者直接停止。 设置此选项是为了 禁止用户意外的启动或者停止某些特定的单元。 默认值是 no
1 | AllowIsolate= |
如果设为 yes , 那么此单元将允许被 systemctl isolate
命令操作, 否则将会被拒绝。 唯一一个将此选项设为 yes 的理由,大概是为了兼容SysV初始化系统。 此时应该仅考虑对 target 单元进行设置, 以防止系统进入不可用状态。 建议保持默认值 no
1 | DefaultDependencies= |
默认值 yes 表示为此单元隐式地创建默认依赖。 不同类型的单元,其默认依赖也不同,详见各自的手册页。 例如对于 service 单元来说, 默认的依赖关系是指:
(1) 开机时,必须在基础系统初始化完成之后才能启动该服务;
(2) 关机时,必须在该服务完全停止后才能关闭基础系统。
通常,只有那些在系统启动的早期就必须启动的单元, 以及那些必须在系统关闭的末尾才能关闭的单元, 才可以将此选项设为 no 。 注意,设为 no 并不表示取消所有的默认依赖, 只是表示取消非关键的默认依赖。 强烈建议对绝大多数普通单元保持此选项的默认值 yes 。
1 | RebootArgument= |
当 StartLimitAction
= 或 FailureAction=
触发关机动作时, 此选项的值就是传递给 reboot 系统调用的字符串参数。 相当于 systemctl reboot
命令接收的可选参数。
1 | ConditionFirstBoot= |
这组选项用于在启动单元之前,首先测试特定的条件是否为真。 若为真则开始启动,否则将会(悄无声息地)跳过此单元(仅是跳过,而不是进入”failed”状态)。 注意,即使某单元由于测试条件为假而被跳过,那些由于依赖关系而必须先于此单元启动的单元并不会受到影响(也就是会照常启动)。 可以使用条件表达式来跳过那些对于本机系统无用的单元, 比如那些对于本机内核或运行环境没有用处的功能。 如果想要单元在测试条件为假时进入”failed”状态(而不是跳过), 可以使用对应的另一组 AssertXXX=
选项(见后文)。
1 | ConditionFirstBoot= |
可设为 yes 或 no 。 用于检测 /etc
目录是否处于未初始化的原始状态(重点是 /etc/machine-id
文件是否存在)。 此选项可用于系统出厂后(或者恢复出厂设置之后),首次开机时执行必要的初始化操作。
1 | ConditionPathExists= |
检测指定的路径是否存在, 必须使用绝对路径。 可以在路径前面加上感叹号(!)前缀表示逻辑反转。
1 | ConditionPathExistsGlob= |
与 ConditionPathExists=
类似, 唯一的不同是支持通配符。
1 | ConditionPathIsDirectory= |
检测指定的路径是否存在并且是一个目录,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionPathIsSymbolicLink= |
检测指定的路径是否存在并且是一个软连接,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionPathIsMountPoint= |
检测指定的路径是否存在并且是一个挂载点,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionPathIsReadWrite= |
检测指定的路径是否存在并且可读写(rw),必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionDirectoryNotEmpty= |
检测指定的路径是否存在并且是一个非空的目录,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionFileNotEmpty= |
检测指定的路径是否存在并且是一个非空的普通文件,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionFileIsExecutable= |
检测指定的路径是否存在并且是一个可执行文件,必须使用绝对路径。 可以在路径前面加上感叹号(!
)前缀表示逻辑反转。
1 | ConditionUser= |
检测 systemd 是否以给定的用户身份运行。 参数可以是数字形式的 “UID” 、 或者字符串形式的UNIX用户名、 或者特殊值 “@system
“(表示属于系统用户范围内) 。 此选项对于系统服务无效, 因为管理系统服务的 systemd 进程总是以 root 用户身份运行。
1 | ConditionGroup= |
检测 systemd 是否以给定的用户组身份运行。 参数可以是数字形式的 “GID” 或者字符串形式的UNIX组名。 注意:
(1) 这里所说的”组”可以是”主组”(Primary Group)、”有效组”(Effective Group)、”辅助组”(Auxiliary Group);
(2) 此选项不存在特殊值 @system
如果在条件之前加上管道符(|
),那么这个条件就是”触发条件”, 其含义是只要满足一个触发条件,该单元就会被启动; 如果在条件之前没有管道符(|
),那么这个条件就是”普通条件”, 其含义是必须满足全部普通条件,该单元才会被启动。 如果在某个单元文件内, 同时存在”触发条件”与”普通条件”,那么必须满足全部普通条件, 并且至少满足一个触发条件,该单元才会被启动。 如果需要对某个条件同时使用”|
“与”!
“, 那么”|
“必须位于”!
“之前。 除 ConditionPathIsSymbolicLink=
之外, 其他路径检测选项都会追踪软连接。 如果将上述某个检测选项设为空字符串, 那么表示重置该选项先前的所有设置, 也就是清空该选项先前的设置。
1 | AssertFirstBoot= |
与前一组 ConditionXXX=
测试选项类似, 这一组选项用于在单元启动之前,首先进行相应的断言检查。 不同之处在于,任意一个断言的失败, 都会导致该单元启动失败(也就是进入”failed”状态,并突出记录在日志中)。 如果用户希望清晰的看到某些单元因为特定的条件未能满足而导致无法正常启动, 那么可以使用断言表达式。
Install 段
这个区段定义如何安装这个配置文件,即怎样做到开机启动。包含的是单元的启用信息。
这部分配置的目标模块通常是特定运行目标的 .target
文件,用来使得服务在系统启动时自动运行。事实上,systemd 在运行时并不使用此区段。 只有 systemctl
的 enable
与 disable
命令在启用/停用单元时才会使用此区段。
“启用”一个单元在多数情况下,效果上相当于将这个单元设为”开机时自动启动”或”插入某个硬件时自动启动”; “停用”一个单元多数时候在效果上相当于撤销该单元的”开机时自动启动”或”插入某个硬件时自动启动”。
1 | Alias= |
启用时使用的别名,可以设为一个空格分隔的别名列表。 每个别名的后缀(也就是单元类型)都必须与该单元自身的后缀相同。 如果多次使用此选项,那么每个选项所设置的别名都会被添加到别名列表中。 在启用此单元时,systemctl enable
命令将会为每个别名创建一个指向该单元文件的软连接。 注意,因为 mount, slice, swap, automount 单元不支持别名,所以不要在这些类型的单元中使用此选项。
1 | WantedBy= |
接受一个空格分隔的单元列表, 表示在使用 systemctl enable
启用此单元时, 将会在每个列表单元的 .wants/
或 .requires/
目录中创建一个指向该单元文件的软连接。 这相当于为每个列表中的单元文件添加了 Wants=此单元
或 Requires=此单元
选项。 这样当列表中的任意一个单元启动时,该单元都会被启动。 有关 Wants=
与 Requires=
的详细说明, 参见前面 [Unit]
区段的说明。 如果多次使用此选项,那么每个选项的单元列表都会合并在一起。
在普通的 bar.service
单元内设置 WantedBy=foo.service
选项与设置 Alias=foo.service.wants/bar.service
选项基本上是等价的。 但是对于模板单元来说,情况则有所不同。 虽然必须使用实例名称调用 systemctl enable
命令, 但是实际上添加到 .wants/
或 .requires/
目录中的软连接, 指向的却是模板单元(因为并不存在真正的实例单元文件)。
假设 getty@.service
文件中存在 WantedBy=getty.target
选项, 那么 systemctl enable getty@tty2.service
命令将会创建指向 getty@.service
的软连接 getty.target.wants/getty
@tty2.service
1 | Also= |
设置此单元的附属单元,可以设为一个空格分隔的单元列表。 表示当使用 systemctl enable
启用 或 systemctl disable
停用 此单元时, 也同时自动的启用或停用附属单元。
如果多次使用此选项, 那么每个选项所设置的附属单元列表都会合并在一起。
1 | DefaultInstance= |
仅对模板单元有意义, 用于指定默认的实例名称。 如果启用此单元时没有指定实例名称, 那么将使用这里设置的名称。
Service 段
用来做 Service 的配置,只有 Service 类型的 Unit 才有这个区块。
服务生命周期控制相关
1 | Type= |
设置进程的启动类型。必须设为 simple, exec, forking, oneshot, dbus, notify, idle 之一:
Type=simple
:当设置了ExecStart=
、 但是没有设置Type=
与BusName=
时,simple 就是默认值。如果设为 simple , 那么此时ExecStart=
进程就是该服务的主进程, 并且 systemd 会认为在创建了该服务的主服务进程之后,该服务就已经启动完成。 如果此进程需要为系统中的其他进程提供服务, 那么必须在该服务启动之前先建立好通信渠道(例如套接字), 这样,在创建主服务进程之后、执行主服务进程之前,即可启动后续单元, 从而加快了后续单元的启动速度。 这就意味着对于 simple 类型的服务来说, 即使不能成功调用主服务进程(例如User=
不存在、或者二进制可执行文件不存在),systemctl start
也仍然会执行成功。Type=exec
:与 simple 类似,不同之处在于, 只有在该服务的主服务进程执行完成之后,systemd 才会认为该服务启动完成。 其他后继单元必须一直阻塞到这个时间点之后才能继续启动。换句话说, simple 表示当fork()
函数返回时,即算是启动完成,而exec
则表示仅在fork()
与execve()
函数都执行成功时,才算是启动完成。 这就意味着对于 exec 类型的服务来说, 如果不能成功调用主服务进程(例如User=
不存在、或者二进制可执行文件不存在), 那么systemctl start
将会执行失败。Type=forking
:表示ExecStart=
进程将会在启动过程中使用fork()
系统调用。 也就是当所有通信渠道都已建好、也已经启动成功之后,父进程将会退出,而子进程将作为主服务进程继续运行。 这是传统UNIX守护进程的经典做法。 在这种情况下,systemd 会认为在父进程退出之后,该服务就已经启动完成。 如果使用了此种类型,那么建议同时设置PIDFile=
选项,以帮助 systemd 准确可靠的定位该服务的主进程。 systemd 将会在父进程退出之后立即开始启动后续单元。Type=oneshot
:一次性进程,只运行第一次。 只有在该服务的主服务进程退出之后,systemd 才会认为该服务启动完成,才会开始启动后续单元。 此种类型的服务通常需要设置RemainAfterExit=
选项。 当Type=
与ExecStart=
都没有设置时,Type=oneshot
就是默认值。
Type=dbus
:该服务只有获得了BusName=
指定的 D-Bus 名称之后,systemd 才会认为该服务启动完成,才会开始启动后继单元。 设为此类型相当于隐含的依赖于dbus.socket
单元。 当设置了BusName=
时, 此类型就是默认值。Type=notify
:与 exec 类似,不同之处在于, 该服务将会在启动完成之后通过sd_notify(3)
之类的接口发送一个通知消息。systemd 将会在启动后续单元之前, 首先确保该进程已经成功的发送了这个消息。如果设为此类型,那么下文的NotifyAccess=
将只能设为非 none 值。如果未设置NotifyAccess=
选项、或者已经被明确设为 none ,那么将会被自动强制修改为 main 。注意,目前Type=notify
尚不能与PrivateNetwork=ye
s 一起使用。Type=idle
:服务进程将会被延迟到所有活动任务都完成之后再执行。 这样可以避免控制台上的状态信息与 shell 脚本的输出混杂在一起。 注意:(1) 仅可用于改善控制台输出,切勿将其用于不同单元之间的排序工具; (2) 延迟最多不超过5秒, 超时后将无条件的启动服务进程。
建议对长时间持续运行的服务尽可能使用 Type=simple
(这是最简单和速度最快的选择)。 注意,因为 simple 类型的服务无法报告启动失败、也无法在服务完成初始化后对其他单元进行排序, 所以,当客户端需要通过仅由该服务本身创建的IPC通道(而非由 systemd 创建的套接字或 D-bus 之类)连接到该服务的时候, simple 类型并不是最佳选择。在这种情况下, notify 或 dbus(该服务必须提供 D-Bus 接口) 才是最佳选择, 因为这两种类型都允许服务进程精确地安排何时算是服务启动成功、何时可以继续启动后继单元。 notify 类型需要服务进程明确使用 sd_notify()
函数或类似的API, 否则,可以使用 forking 作为替代(它支持传统的UNIX服务启动协议)。 最后,如果能够确保服务进程调用成功、服务进程自身不做或只做很少的初始化工作(且不大可能初始化失败), 那么 exec 将是最佳选择。 注意,因为使用任何 simple 之外的类型都需要等待服务完成初始化,所以可能会减慢系统启动速度。 因此,应该尽可能避免使用 simple 之外的类型(除非必须)。另外,也不建议对长时间持续运行的服务使用 idle 或 oneshot 类型。
1 | RemainAfterExit= |
值为 true 或 false(默认)。当该服务的所有进程全部退出之后, 是否依然将此服务视为活动(active)状态。当配置为 true 时,Systemd 只会负责启动服务进程,之后即便服务进程退出了,Systemd 也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出,然后等待消息按需启动的特殊类型服务使用的。
1 | GuessMainPID= |
在无法明确定位 该服务主进程的情况下, systemd 是否应该猜测主进程的PID(可能不正确)。 该选项仅在设置了 Type=forking
但未设置 PIDFile=
的情况下有意义。 如果PID猜测错误, 那么该服务的失败检测与自动重启功能将失效。 默认值为 yes
1 | PIDFile= |
该服务PID文件的路径(一般位于 /run/
目录下)。 强烈建议在 Type=forking
的情况下明确设置此选项。 如果设为相对路径,那么表示相对于 /run/
目录。 systemd 将会在此服务启动完成之后,从此文件中读取主服务进程的PID 。 systemd 不会写入此文件,但会在此服务停止后删除它(若仍然存在)。 PID文件的拥有者不必是特权用户, 但是如果拥有者是非特权用户,那么必须施加如下安全限制:
(1) 不能是一个指向其他拥有者文件的软连接(无论直接还是间接);
(2) 其中的PID必须指向一个属于该服务的进程。
1 | ExecStart= |
在启动该服务时需要执行的命令行(命令+参数)。
除非 Type=oneshot
,否则必须且只能设置一个命令行。 仅在 Type=oneshot
的情况下,才可以设置任意个命令行(包括零个), 多个命令行既可以在同一个 ExecStart=
中设置,也可以通过设置多个 ExecStart=
来达到相同的效果。 如果设为一个空字符串,那么先前设置的所有命令行都将被清空。 如果不设置任何 ExecStart=
指令, 那么必须确保设置了 RemainAfterExit=yes
指令,并且至少设置一个 ExecStop=
指令。 同时缺少 ExecStart=
与 ExecStop=
的服务单元是非法的(也就是必须至少明确设置其中之一)。
命令行必须以一个可执行文件(要么是绝对路径、要么是不含任何斜线的文件名)开始, 并且其后的那些参数将依次作为”argv[1] argv[2] …”传递给被执行的进程。 可选的,可以在绝对路径前面加上各种不同的前缀表示不同的含义:
可执行文件前的特殊前缀:
前缀 | 效果 |
---|---|
“@” | 如果在绝对路径前加上可选的 “@” 前缀,那么其后的那些参数将依次作为”argv[0] argv[1] argv[2] …”传递给被执行的进程(注意,argv[0] 是可执行文件本身)。 |
“-“ | 如果在绝对路径前加上可选的 “-“ 前缀,那么即使该进程以失败状态(例如非零的返回值或者出现异常)退出,也会被视为成功退出,但同时会留下错误日志。 |
“+” | 如果在绝对路径前加上可选的 “+” 前缀,那么进程将拥有完全的权限(超级用户的特权),并且 User=, Group=, CapabilityBoundingSet= 选项所设置的权限限制以及 PrivateDevices=, PrivateTmp= 等文件系统名字空间的配置将被该命令行启动的进程忽略(但仍然对其他 ExecStart=, ExecStop= 有效)。 |
“!” | 与 “+” 类似(进程仍然拥有超级用户的身份),不同之处在于仅忽略 User=, Group=, SupplementaryGroups= 选项的设置,而例如名字空间之类的其他限制依然有效。注意,当与 DynamicUser= 一起使用时,将会在执行该命令之前先动态分配一对 user/group ,然后将身份凭证的切换操作留给进程自己去执行。 |
“!!” | 与 “!” 极其相似,仅用于让利用 ambient capability 限制进程权限的单元兼容不支持 ambient capability 的系统(也就是不支持 AmbientCapabilities= 选项)。如果在不支持 ambient capability 的系统上使用此前缀,那么 SystemCallFilter= 与 CapabilityBoundingSet= 将被隐含的自动修改为允许进程自己丢弃 capability 与特权用户的身份(即使原来被配置为禁止这么做),并且 AmbientCapabilities= 选项将会被忽略。此前缀在支持 ambient capability 的系统上完全没有任何效果。 |
@
, -
以及 +
/ !
/ !!
之一,可以按任意顺序同时混合使用。 注意,对于 +
, !
, !!
前缀来说,仅能单独使用三者之一,不可混合使用多个。 注意,这些前缀同样也可以用于 ExecStartPre=
, ExecStartPost=
, ExecReload=
, ExecStop=
, ExecStopPost=
这些接受命令行的选项。
如果设置了多个命令行, 那么这些命令行将以其在单元文件中出现的顺序依次执行。 如果某个无 -
前缀的命令行执行失败, 那么剩余的命令行将不会被继续执行, 同时该单元将变为失败(failed)状态。
当未设置 Type=forking 时, 这里设置的命令行所启动的进程 将被视为该服务的主守护进程。
1 | ExecStartPre= |
设置在执行 ExecStart=
之前/后执行的命令行。 语法规则与 ExecStart=
完全相同。 如果设置了多个命令行, 那么这些命令行将以其在单元文件中出现的顺序依次执行。
如果某个无 -
前缀的命令行执行失败, 那么剩余的命令行将不会被继续执行, 同时该单元将变为失败(failed)状态。
仅在所有无 -
前缀的 ExecStartPre=
命令全部执行成功的前提下, 才会继续执行 ExecStart=
命令。
ExecStartPost=
命令仅在 ExecStart=
中的命令已经全部执行成功之后才会运行, 判断的标准基于 Type=
选项。 具体说来:
- 对于
Type=simple
或Type=idle
就是主进程已经成功启动; - 对于
Type=oneshot
来说就是最后一个ExecStart=
进程已经成功退出; - 对于
Type=forking
来说就是初始进程已经成功退出; - 对于
Type=notify
来说就是已经发送了 “READY=1” ; - 对于
Type=dbus
来说就是已经取得了BusName=
中设置的总线名称。
注意,不可将 ExecStartPre=
用于 需要长时间执行的进程。 因为所有由 ExecStartPre=
派生的子进程都会在启动 ExecStart=
服务进程之前被杀死。
注意,如果在服务启动完成之前,任意一个 ExecStartPre=
, ExecStart=
, ExecStartPost=
中无 -
前缀的命令执行失败或超时, 那么,ExecStopPost=
将会被继续执行,而 ExecStop=
则会被跳过。
1 | ExecReload= |
这是一个可选的指令, 用于设置当该服务被要求重新载入配置时 所执行的命令行。 语法规则与 ExecStart=
完全相同。
另外,还有一个特殊的环境变量 $MAINPID
可用于表示主进程的PID, 例如可以这样使用:
1 | /bin/kill -HUP $MAINPID |
注意,像上例那样,通过向守护进程发送复位信号, 强制其重新加载配置文件,并不是一个好习惯。 因为这是一个异步操作, 所以不适用于需要按照特定顺序重新加载配置文件的服务。 我们强烈建议将 ExecReload=
设为一个能够确保重新加载配置文件的操作同步完成的命令行。
1 | ExecStop= |
这是一个可选的指令, 用于设置当该服务被要求停止时所执行的命令行。 语法规则与 ExecStart=
完全相同。 执行完此处设置的所有命令行之后,该服务将被视为已经停止, 此时,该服务所有剩余的进程将会根据 KillMode=
的设置被杀死(参见 systemd.kill(5))。 如果未设置此选项,那么当此服务被停止时, 该服务的所有进程都将会根据 KillSignal=
的设置被立即全部杀死。 与 ExecReload=
一样, 也有一个特殊的环境变量 $MAINPID
可用于表示主进程的PID 。
一般来说,不应该仅仅设置一个结束服务的命令而不等待其完成。 因为当此处设置的命令执行完之后, 剩余的进程会被按照 KillMode=
与 KillSignal=
的设置立即杀死, 这可能会导致数据丢失。 因此,这里设置的命令必须是同步操作,而不能是异步操作。
注意,仅在服务确实启动成功的前提下,才会执行 ExecStop=
中设置的命令。 如果服务从未启动或启动失败(例如,任意一个 ExecStart=
, ExecStartPre=
, ExecStartPost=
中无 -
前缀的命令执行失败或超时), 那么 ExecStop=
将会被跳过。 如果想要无条件的在服务停止后执行特定的动作,那么应该使用 ExecStopPost=
选项。 如果服务启动成功,那么即使主服务进程已经终止(无论是主动退出还是被杀死),也会继续执行停止操作。 因此停止命令必须正确处理这种场景,如果 systemd 发现在调用停止命令时主服务进程已经终止,那么将会撤销 $MAINPID
变量。
重启服务的动作被实现为”先停止、再启动”。所以在重启期间,将会执行 ExecStop=
与 ExecStopPost=
命令。 推荐将此选项用于那些必须在服务干净退出之前执行的命令(例如还需要继续与主服务进程通信)。当此选项设置的命令被执行的时候,应该假定服务正处于完全正常的运行状态,可以正常的与其通信。 如果想要无条件的在服务停止后”清理尸体”,那么应该使用 ExecStopPost=
选项。
1 | ExecStopPost= |
这是一个可选的指令, 用于设置在该服务停止之后所执行的命令行。 语法规则与 ExecStart=
完全相同。 注意,与 ExecStop=
不同,无论服务是否启动成功, 此选项中设置的命令都会在服务停止后被无条件的执行。
应该将此选项用于设置那些无论服务是否启动成功, 都必须在服务停止后无条件执行的清理操作。 此选项设置的命令必须能够正确处理由于服务启动失败而造成的各种残缺不全以及数据不一致的场景。 由于此选项设置的命令在执行时,整个服务的所有进程都已经全部结束, 所以无法与服务进行任何通信。
注意,此处设置的所有命令在被调用之后都可以读取如下环境变量: $SERVICE_RESULT
(服务的最终结果), $EXIT_CODE
(服务主进程的退出码), $EXIT_STATUS
(服务主进程的退出状态)。 详见 systemd.exec(5) 手册。
1 | RestartSec= |
设置在重启服务(Restart=
)前暂停多长时间。 默认值是100毫秒(100ms)。 如果未指定时间单位,那么将视为以秒为单位。 例如设为”20”等价于设为”20s”。
1 | TimeoutStartSec= |
设置该服务允许的最大启动时长。 如果守护进程未能在限定的时长内发出”启动完毕”的信号,那么该服务将被视为启动失败,并会被关闭。 如果未指定时间单位,那么将视为以秒为单位。 例如设为”20”等价于设为”20s”。 设为 “infinity” 则表示永不超时。 当 Type=oneshot
时, 默认值为 “infinity” (永不超时), 否则默认值等于 DefaultTimeoutStartSec=
的值。
1 | TimeoutStopSec= |
此选项有两个用途:
(1) 设置每个 ExecStop=
的超时时长。如果其中之一超时, 那么所有后继的 ExecStop=
都会被取消,并且该服务也会被 SIGTERM 信号强制关闭。 如果该服务没有设置 ExecStop=
,那么该服务将会立即被 SIGTERM 信号强制关闭。
(2) 设置该服务自身停止的超时时长。如果超时,那么该服务将会立即被 SIGTERM 信号强制关闭(参见 systemd.kill(5) 手册中的 KillMode=
选项)。 如果未指定时间单位,那么将视为以秒为单位。 例如设为”20”等价于设为”20s”。 设为 “infinity” 则表示永不超时。 默认值等于 DefaultTimeoutStopSec
= 的值。
1 | TimeoutSec= |
一个同时设置 TimeoutStartSec=
与 TimeoutStopSec=
的快捷方式。
1 | RuntimeMaxSec= |
允许服务持续运行的最大时长。 如果服务持续运行超过了此处限制的时长,那么该服务将会被强制终止,同时将该服务变为失败(failed)状态。 注意,此选项对 Type=oneshot
类型的服务无效,因为它们会在启动完成后立即终止。 默认值为 “infinity” (不限时长)。
1 | Restart= |
当服务进程 正常退出、异常退出、被杀死、超时的时候, 是否重新启动该服务。 所谓”服务进程” 是指 ExecStartPre=
, ExecStartPost=
, ExecStop=
, ExecStopPost=
, ExecReload=
中设置的进程。 当进程是由于 systemd 的正常操作(例如 systemctl stop|restart
)而被停止时, 该服务不会被重新启动。 所谓”超时”可以是看门狗的”keep-alive ping”超时, 也可以是 systemctl start|reload|stop
操作超时。
该选项的值可以取 no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always 之一。 no(默认值) 表示不会被重启。 always 表示会被无条件的重启。 on-success 表示仅在服务进程正常退出时重启, 所谓”正常退出”是指:退出码为”0”, 或者进程收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 信号之一, 并且 退出码符合 SuccessExitStatus=
的设置。 on-failure 表示 仅在服务进程异常退出时重启, 所谓”异常退出” 是指: 退出码不为”0”, 或者 进程被强制杀死(包括 “core dump”以及收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 之外的其他信号), 或者进程由于 看门狗超时 或者 systemd 的操作超时 而被杀死。
表 2. Restart= 的设置分别对应于哪些退出原因
退出原因(↓) Restart= (→) | no | always | on-success | on-failure | on-abnormal | on-abort | on-watchdog |
---|---|---|---|---|---|---|---|
正常退出 | √ | √ | |||||
退出码不为”0” | √ | √ | |||||
进程被强制杀死 | √ | √ | √ | √ | |||
systemd 操作超时 | √ | √ | √ | ||||
看门狗超时 | √ | √ | √ | √ |
注意如下例外情况:
(1) RestartPreventExitStatus=
中列出的退出码或信号永远不会导致该服务被重启。
(2) 被 systemctl stop
命令或等价的操作停止的服务永远不会被重启。
(3) RestartForceExitStatus=
中列出的退出码或信号将会无条件的导致该服务被重启。
注意,服务的重启频率仍然会受到由 StartLimitIntervalSec
= 与 StartLimitBurst=
定义的启动频率的制约。只有在达到启动频率限制之后, 重新启动的服务才会进入失败状态。
对于需要长期持续运行的守护进程, 推荐设为 on-failure 以增强可用性。 对于自身可以自主选择何时退出的服务, 推荐设为 on-abnormal
1 | SuccessExitStatus= |
额外定义其他的进程”正常退出”状态。 也就是,在退出码”0”、以及表示”正常退出”的 SIGHUP, SIGINT, SIGTERM, SIGPIPE 信号之外, 再额外添加一组表示”正常退出”的退出码或信号。 可以设为一系列 以空格分隔的数字退出码或者信号名称, 例如:SuccessExitStatus=1 2 8 SIGKILL
表示当进程的退出码是 1, 2, 8 或被 SIGKILL 信号终止时, 都可以视为”正常退出”。
如果多次使用此选项, 那么最终的结果将是多个列表的合并。 如果将此选项设为空, 那么先前设置的列表 将被清空。
1 | RestartPreventExitStatus= |
可以设为一系列 以空格分隔的数字退出码或信号名称, 当进程的退出码或收到的信号与此处的设置匹配时, 无论 Restart=
选项 是如何设置的, 该服务都将无条件的禁止重新启动。 例如:RestartPreventExitStatus=1 6 SIGABRT
可以确保退出码 1, 6 与 SIGABRT 信号 不会导致该服务被自动重启。 默认值为空, 表示完全遵守 Restart= 的设置。 如果多次使用此选项,那么最终的结果将是多个列表的合并。 如果将此选项设为空,那么先前设置的列表将被清空。
命令行
主要说明 ExecStart=
, ExecStartPre=
, ExecStartPost=
, ExecReload=
, ExecStop=
, ExecStopPost=
选项的命令行解析规则。
如果要一次设置多个命令,那么可以使用分号 ;
将多个命令行连接起来。 注意,仅在设置了 Type=oneshot
的前提下,才可以一次设置多个命令。 分号自身必须用 \;
表示。
每个命令行的内部都以空格分隔, 第一项是要运行的命令, 随后的各项则是命令的参数。 每一项的边界都可以用单引号或双引号界定, 但引号自身最终将会被剥离。 还可以使用C语言风格的转义序列, 但仅可使用下文表格中的转义序列。 最后,行尾的反斜杠( \
) 将被视作续行符(借鉴了 bash 续行语法)。
命令行的语法刻意借鉴了 shell 中的转义字符与变量展开语法, 但两者并不完全相同。 特别是, 重定向(<
, <<
, >
, >>
)、 管道(|
)、 后台运行(&
), 以及其他下文未明确提及的符号都不被支持。
要运行的命令(第一项)可以包含空格,但是不能包含控制字符。
可以在各项命令参数中使用 “%” 占位符系列替换标记。
支持 ${FOO}
与 $FOO
两种不同的环境变量替换方式。 具体说来就是: ${FOO}
的内容将原封不动的转化为一个单独的命令行参数, 无论其中是否包含空格与引号,也无论它是否为空。 $FOO
的内容将被原封不动的插入命令行中, 但对插入内容的解释却遵守一般的命令行解析规则。 后文的两个例子, 将能清晰的体现两者的差别。
如果要运行的命令(第一项)不是一个绝对路径, 那么将会在编译时设定的可执行文件搜索目录中查找。 因为默认包括 /usr/local/bin/
, /usr/bin/
, /bin/
, /usr/local/sbin/
, /usr/sbin/
, /sbin/
目录, 所以可以安全的直接使用”标准目录”中的可执行程序名称(没必要再使用绝对路径), 而对于非标准目录中的可执行程序,则必须使用绝对路径。建议始终使用绝对路径以避免歧义。
例(1):
1 | Environment="ONE=one" 'TWO=two two' |
这将给 /bin/echo
命令依次传递如下四个参数: “one”, “two”, “two”, “two two”
例(2):
1 | Environment=ONE='one' "TWO='two two' too" THREE= |
这将导致/bin/echo
被执行两次。 第一次被依次传递如下三个参数: “‘one’”, “‘two two’ too”, “” ; 第二次被依次传递如下三个参数: “one”, “two two”, “too” 。
此外,如果想要传递美元符号$
自身, 则必须使用 $$
。 而那些无法在替换时确定内容的变量将被当做空字符串。 注意,不可以在第一项(也就是命令的绝对路径)中使用变量替换。
注意,这里使用的变量必须已经在 Environment=
或 EnvironmentFile=
中定义。 此外,在 systemd.exec(5)
手册的”环境变量”小节中列出的”静态变量”也可以使用。 例如 $USER
就是一个”静态变量”, 而 $TERM
则不是。
注意, 这里的命令行并不直接支持 shell 命令, 但是可以通过模仿下面这个变通的方法来实现:
1 | ExecStart=sh -c 'dmesg | tac' |
例一
1 | ExecStart=echo one ; echo "two two" |
这将导致 echo 被执行两次。 第一次被传递了单独一个 “one” 参数; 第二次被传递了单独一个 “two two” 参数。 因为一次设置了多个命令,所以仅能用于 Type=oneshot
类型。
例二
1 | ExecStart=echo / >/dev/null & \; \ |
这表示向 echo 命令传递五个参数: /
, >/dev/null
, &
, ;
, ls
可以在命令行与环境变量中使用的C语言风格的转义符
转义符 | 实际含义 |
---|---|
\a |
响铃 |
\b |
退格 |
\f |
换页 |
\n |
换行 |
\r |
回车 |
\t |
制表符 |
\v |
纵向制表符 |
\\ |
反斜线 |
\" |
双引号 |
\' |
单引号 |
\s |
空白 |
\xxx |
十六进制数 xx 所对应的字符 |
\nnn |
八进制数 nnn 所对应的字符 |
例子
简单服务
下面是一个 metricbeat 守护进程的服务范例,未设置 Type=
等价于 Type=simple
默认设置。 systemd 执行守护进程之后, 即认为该单元已经启动成功。
1 | [Unit] |
注意,本例中的 /usr/share/metricbeat/bin/metricbeat
必须在启动后持续运行到服务被停止。 如果该进程只是为了派生守护进程,那么应该使用 Type=forking
因为没有设置 ExecStop=
选项, 所以在停止服务时,systemd 将会直接向该服务启动的所有进程发送 SIGTERM 信号。 若超过指定时间依然存在未被杀死的进程,那么将会继续发送 SIGKILL 信号。
默认的 Type=simple
并不包含任何通知机制(例如通知”服务启动成功”)。 要想使用通知机制,应该将 Type=
设为其他非默认值: Type=notify
可用于能够理解 systemd 通知协议的服务; Type=forking
可用于能将自身切换到后台的服务; Type=dbus
可用于能够在完成初始化之后 获得一个 D-Bus 名称的单元。
一次性服务
Type=oneshot
用于那些只需要执行一次性动作而不需要持久运行的单元, 例如文件系统检查或者清理临时文件。 此类单元, 将会在启动后一直等待指定的动作完成, 然后再回到停止状态。 下面是一个执行清理动作的单元:
1 | [Unit] |
注意,在 /usr/sbin/foo-cleanup
执行结束前, 该服务一直处于”启动中”(activating)状态,而一旦执行结束,该服务又立即变为”停止”(inactive)状态。 也就是说,对于 Type=oneshot
类型的服务,不存在”活动”(active)状态。 这意味着,如果再一次启动该服务,将会再一次执行该服务定义的动作。 注意,在先后顺序上晚于该服务的单元, 将会一直等到该服务变成”停止”(inactive)状态后, 才会开始启动。
Type=oneshot
是唯一可以设置多个 ExecStart=
指令的服务类型。 多个 ExecStart=
指令将按照它们出现的顺序依次执行, 一旦遇到错误,就会立即停止,不再继续执行, 同时该服务也将进入”失败”(failed)状态。
可停止的一次性服务
有时候, 单元需要执行一个程序以完成某个设置(启动), 然后又需要再执行另一个程序以撤消先前的设置(停止), 而在设置持续有效的时段中,该单元应该视为处于”活动”(active)状态, 但实际上并无任何程序在持续运行。 网络配置服务就是一个典型的例子。 此外,只能启动一次(不可多次启动)的一次性服务, 也是一个例子。
可以通过设置 RemainAfterExit=yes
来满足这种需求。 在这种情况下,systemd 将会在启动成功后将该单元视为处于”活动”(active)状态(而不是”停止”(inactive)状态)。 RemainAfterExit=yes
虽然可以用于所有 Type=
类型, 但是在实践中主要用于 Type=oneshot
和 Type=simple
类型。 对于 Type=oneshot
类型, systemd 一直等到服务启动成功之后,才会将该服务置于”活动”(active)状态。 所以,依赖于该服务的其他单元必须等待该服务启动成功之后,才能启动。 但是对于 Type=simple
类型, 依赖于该服务的其他单元无需等待, 将会和该服务同时并行启动。 下面的类似展示了一个简单的静态防火墙服务:
1 | [Unit] |
因为服务启动成功后一直处于”活动”(active)状态, 所以再次执行 systemctl start 命令不会有任何效果。
传统的服务
多数传统的守护进程(服务)在启动时会转入后台运行。 systemd 通过 Type=forking
来支持这种工作方式。 对于这种类型的服务,如果最初启动的进程尚未退出, 那么该单元将依然处于”启动中”(activating)状态。 当最初的进程成功退出, 并且至少有一个进程仍然在运行(并且 RemainAfterExit=no
), 该服务才会被视为处于”活动”(active)状态。
对于单进程的传统服务,当最初的进程成功退出后, 将会只剩单独一个进程仍然在持续运行, systemd 将会把这个唯一剩余的进程视为该服务的主进程。 仅在这种情况下,才将可以在 ExecReload=
, ExecStop=
… 之类的选项中使用 $MAINPID
变量。
对于多进程的传统服务,当最初的进程成功退出后,将会剩余多个进程在持续运行, 因此,systemd 无法确定哪一个进程才是该服务的主进程。 在这种情况下,不可以使用 $MAINPID
变量。 然而,如果主进程会创建传统的PID文件, 那么应该将 PIDFile=
设为此PID文件的绝对路径, 以帮助 systemd 从该PID文件中读取主进程的PID,从而帮助确定该服务的主进程。 注意,守护进程必须在完成初始化之前写入PID文件, 否则可能会导致 systemd 读取失败 (读取时文件不存在)。
下面是 named 单进程传统服务的示例:
1 | [Unit] |
下面是 nginx 单进程传统服务的示例:
1 | [Unit] |
服务上下文配置相关
1 | WorkingDirectory= |
设置进程的工作目录。 既可以设为特殊值 ~
表示 User=
用户的家目录,也可以设为一个以 RootDirectory=
为基准的绝对路径。 例如当 RootDirectory=/sysroot
并且 WorkingDirectory=/work/dir
时,实际的工作目录将是 /sysroot/work/dir
。 当 systemd 作为系统实例运行时,此选项的默认值是 /
; 当 systemd 作为用户实例运行时,此选项的默认值是对应用户的家目录。 如果给目录加上 -
前缀,那么表示即使此目录不存在,也不算致命错误。 如果未设置 RootDirectory=
/RootImage=
选项,那么为 WorkingDirectory=
设置的绝对路径将以主机(或容器)的根目录(也就是运行 systemd 的系统根目录)为基准。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。
1 | RootDirectory= |
此选项仅可用于系统单元(不适用于用户单元)。 设置以 chroot 方式执行进程时的根目录。 必须设为一个以主机(或容器)的根目录(也就是运行 systemd 的系统根目录)为基准的绝对路径。 如果设置了此选项,必须确保进程及其辅助文件在 chroot() 监狱中确实可用。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。
1 | RootImage= |
可设为一个块设备节点或者一个普通文件。 此设置的含义与 RootDirectory= 相同,不同之处在于是从块设备或回环文件挂载一个文件系统(而不是直接使用一个现成的目录)。 需要注意的是,块设备或回环文件中必须包含合法的文件系统,同时还需要满足以下条件之一:
(1) 不包含任何分区表;
(2) 仅包含单独一个 Linux 能够识别的 MBR/MS-DOS 或 GPT 分区;
(3) 包含一组完全遵守 Discoverable Partitions Specification 规范的 GPT 分区。
1 | Environment= |
设置进程的环境变量, 接受一个空格分隔的 VAR=VALUE
列表。 可以多次使用此选项以增加新的变量或者修改已有的变量(同一个变量以最后一次设置为准)。 设为空表示清空先前所有已设置的变量。 注意:
(1) 不会在字符串内部进行变量展开(也就是$
没有特殊含义);
(2) 如果值中包含空格或者等号,那么必须在字符串两边使用双引号"
界定。
例如:Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
设置了 VAR1
, VAR2
, VAR3
三个变量,其值分别为 word1 word2
, word3
, $word 5 6
注意,不要使用环境变量向单元中的进程传递机密信息(例如密码与口令之类)。 一方面,环境变量会通过 D-Bus IPC 暴露给其他非特权客户端; 另一方面,环境变量在概念上也不属于需要保护的机密数据。 此外,因为环境变量能够沿进程树传播,并且能够跨越安全边界(例如 setuid/setgid 程序), 所以可能会将机密数据泄漏给不应访问的进程。
1 | EnvironmentFile= |
与 Environment=
类似,不同之处在于此选项是从文本文件中读取环境变量的设置。 文件中的空行以及以分号;
或井号#
开头的行会被忽略, 其他行的格式必须符合 VAR=VALUE
的shell变量赋值语法。 行尾的反斜杠\
将被视为续行符,这与 shell 语法类似。 若想在变量值中包含空格, 则必须在值的两端加上双引号"
界定。
文件必须用绝对路径表示(可以包含通配符)。 但可在路径前加上 -
前缀表示忽略不存在的文件。 可以多次使用此选项,以从多个不同的文件中读取设置。 若设为空,则表示清空所有先前已经从文件中读取的环境变量。
这里列出的文件将在进程启动前的瞬间被读取, 因此可以由前一个单元生成配置文件, 再由后一个单元去读取它。
从文件中读取的环境变量会覆盖 Environment=
中设置的同名变量。 文件的读取顺序就是它们出现在单元文件中的顺序, 并且对于同一个变量,以最后读取的文件中的设置为准。
1 | PassEnvironment= |
将某些 systemd 系统服务管理器进程(PID=1)所持有的环境变量传递给该单元中的进程。 接受一个空格分隔的变量名列表。可以多次使用此选项以传递更多变量。 若设为空,则表示清空先前已设置的所有变量。 如果此处设置的变量并不是系统服务管理器进程(PID=1)所持有的环境变量,那么将会被悄无声息的忽略掉。 注意,此选项仅可用于传递PID=1的 systemd 系统服务管理器进程所持有的环境变量, 因为系统服务单元默认并不自动继承PID=1进程所持有的环境变量。 又因为用户服务单元默认就会自动继承PID≠1的 systemd 用户服务管理器进程的所有环境变量, 所以此选项对于用户服务管理器没有意义。
注意,通过此选项传递过来的环境变量的值会被 Environment=
或 EnvironmentFile=
选项中的同名变量所覆盖。例如:PassEnvironment=VAR1 VAR2 VAR3
传递了 “VAR1”, “VAR2”, “VAR3” 三个变量,其值等于PID=1进程所持有的值。
1 | User= |
设置进程在执行时使用的用户与组。 既可以设为一个数字形式的 UID/GID 也可以设为一个字符串形式的名称。 对于系统服务(由 PID=1 的 systemd 系统实例管理)以及由 root 运行的用户服务(由 root 用户启动的 systemd –user 用户实例管理), User=
的默认值是 “root” ,同时亦可明确将 User=
设为其他用户。 对于普通用户运行的用户服务,User=
的默认值就是该用户自身,并且禁止将 User=
切换为其他用户。 如果没有明确设置 Group=
选项,则使用 User=
所属的默认组。 此选项不影响 带有 “+” 前缀的命令。
注意, 为了避免歧义以及确保在不同 Linux 系统之间的兼容性, 用户与组的名称必须满足以下规则:
(1) 仅可包含 a-z
, A-Z
, 0-9
, _
, -
字符;
(2) 首字母只能是 a-z
, A-Z
, _
之一(也就是禁止使用数字与 “-“ 字符);
(3) 字符串长度必须介于 1~31
之间。
注意:如果在
ExecStart
、ExecStop
等属性中使用了 Linux 命令,则必须要写出完整的绝对路径。对于ExecStartPre
和ExecStartPost
辅助命令,若前面有个 “-” 符号,表示忽略这些命令的出错。因为有些 “辅助” 命令本来就不一定成功,比如尝试清空一个文件,但文件可能不存在。
Unit 文件占位符和模板
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 模板
在现实中,往往有一些应用需要被复制多份运行。例如,用于同一个负载均衡器分流的多个服务实例,或者为每个 SSH 连接建立一个独立的 sshd 服务进程。
Unit 模板文件的写法与普通的服务 Unit 文件基本相同,不过 Unit 模板的文件名是以 @
符号结尾的。通过模板启动服务实例时,需要在其文件名的 @
字符后面附加一个参数字符串。
示例:apache@.service
apache@.service 模板
1 | [Unit] |
启动 Unit 模板的服务实例
在服务启动时需要在 @
后面放置一个用于区分服务实例的附加字符参数,通常这个参数用于监控的端口号或控制台 TTY 编译号。
1 | # systemctl start apache@8080.service |
Systemd 在运行服务时,总是会先尝试找到一个完整匹配的 Unit 文件,如果没有找到,才会尝试选择匹配模板。例如上面的命令,System 首先会在约定的目录下寻找名为 `apache@8080.service的文件,如果没有找到,而文件名中包含
@字符,它就会尝试去掉后缀参数匹配模板文件。对于
apache@8080.service,systemd 会找到
apache@.service` 模板文件,并通过这个模板文件将服务实例化。
例子
允许单元被启用
下面这个 foo.service 单元中的 [Install] 小节表明该单元可以通过 systemctl enable 命令启用。
1 | [Unit] |
执行 systemctl enable 启用命令之后, 将会建立一个指向该单元文件的软链接 /etc/systemd/system/multi-user.target.wants/foo.service
, 表示将 foo.service 包含到 multi-user.target 目标中, 这样,当启动 multi-user.target 目标时, 将会自动起动 foo.service 服务。 同时,systemctl disable 命令 将会删除这个软连接。
覆盖软件包的默认设置
以例如 foo.type 这样的系统单元为例, 有两种修改单元文件的方法:
(1) 将单元文件从 /usr/lib/systemd/system
目录复制到 /etc/systemd/system
目录中, 然后直接修改复制后的副本。 (2) 创建 /etc/systemd/system/foo.type.d
/ 目录, 并在其中创建一些 name.conf 文件, 然后仅针对性的修改某些个别选项。
第一种方法的优点是易于修改整个单元, 因为原有的单元文件会被完全忽略。 但此种方法的缺点是, 当原有的单元文件被更新时, 变更不能在修改后的副本上自动体现出来。
第二种方法的优点是仅需修改个别选项, 并且原有单元文件的更新能够自动生效。 因为 .conf
文件只会按照其文件名的字典顺序,被依次追加到原有单元文件的尾部。 但此种方法的缺点是原有单元文件的更新有可能导致与 .conf
文件中的设置不兼容。
这同样适用于 systemd 用户实例, 只是用户单元文件的文件系统位置不同而已。
下面是一个实例,假定原有的单元文件 /usr/lib/systemd/system/httpd.service
包含以下内容:
1 | [Unit] |
假定系统管理员想要修改几个设置:
(1) 本地并不存在 /srv/webserver
目录,需要修改为 /srv/www
目录。
(2) 让此服务依赖于本地已经存在的 memcached.service
服务(Requires=
), 且在其后启动(After=
)。
(3) 为了加固此服务, 添加一个 PrivateTmp=
设置(参见 systemd.exec(5)
手册)。
(4) 将此服务的进程谦让值重置为默认值”0”。
第一种方法,将原有的单元文件复制到 /etc/systemd/system/httpd.service
并做相应的修改:
1 | [Unit] |
第二种方法, 创建配置片段 /etc/systemd/system/httpd.service.d/local.conf
并在其中填入如下内容:
1 | [Unit] |
注意, 对于单元配置片段, 如果想要移除列表选项(例如 AssertPathExists=
或 ExecStart=
)中的某些项, 那么必须首先清空列表(设为空),然后才能添加(剔除掉某些项之后)剩余的列表项。 注意,因为依赖关系列表(After=
之类)不能被重置为空,所以:
(1) 在配置片段(.conf
)中只能添加依赖关系;
(2) 如果你想移除现有的依赖关系,并重新设定, 那么只能用第一种方法(先复制,然后直接修改复制后的副本)。
使用 systemctl 命令管理 systemd 资源
电源管理
主要涉及关机、系统重启、急救模式等
1 | systemctl reboot # 重启 |
分析系统状态
主要用来查看系统中纳入systemd管理的服务的状态
1 | systemctl status # 系统状态 |
Unit 管理
查看当前系统的所有 Unit
1 | # 列出正在运行的 Unit |
查看 Unit 的依赖关系
1 | # 列出一个 Unit 的所有依赖,默认不会列出 target 类型 |
查看 Unit 的状态
1 | # 显示系统状态 |
输出信息说明:
在彩色终端上,前导点(“●”)使用不同的颜色来标记单元的不同状态。 白色表示 “inactive” 或 “deactivating” 状态; 红色表示 “failed” 或 “error” 状态; 绿色表示 “active” 或 “reloading” 或 “activating” 状态。
以”Loaded:”开头的行显示了单元的加载状态: “loaded” 表示已经被载到内存中; “error” 表示加载失败; “not-found” 表示未找到单元文件; “bad-setting” 表示无法解析单元文件中的关键设置; “masked” 表示已被屏蔽。 同时还包含了单元文件的路径、开机自启状态、预设的启用状态。
以”Active:”开头的行显示了单元的启动状态: “active” 表示已启动成功; “inactive” 表示尚未启动; “activating” 表示正在启动中; “deactivating” 表示正在停止中; “failed” 表示启动失败(崩溃、超时、退出码不为零……)。 对于启动失败的单元,将会在日志中记录下导致启动失败的原因, 以方便事后查找故障原因。
服务的管理
查看系统中的所有服务。
1 | systemctl list-units --type service --all |
查看服务的运行状态。
1 | systemctl status apache.service |
查看服务当前激活与否的状态。
1 | systemctl is-active apache.service |
立即启动一个服务,这会依次启动定义在 Unit 文件中的 ExecStartPre、ExecStart 和 ExecStartPost 命令。
1 | systemctl start apache.service |
立即停止一个服务,这会依次停止定义在 Unit 文件中的 ExecStopPre、ExecStop 和 ExecStopPost 命令。
1 | systemctl stop apache.service |
重启一个服务。
1 | systemctl restart apache.service |
条件式重启,如果某个服务在此前已经启动了才把它重启,如果服务没有启动,就不做任何操作。
1 | systemctl try-restart apache.service |
重载或重启服务。
1 | systemctl reload-or-restart name.service |
重载或条件式重启服务。
1 | systemctl reload-or-try-restart name.service |
将服务取消设置开机自启,这会删除在 /etc/systemd/system/
建立服务的指向 /usr/lib/systemd/system/
中的符号链接。同时使用 --now
则在取消设置开机自启时立即停止服务。
1 | systemctl enable apache.service |
将服务重新设置为开机自启,先 disable 再 enable 。
1 | systemctl reenable |
查看服务是否设置了开机自启。
1 | systemctl is-enabled apache.service |
查看所有服务的开机自启状态。
1 | systemctl list-unit-files --type service |
禁止设定为开机自启,此操作将会把 unit 链接至 /dev/null
,使得它们不可能被启动,这是 disable
的加强版。
1 | systemctl mask apache.services |
取消禁止设定为开机自启
1 | systemctl unmask apache.service |
systemd 会将 Unit 文件的内容写到缓存中,因此当 Unit 文件被更新时,需要告诉 Systemd 重新读取所有的 Unit 文件
1 | # 重载所有修改过的配置文件 |
在删除 Unit 文件后,由于缓存的关系,即使通过 daemon-reload
更新了缓存,在 list-units
中依然会显示标记为 not-found
的 Unit,使用 reset-failed
可解决这个问题
1 | # 移除的 unit 标记为丢失的 |
1 | # 杀死一个服务的所有子进程 |
Target 管理
Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。
在传统的 SysV-init 启动模式里面,有 RunLevel 的概念,跟 Target 的作用很类似。不同的是,RunLevel 是互斥的,不可能多个 RunLevel 同时启动,但是多个 Target 可以同时启动。
1 | # 查看当前系统的所有 Target |
Target 与 SysV-init 进程的主要区别:
默认的 RunLevel(在 /etc/inittab 文件设置)现在被默认的 Target 取代,位置是
/etc/systemd/system/default.target
,通常符号链接到 graphical.target(图形界面)或者multi-user.target(多用户命令行)。启动脚本的位置,以前是
/etc/init.d
目录,符号链接到不同的 RunLevel 目录 (比如/etc/rc3.d
、/etc/rc5.d
等),现在则存放在/lib/systemd/system
和/etc/systemd/system
目录。配置文件的位置,以前 init 进程的配置文件是 /etc/inittab,各种服务的配置文件存放在 /etc/sysconfig 目录。现在的配置文件主要存放在
/lib/systemd
目录,在/etc/systemd
目录里面的修改可以覆盖原始设置。
使用 journalctl 命令管理 systemd 日志
Systemd 通过其标准日志服务 Journald 提供的配套程序 journalctl 将其管理的所有后台进程打印到 stdout(即控制台)的输出重定向到了日志文件。
Systemd 的日志文件是二进制格式的,必须使用 Journald 提供的 journalctl 来查看,默认不带任何参数时会输出系统和所有后台进程的混合日志。
默认日志最大限制为所在文件系统容量的 10%,可以修改 /etc/systemd/journald.conf
中的 SystemMaxUse
来指定该最大限制。
选项说明
journalctl
:不加任何参数,则查看所有日志(默认情况下 ,只保存本次启动的日志)
1 | journalctl |
-f
, --follow
:只显示最新的日志项, 并且不断显示新生成的日志项。 此选项隐含了 -n
选项。
1 | # 实时滚动显示某个 Unit 的最新日志 |
-e
, --pager-end
在分页工具内立即跳转到日志的尾部。 此选项隐含了 -n1000
以确保分页工具不必缓存太多的日志行。 不过这个隐含的行数可以被明确设置的 -n
选项覆盖。 注意,此选项仅可用于 less 分页器。
-n
, --lines=
:显示尾部的最新 x 行日志,此选项的参数:若为正整数则表示最大行数; 若为 “all” 则表示不限制行数; 若不设参数则表示默认值10行。
1 | journalctl -n |
--no-tail
:显示所有日志行, 也就是用于撤销已有的 --lines=
选项(即使与 -f
连用)。
-r
, --reverse
:反转日志行的输出顺序, 也就是将最新的日志显示在前面
1 | journalctl -r -u crond.service |
-o
, --output=
:
控制日志的 输出格式。 可以使用如下选项:
short
:这是默认值, 其输出格式与传统的 syslog 文件的格式相似, 每条日志一行。short-full
:与 short 类似,只是将时间戳字段 按照--since=
与--until=
接受的格式显示。 与 short 的不同之处在于, 输出的时间戳中还包含星期、年份、时区信息,并且与系统的本地化设置无关。short-iso
:与 short 类似,只是将时间戳字段以 ISO 8601 格式 显示。short-iso-precise
:与 short-iso 类似,只是将时间戳字段的秒数精确到了微秒级别(百万分之一秒)。short-precise
:与 short 类似,只是将时间戳字段的秒数精确到了微秒级别(百万分之一秒)。short-monotonic
:与 short 类似,只是将时间戳字段的零值 从内核启动时开始计算。short-unix
:与 short 类似,只是将时间戳字段显示为从”UNIX时间原点”(1970-1-1 00:00:00 UTC)以来的秒数。 精确到微秒级别。verbose
:以结构化的格式显示每条日志的所有字段。export
:将日志序列化为二进制字节流 (大部分依然是文本), 以适用于备份与网络传输(详见 Journal Export Format 文档)。亦可使用 systemd-journal-remote.service(8) 工具将二进制字节流转换为本地 journald 格式。json
:将日志项格式化为 JSON 对象,并用换行符分隔(也就是每条日志一行,详见 Journal JSON Format 文档)。 字段值通常按照 JSON 字符串规范进行编码,但是如下三种情况例外:大于4096字节的字段将被编码为 null 值(可以使用
--all
选项关闭此特性,但是这样做会导致生成又大又长的 JSON 对象)。日志中允许存在同名字段,但是在 JSON 对象中不允许。 因此,同名字段的多个值在 JSON 对象中将会被 编码为一个数组。
包含非打印字符或非UTF8字符的字段, 将被编码为包含原始二进制字节的数组(其中的每个字节都视为一个无符号整数)。
注意,这种编码方式是可逆的(存在空间大小的限制)。
json-pretty
:将日志项按照JSON数据结构格式化, 但是每个字段一行, 以便于人类阅读。json-sse
:将日志项按照JSON结构格式化,每条日志一行,但是用大括号包围, 以符合 Server-Sent Events 的要求。json-seq
:将日志项按照JSON结构格式化, 同时为每条日志加上一个ASCII记录分隔符(0x1E)前缀以及一个ASCII换行符(0x0A)后缀,以符合 JavaScript Object Notation (JSON) Text Sequences (“application/json-seq”) 的要求。cat
:仅显示日志的实际内容, 而不显示与此日志相关的 任何元数据(包括时间戳)。with-unit
:与 short-full 类似, 但是在日志项前缀中使用单元名称代替传统的 syslog 标识。 这对于从模板实例化而来的单元比较有意义, 因为会在单元名称中包含实例化参数。
1 | journalctl -b -o short-full --no-hostname -u sshd.service |
--output-fields=
:一个逗号分隔的字段名称列表,表示仅输出列表中的字段。 仅影响默认输出全部字段的输出格式(verbose, export, json, json-pretty, json-sse, json-seq)。注意, "__CURSOR"
, "__REALTIME_TIMESTAMP"
, "__MONOTONIC_TIMESTAMP"
, "_BOOT_ID"
字段永远输出, 不能被排除。
1 | journalctl -o json-pretty --no-hostname -u sshd.service --output-fields MESSAGE |
--utc
:以世界统一时间(UTC) 表示时间
--no-hostname
:不显示来源于本机的日志消息的主机名字段。 此选项仅对 short 系列输出格式(见上文)有效。
-x
, --catalog
:
在日志的输出中增加一些解释性的短文本, 以帮助进一步说明 日志的含义、 问题的解决方案、支持论坛、 开发文档、以及其他任何内容。 并非所有日志都有这些额外的帮助文本, 详见 Message Catalog Developer Documentation 文档。
注意,如果要将日志输出用于 bug 报告, 请不要使用此选项。
-q
, --quiet
:安静模式, 也就是当以普通用户身份运行时, 不显示任何警告信息与提示信息。 例如: “– Logs begin at …”, “– Reboot –”
-b
:查看系统本次启动的日志(其中包括了内核日志和各类系统服务的控制台输出):,如果不加参数则表示仅显示本次启动的日志。
1 | journalctl -b |
不过,一般更关心的不是本次启动后的日志,而是上次启动时的(例如刚刚系统崩溃的情况):
1 | journalctl -b -0 # 显示本次启动的信息 |
--list-boots
: 列出每次启动的序号(也就是相对于本次启动的偏移量)、32字符的ID、 第一条日志的时间戳、最后一条日志的时间戳。
1 | journactl --list-boots |
-k
, --dmesg
:仅显示内核日志(不显示应用日志)。隐含了 -b
选项以及 _TRANSPORT=kernel
匹配项。
1 | journalctl -k |
--unit
或 -u
:查看指定服务的日志
1 | journalctl -u docker.servcie |
-p
, --priority=
:
根据 日志等级(包括等级范围) 过滤输出结果。 日志等级数字与其名称之间的 对应关系如下:
1 | # 0: emerg |
若设为一个单独的数字或日志等级名称, 则表示仅显示小于或等于此等级的日志(也就是重要程度等于或高于此等级的日志)。 若使用 FROM..TO.. 设置一个范围, 则表示仅显示指定的等级范围内(含两端)的日志。 此选项相当于添加了 “PRIORITY=” 匹配条件。
1 | # 查看指定优先级(及其以上级别)的日志 |
-g
, --grep=
:
使用指定的正则表达式对 MESSAGE=
字段进行过滤,仅输出匹配的日志项。 必须使用 PERL 兼容的正则表达式。
如果正则表达式中仅含小写字母,那么将自动进行大小写无关的匹配, 否则将使用大小写敏感的匹配。是否大小写敏感可以使用下面的 --case-sensitive
选项强制指定。
--case-sensitive[=BOOLEAN]
:是否对正则表达式进行大小写敏感的匹配。
-S
, --since=
, -U
, --until=
:
显示晚于指定时间(--since=
)的日志、显示早于指定时间(--until=
)的日志。 参数的格式类似 “2012-10-30 18:17:16” 这样。 如果省略了”时:分:秒”部分,则相当于设为 “00:00:00” 。 如果仅省略了”秒”的部分则相当于设为 “:00” 。 如果省略了”年-月-日”部分,则相当于设为当前日期。 除了”年-月-日 时:分:秒”格式,参数还可以进行如下设置:
(1) 设为 “yesterday”, “today”, “tomorrow” 以表示那一天的零点(00:00:00)。
(2) 设为 “now” 以表示当前时间。
(3) 可以在”年-月-日 时:分:秒”前加上 “-“(前移) 或 “+”(后移) 前缀以表示相对于当前时间的偏移。--output=short-full
将会严格按照上述格式输出时间戳。
1 | journalctl --since="2012-10-30 18:17:16" |
--disk-usage
:此选项并不用于显示日志内容, 而是用于显示所有日志文件(归档文件与活动文件)的磁盘占用总量。
1 | # 显示日志占据的硬盘空间 |
--vacuum-size=
, --vacuum-time=
, --vacuum-files=
:
这些选项并不用于显示日志内容,而是用于清理过期的日志归档文件(并不清理活动的日志文件),以释放磁盘空间。
--vacuum-size=
可用于限制归档文件的最大磁盘使用量(可以使用 “K”, “M”, “G”, “T” 后缀);--vacuum-time=
可用于清除指定时间之前的归档(可以使用 “s”, “m”, “h”, “days”, “weeks”, “months”, “years” 后缀);--vacuum-files=
可用于限制日志归档文件的最大数量。
注意,--vacuum-size=
对 --disk-usage
的输出仅有间接效果, 因为 --disk-usage
输出的是归档日志与活动日志的总量。 同样,--vacuum-files=
也未必一定会减少日志文件的总数, 因为它同样仅作用于归档文件而不会删除活动的日志文件。
此三个选项可以同时使用, 以同时从三个维度去限制归档文件。 若将某选项设为零, 则表示取消此选项的限制。
此三个选项还可以和 --rotate
一起使用, 表示首先滚动所有日志归档, 然后再执行清理操作。 这样可以确保首先完成所有活动日志的归档, 进而使得清理操作变得非常高效。
1 | # 清理日志使总大小小于 1G(指定日志文件占据的最大空间) |
--no-pager
:日志默认分页输出,使用此项后将改为正常的标准输出,而不将程序的输出内容管道(pipe)给分页程序。
1 | journalctl --no-pager |
例子
不带任何选项与参数, 表示显示全部日志
1 | journalctl |
显示上一次启动所产生的所有内核日志
1 | journalctl -k -b -1 |
合并显示多个 Unit 的日志
1 | journalctl -u nginx.service -u php-fpm.service --since today |
查看指定进程的日志
1 | journalctl _PID=1 |
查看指定用户的日志
1 | journalctl _UID=33 --since today |
仅指定一个匹配条件, 显示所有符合该匹配条件的日志
1 | journalctl _SYSTEMD_UNIT=avahi-daemon.service |
指定了两个不同字段的匹配条件, 显示同时满足两个匹配条件的日志
1 | journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 |
指定了同一个字段的两个不同匹配条件, 显示满足其中任意一个条件的日志
1 | journalctl _SYSTEMD_UNIT=avahi-daemon.service _SYSTEMD_UNIT=dbus.service |
使用 “+” 连接 两组匹配条件, 相当于 逻辑”OR”连接
1 | journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 + _SYSTEMD_UNIT=dbus.service |
要显示由特定单元产生的、关于特定单元的日志, 可使用 -u/--unit=
选项。 journalctl -u name
将被展开为类似于如下过滤器:(参见 systemd.journal-fields(5) 手册以了解对下述日志字段的解释)
1 | _SYSTEMD_UNIT=name.service |
显示所有 D-Bus 进程产生的日志
1 | journalctl /usr/bin/dbus-daemon |
查看某个路径的脚本的日志
1 | journalctl /usr/bin/bash |
错误诊断
这个例子中的失败的服务是 nginx.service :
1、通过 systemd 寻找启动失败的服务。
1 | [root@bongon ~]# systemctl --state failed |
或者使用 systemd 日志:
1 | [root@bongon ~]# journalctl --follow --priority err |
2、发现了启动失败的 nginx.service 服务,输出更多信息。
1 | [root@bongon ~]# systemctl status nginx |
3、通过输出信息可以看到执行失败的进程信息:
1 | Process: 4047 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=1/FAILURE) |
得到 PID 就可以进一步探查错误的详细信息:
1 | [root@bongon ~]# journalctl -b _PID=2301 |
4、我们发现配置文件不正确,导致 nginx 检查失败。因此根据提示信息在 /etc/nginx/nginx.conf
的第 60 行左右检查一下。
1 | proxy_pass https://192.168.123.3 |
5、错误原因是这一行的末尾没有加分号(;
)。修正后重新启动服务:
1 | [root@bogon ~]# systemctl start nginx.service |
Systemd 工具集
systemctl
:用于检查和控制各种系统服务和资源的状态bootctl
:用于查看和管理系统启动分区hostnamectl
:用于查看和修改系统的主机名和主机信息journalctl
:用于查看系统日志和各类应用服务日志localectl
:用于查看和管理系统的地区信息loginctl
:用于管理系统已登录用户和 Session 的信息machinectl
:用于操作 Systemd 容器timedatectl
:用于查看和管理系统的时间和时区信息systemd-analyze
显示此次系统启动时运行每个服务所消耗的时间,可以用于分析系统启动过程中的性能瓶颈systemd-ask-password
:辅助性工具,用星号屏蔽用户的任意输入,然后返回实际输入的内容systemd-cat
:用于将其他命令的输出重定向到系统日志systemd-cgls
:递归地显示指定 CGroup 的继承链systemd-cgtop
:显示系统当前最耗资源的 CGroup 单元systemd-escape
:辅助性工具,用于去除指定字符串中不能作为 Unit 文件名的字符systemd-hwdb
:Systemd 的内部工具,用于更新硬件数据库systemd-delta
:对比当前系统配置与默认系统配置的差异systemd-detect-virt
:显示主机的虚拟化类型systemd-inhibit
:用于强制延迟或禁止系统的关闭、睡眠和待机事件systemd-machine-id-setup
:Systemd 的内部工具,用于给 Systemd 容器生成 IDsystemd-notify
:Systemd 的内部工具,用于通知服务的状态变化systemd-nspawn
:用于创建 Systemd 容器systemd-path
:Systemd 的内部工具,用于显示系统上下文中的各种路径配置systemd-run
:用于将任意指定的命令包装成一个临时的后台服务运行systemd-stdio- bridge
:Systemd 的内部 工具,用于将程序的标准输入输出重定向到系统总线systemd-tmpfiles
:Systemd 的内部工具,用于创建和管理临时文件目录systemd-tty-ask-password-agent
:用于响应后台服务进程发出的输入密码请求
参考链接
- 金步国翻译的 systemctl 中文手册
- 金步国翻译的 systemd.service 中文手册
- 金步国翻译的 systemd.exec 中文手册
- 金步国翻译的 journalctl 中文手册