使用 systemd 管理启动

了解 systemd 如何确定服务启动的顺序,即使它本质上是一个并行系统。
143 位读者喜欢这篇文章。
How Linux became my job

Opensource.com

最近在设置 Linux 系统时,我想知道如何确保服务和其他单元的依赖项在这些依赖服务和单元启动之前启动并运行。具体来说,我需要更多关于 systemd 如何管理启动序列的知识,尤其是在确定服务启动顺序方面,即使它本质上是一个并行系统。

您可能知道 SystemV(systemd 的前身,正如我在本系列第一篇文章中所解释的那样)通过使用 SXX 前缀命名启动脚本来排序启动序列,其中 XX 是 00 到 99 之间的数字。然后 SystemV 使用名称的排序顺序,并为所需的运行级别按顺序运行每个启动脚本。

但是 systemd 使用单元文件,系统管理员可以创建或修改这些文件,以定义不仅用于初始化,还用于常规操作的子例程。在本系列第三篇文章中,我解释了如何创建挂载单元文件。在本文的第五篇文章中,我演示了如何创建不同类型的单元文件——一个服务单元文件,用于在启动时运行程序。您还可以更改单元文件中的某些配置设置,并使用 systemd 日志来查看启动序列中更改的位置。

准备工作

确保您已从 /etc/default/grub 文件中的 GRUB_CMDLINE_LINUX= 行中删除了 rhgbquiet,正如我在本系列第二篇文章中所示。这使您可以观察 Linux 启动消息流,这对于本文中的某些实验是必需的。

程序

在本教程中,您将创建一个简单的程序,使您能够在启动期间在控制台上观察到一条消息,并在稍后在 systemd 日志中观察到它。

创建 shell 程序 /usr/local/bin/hello.sh 并添加以下内容。您需要确保结果在启动期间可见,并且在查看 systemd 日志时可以轻松找到它。您将使用带有条形框的“Hello world”程序版本,以便它突出显示。确保该文件是可执行的,并且具有 root 用户和组所有权,并使用 700 权限 以确保安全

#!/usr/bin/bash
# Simple program to use for testing startup configurations
# with systemd.
# By David Both
# Licensed under GPL V2
#
echo "###############################"
echo "######### Hello World! ########"
echo "###############################"

从命令行运行此程序以验证它是否正常工作

[root@testvm1 ~]# hello.sh 
###############################
######### Hello World! ########
###############################
[root@testvm1 ~]#

此程序可以使用任何脚本或编译语言创建。hello.sh 程序也可以位于基于 Linux 文件系统层次结构 (FHS) 的其他位置。我将其放置在 /usr/local/bin 目录中,以便可以从命令行轻松运行它,而无需在键入命令时预先添加路径。我发现我创建的许多 shell 程序都需要从命令行以及 systemd 等其他工具运行。

服务单元文件

使用以下内容创建服务单元文件 /etc/systemd/system/hello.service。此文件不需要是可执行文件,但为了安全起见,它需要 root 用户和组所有权以及 644640 权限

# Simple service unit file to use for testing 
# startup configurations with systemd.
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=My hello shell script

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hello.sh 

[Install]
WantedBy=multi-user.target

通过查看服务状态来验证服务单元文件是否按预期执行。任何语法错误都会在此处显示

[root@testvm1 ~]# systemctl status hello.service 
● hello.service - My hello shell script
     Loaded: loaded (/etc/systemd/system/hello.service; disabled; vendor preset: disabled)
     Active: inactive (dead)
[root@testvm1 ~]#

您可以多次运行此“oneshot”服务类型而不会出现问题。oneshot 类型适用于服务单元文件启动的程序是主进程,并且必须在 systemd 启动任何依赖进程之前完成的服务。

共有七种服务类型,您可以在 systemd.service(5) 手册页中找到每种类型的说明(以及服务单元文件的其他部分)。(您还可以在本文末尾的资源中找到更多信息。)

出于好奇,我想看看错误可能是什么样子。因此,我从 Type=oneshot 行中删除了“o”,使其看起来像 Type=neshot,然后再次运行命令

[root@testvm1 ~]# systemctl status hello.service
● hello.service - My hello shell script
     Loaded: loaded (/etc/systemd/system/hello.service; disabled; vendor preset: disabled)
     Active: inactive (dead)

May 06 08:50:09 testvm1.both.org systemd[1]: /etc/systemd/system/hello.service:12: Failed to parse service type, ignoring: neshot
[root@testvm1 ~]#

这些结果准确地告诉我错误在哪里,并使解决问题变得非常容易。

请注意,即使在您将 hello.service 文件恢复到原始形式后,错误仍然会存在。虽然重新启动将清除错误,但您不应该这样做,因此我开始寻找一种清除此类持久性错误的方法。我遇到过需要命令 systemctl daemon-reload 来重置错误情况的服务错误,但这在这种情况下不起作用。可以使用此命令修复的错误消息似乎总是包含对此效果的声明,因此您知道要运行它。

但是,建议您在更改单元文件或创建新单元文件后运行 systemctl daemon-reload。这会通知 systemd 已进行更改,并且可以防止管理已更改的服务或单元时出现某些类型的问题。继续并运行此命令。

在更正服务单元文件中的拼写错误后,简单的 systemctl restart hello.service 清除了错误。通过在 hello.service 文件中引入其他一些错误来做一些实验,看看您会得到什么样的结果。

启动服务

现在您已准备好启动新服务并检查状态以查看结果。虽然您可能在上一节中进行了重启,但您可以根据需要多次启动或重启 oneshot 服务,因为它运行一次然后退出。

继续启动服务(如下所示),然后检查状态。根据您在错误方面进行的实验量,您的结果可能与我的不同

[root@testvm1 ~]# systemctl start hello.service 
[root@testvm1 ~]# systemctl status hello.service 
● hello.service - My hello shell script
     Loaded: loaded (/etc/systemd/system/hello.service; disabled; vendor preset: disabled)
     Active: inactive (dead)

May 10 10:37:49 testvm1.both.org hello.sh[842]: ######### Hello World! ########
May 10 10:37:49 testvm1.both.org hello.sh[842]: ###############################
May 10 10:37:49 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:37:49 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:54:45 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ######### Hello World! ########
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:54:45 testvm1.both.org systemd[1]: Finished My hello shell script.
[root@testvm1 ~]#

请注意,在状态命令的输出中,systemd 消息指示 hello.sh 脚本已启动,服务已完成。您还可以看到脚本的输出。此显示是从服务最近调用的日志条目生成的。尝试多次启动服务,然后再次运行 status 命令,看看我的意思。

您还应该直接查看日志内容;有多种方法可以做到这一点。一种方法是指定记录类型标识符,在本例中,是 shell 脚本的名称。这显示了以前重启以及当前会话的日志条目。正如您所看到的,我已经为这篇文章研究和测试了一段时间了

[root@testvm1 ~]# journalctl -t hello.sh
<snip>
-- Reboot --
May 08 15:55:47 testvm1.both.org hello.sh[840]: ###############################
May 08 15:55:47 testvm1.both.org hello.sh[840]: ######### Hello World! ########
May 08 15:55:47 testvm1.both.org hello.sh[840]: ###############################
-- Reboot --
May 08 16:01:51 testvm1.both.org hello.sh[840]: ###############################
May 08 16:01:51 testvm1.both.org hello.sh[840]: ######### Hello World! ########
May 08 16:01:51 testvm1.both.org hello.sh[840]: ###############################
-- Reboot --
May 10 10:37:49 testvm1.both.org hello.sh[842]: ###############################
May 10 10:37:49 testvm1.both.org hello.sh[842]: ######### Hello World! ########
May 10 10:37:49 testvm1.both.org hello.sh[842]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ######### Hello World! ########
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
[root@testvm1 ~]#

要查找 hello.service 单元的 systemd 记录,您可以搜索 systemd。您可以使用 G+Enter 翻页到日志条目的末尾,然后向后滚动以查找您感兴趣的条目。使用 -b 选项仅显示最近启动的条目

[root@testvm1 ~]# journalctl -b -t systemd
<snip>
May 10 10:37:49 testvm1.both.org systemd[1]: Starting SYSV: Late init script for live image....
May 10 10:37:49 testvm1.both.org systemd[1]: Started SYSV: Late init script for live image..
May 10 10:37:49 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:37:49 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:37:50 testvm1.both.org systemd[1]: Starting D-Bus System Message Bus...
May 10 10:37:50 testvm1.both.org systemd[1]: Started D-Bus System Message Bus.

我复制了一些其他日志条目,以便您了解您可能会发现什么。此命令喷射出与 systemd 相关的所有日志行——当我写这篇文章时,有 109,183 行。这有很多数据需要整理。您可以使用寻呼机的搜索功能,通常是 less,或者您可以使用内置的 grep 功能。-g(或 --grep=)选项使用 Perl 兼容的正则表达式

[root@testvm1 ~]# journalctl -b -t systemd -g "hello"
[root@testvm1 ~]# journalctl -b -t systemd -g "hello"
-- Logs begin at Tue 2020-05-05 18:11:49 EDT, end at Sun 2020-05-10 11:01:01 EDT. --
May 10 10:37:49 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:37:49 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:37:49 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:54:45 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:54:45 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:54:45 testvm1.both.org systemd[1]: Finished My hello shell script.
[root@testvm1 ~]#

您可以使用标准的 GNU grep 命令,但这不会在第一行显示日志元数据。

如果您不想只看到与您的 hello 服务相关的日志条目,您可以通过指定时间范围来缩小范围。例如,我将从我的测试 VM 上的 10:54:00 开始时间开始,这是上面条目所在分钟的开始时间。 请注意,--since= 选项必须用引号括起来,并且此选项也可以表示为 -S "<time specification>"

日期和时间在您的主机上会有所不同,因此请务必使用与日志中的时间戳匹配的时间戳

[root@testvm1 ~]# journalctl --since="2020-05-10 10:54:00"
May 10 10:54:35 testvm1.both.org audit: BPF prog-id=54 op=LOAD
May 10 10:54:35 testvm1.both.org audit: BPF prog-id=55 op=LOAD
May 10 10:54:45 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ######### Hello World! ########
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:54:45 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:54:45 testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd"'
May 10 10:54:45 testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/"'
May 10 10:56:00 testvm1.both.org NetworkManager[840]: <error> [1589122560.0633] dhcp4 (enp0s3): error -113 dispatching events
May 10 10:56:00 testvm1.both.org NetworkManager[840]: <info>  [1589122560.0634] dhcp4 (enp0s3): state changed bound -> fail
<snip>

since 规范跳过该时间之前的所有条目,但之后仍然有很多您不需要的条目。您还可以使用 until 选项来修剪掉在您感兴趣的时间之后一点出现的条目。我想要事件发生时的整整一分钟,仅此而已

[root@testvm1 ~]# journalctl --since="2020-05-10 10:54:35" --until="2020-05-10 10:55:00"
-- Logs begin at Tue 2020-05-05 18:11:49 EDT, end at Sun 2020-05-10 11:04:59 EDT. --
May 10 10:54:35 testvm1.both.org systemd[1]: Reloading.
May 10 10:54:35 testvm1.both.org audit: BPF prog-id=27 op=UNLOAD
May 10 10:54:35 testvm1.both.org audit: BPF prog-id=26 op=UNLOAD
<snip>
ay 10 10:54:35 testvm1.both.org audit: BPF prog-id=55 op=LOAD
May 10 10:54:45 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ######### Hello World! ########
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:54:45 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:54:45 testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd>
May 10 10:54:45 testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/>
lines 1-46/46 (END)

如果在此时间段内有很多活动,您可以结合使用这些选项来进一步缩小结果数据流

[root@testvm1 ~]# journalctl --since="2020-05-10 10:54:35" --until="2020-05-10 10:55:00" -t "hello.sh"
-- Logs begin at Tue 2020-05-05 18:11:49 EDT, end at Sun 2020-05-10 11:10:41 EDT. --
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ######### Hello World! ########
May 10 10:54:45 testvm1.both.org hello.sh[1380]: ###############################
[root@testvm1 ~]#

您的结果应该与我的类似。您可以从这一系列实验中看到,该服务已正确执行。

重启——终于

到目前为止,您还没有重启安装服务的主机。所以现在就重启吧,因为毕竟,这个操作指南是关于在启动时运行程序的。首先,您需要启用该服务以便在启动序列期间启动

[root@testvm1 ~]# systemctl enable hello.service 
Created symlink /etc/systemd/system/multi-user.target.wants/hello.service → /etc/systemd/system/hello.service.
[root@testvm1 ~]#

请注意,链接是在 /etc/systemd/system/multi-user.target.wants 目录中创建的。这是因为服务单元文件指定该服务是 multi-user.target “想要的”。

重启,并务必在启动序列期间观看数据流,以查看“Hello world”消息。等等……你没看到吗?好吧,我也没看到。虽然它闪得很快,但我确实看到了 systemd 的消息,表明它正在启动 hello.service

查看自上次系统启动以来的日志。您可以使用 less 寻呼机的搜索工具来查找“Hello”或“hello”。我删除了许多数据行,但我留下了一些周围的日志条目,以便您可以了解与您的服务相关的条目在本地的样子

[root@testvm1 ~]# journalctl -b
<snip>
May 10 10:37:49 testvm1.both.org systemd[1]: Listening on SSSD Kerberos Cache Manager responder socket.
May 10 10:37:49 testvm1.both.org systemd[1]: Reached target Sockets.
May 10 10:37:49 testvm1.both.org systemd[1]: Reached target Basic System.
May 10 10:37:49 testvm1.both.org systemd[1]: Starting Modem Manager...
May 10 10:37:49 testvm1.both.org systemd[1]: Starting Network Manager...
May 10 10:37:49 testvm1.both.org systemd[1]: Starting Avahi mDNS/DNS-SD Stack...
May 10 10:37:49 testvm1.both.org systemd[1]: Condition check resulted in Secure Boot DBX (blacklist) updater being skipped.
May 10 10:37:49 testvm1.both.org systemd[1]: Starting My hello shell script...
May 10 10:37:49 testvm1.both.org systemd[1]: Starting IPv4 firewall with iptables...
May 10 10:37:49 testvm1.both.org systemd[1]: Started irqbalance daemon.
May 10 10:37:49 testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=irqbalance comm="systemd" exe="/usr/lib/sy>"'
May 10 10:37:49 testvm1.both.org systemd[1]: Starting LSB: Init script for live image....
May 10 10:37:49 testvm1.both.org systemd[1]: Starting Hardware Monitoring Sensors...
<snip>
May 10 10:37:49 testvm1.both.org systemd[1]: Starting NTP client/server...
May 10 10:37:49 testvm1.both.org systemd[1]: Starting SYSV: Late init script for live image....
May 10 10:37:49 testvm1.both.org systemd[1]: Started SYSV: Late init script for live image..
May 10 10:37:49 testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=livesys-late comm="systemd" exe="/usr/lib/>"'
May 10 10:37:49 testvm1.both.org hello.sh[842]: ###############################
May 10 10:37:49 testvm1.both.org hello.sh[842]: ######### Hello World! ########
May 10 10:37:49 testvm1.both.org hello.sh[842]: ###############################
May 10 10:37:49 testvm1.both.org systemd[1]: hello.service: Succeeded.
May 10 10:37:49 testvm1.both.org systemd[1]: Finished My hello shell script.
May 10 10:37:49 testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd>"'
May 10 10:37:49 testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/>
May 10 10:37:50 testvm1.both.org audit: BPF prog-id=28 op=LOAD
<snip>

您可以看到 systemd 启动了 hello.service 单元,该单元运行了 hello.sh shell 脚本,输出记录在日志中。如果您能够在启动期间捕获到它,您还会看到 systemd 消息,指示它正在启动脚本,以及另一条消息,指示服务成功。通过查看上面数据流中的第一个 systemd 消息,您可以看到 systemd 在达到基本系统目标后很快就启动了您的服务。

但我也想在启动时看到消息显示。有一种方法可以实现这一点:将以下行添加到 hello.service 文件的 [Service] 部分

StandardOutput=journal+console

现在 hello.service 文件看起来像这样

# Simple service unit file to use for testing 
# startup configurations with systemd.
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=My hello shell script

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hello.sh 
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target

添加此行后,重新启动系统,并观察数据流在启动过程中向上滚动显示。您应该会在小框中看到消息。启动序列完成后,您可以查看最近启动的日志,并找到新服务的条目。

更改顺序

现在您的服务正在工作,您可以查看它在启动序列中的启动位置,并尝试更改它。重要的是要记住,systemd 的目的是在每个主要目标(basic.targetmulti-user.targetgraphical.target)中并行启动尽可能多的服务和其他单元类型。您应该刚刚看到最近启动的日志条目,它应该与上面输出中的日志类似。

请注意,systemd 在达到目标基本系统后不久就启动了您的测试服务。这就是您在服务单元文件中 WantedBy 行中指定的,所以它是正确的。在您更改任何内容之前,列出 /etc/systemd/system/multi-user.target.wants 目录的内容,您将看到指向服务单元文件的符号(软)链接。服务单元文件的 [Install] 部分指定哪个目标将启动服务,运行 systemctl enable hello.service 命令将在相应的“目标想要”目录中创建链接

hello.service -> /etc/systemd/system/hello.service

某些服务需要在 basic.target 期间启动,而其他服务则不需要启动,除非系统正在启动 graphical.target。此实验中的服务不会在 basic.target 中启动——假设您不需要它在 graphical.target 之前启动。因此,请更改 WantedBy

WantedBy=graphical.target

务必禁用 hello.service 并重新启用它,以删除旧链接并在 graphical.targets.wants 目录中添加新链接。我注意到,如果我忘记在更改想要它的目标之前禁用服务,我可以运行 systemctl disable 命令,链接将从两个“目标想要”目录中删除。然后,我只需要重新启用服务并重新启动。

graphical.target 中启动服务的一个担忧是,如果主机启动到 multi-user.target,则此服务将不会自动启动。如果服务需要 GUI 桌面界面,这可能是您想要的,但也可能不是您想要的。

使用 -o short-monotonic 选项查看 graphical.targetmulti-user.target 的日志条目,该选项显示内核启动后的秒数,精度为微秒

[root@testvm1 ~]# journalctl -b -o short-monotonic

multi-user.target 的一些结果

[   17.264730] testvm1.both.org systemd[1]: Starting My hello shell script...
[   17.265561] testvm1.both.org systemd[1]: Starting IPv4 firewall with iptables...
<SNIP>
[   19.478468] testvm1.both.org systemd[1]: Starting LSB: Init script for live image....
[   19.507359] testvm1.both.org iptables.init[844]: iptables: Applying firewall rules: [  OK  ]
[   19.507835] testvm1.both.org hello.sh[843]: ###############################
[   19.507835] testvm1.both.org hello.sh[843]: ######### Hello World! ########
[   19.507835] testvm1.both.org hello.sh[843]: ###############################
<SNIP>
[   21.482481] testvm1.both.org systemd[1]: hello.service: Succeeded.
[   21.482550] testvm1.both.org smartd[856]: Opened configuration file /etc/smartmontools/smartd.conf
[   21.482605] testvm1.both.org systemd[1]: Finished My hello shell script.

graphical.target 的一些结果

[   19.436815] testvm1.both.org systemd[1]: Starting My hello shell script...
[   19.437070] testvm1.both.org systemd[1]: Starting IPv4 firewall with iptables...
<SNIP>
[   19.612614] testvm1.both.org hello.sh[841]: ###############################
[   19.612614] testvm1.both.org hello.sh[841]: ######### Hello World! ########
[   19.612614] testvm1.both.org hello.sh[841]: ###############################
[   19.629455] testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[   19.629569] testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[   19.629682] testvm1.both.org systemd[1]: hello.service: Succeeded.
[   19.629782] testvm1.both.org systemd[1]: Finished My hello shell script.

尽管单元文件中包含 graphical.target “想要”,但 hello.service 单元在启动后约 19.5 或 19.6 秒运行。但是 hello.servicemulti-user.target 中大约在 17.24 秒启动,在 graphical target 中大约在 19.43 秒启动。

这意味着什么?查看 /etc/systemd/system/default.target 链接。该文件的内容显示 systemd 首先启动默认目标 graphical.target,然后拉入 multi-user.target

[root@testvm1 system]# cat default.target
#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Graphical Interface
Documentation=man:systemd.special(7)
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target display-manager.service
AllowIsolate=yes
[root@testvm1 system]#

无论它是使用 graphical.target 还是 multi-user.target 启动服务,hello.service 单元都在启动后约 19.5 或 19.6 秒运行。基于此以及日志结果(尤其是使用单调输出的结果),您知道这两个目标都是并行启动的。再看一下日志输出中的一件事

[   28.397330] testvm1.both.org systemd[1]: Reached target Multi-User System.
[   28.397431] testvm1.both.org systemd[1]: Reached target Graphical Interface.

两个目标几乎同时完成。这是一致的,因为 graphical.target 拉入 multi-user.target,并且在达到 multi.user target (即完成)之前无法完成。但是 hello.service 完成时间比这早得多。

所有这些意味着这两个目标几乎是并行启动的。如果您探索日志条目,您将看到来自每个主要目标的各种目标和服务,它们大部分是并行启动的。很明显,multi-user.target 不需要在 graphical.target 启动之前完成。因此,仅仅使用这些主要目标来对启动进行排序效果不是很好,尽管它对于确保仅在 graphical.target 需要单元时才启动单元可能很有用。

在继续之前,将 hello.service 单元文件恢复为 WantedBy=multi-user.target(如果尚未恢复)。

确保服务在网络运行后启动

一个常见的启动序列问题是确保单元在网络启动并运行后启动。Freedesktop.org 文章 网络启动后运行服务 说,对于何时将网络视为“启动”没有真正的共识。但是,该文章提供了三个选项,其中一个满足完全运行网络的需求的选项是 network-online.target。请注意,network.target 用于关闭而不是启动,因此当您尝试对启动进行排序时,它不会对您有任何好处。

在进行任何其他更改之前,请务必检查日志并验证 hello.service 单元是否在网络之前启动。您可以在日志中查找 network-online.target 进行检查。

您的服务实际上不需要网络服务,但您可以将其用作需要网络服务的服务的化身。

由于设置 WantedBy=graphical.target 不能确保服务将在网络启动并运行后启动,因此您需要另一种方法来确保它启动。幸运的是,有一种简单的方法可以做到这一点。将以下两行添加到 hello.service 单元文件的 [Unit] 部分

After=network-online.target                                                                             
Wants=network-online.target

要使此操作生效,这两个条目都是必需的。重新启动主机并在日志中查找服务条目的位置

[   26.083121] testvm1.both.org NetworkManager[842]: <info>  [1589227764.0293] device (enp0s3): Activation: successful, device activated.
[   26.083349] testvm1.both.org NetworkManager[842]: <info>  [1589227764.0301] manager: NetworkManager state is now CONNECTED_GLOBAL
[   26.085818] testvm1.both.org NetworkManager[842]: <info>  [1589227764.0331] manager: startup complete
[   26.089911] testvm1.both.org systemd[1]: Finished Network Manager Wait Online.
[   26.090254] testvm1.both.org systemd[1]: Reached target Network is Online.
[   26.090399] testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=NetworkManager-wait-online comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? termina>"'
[   26.091991] testvm1.both.org systemd[1]: Starting My hello shell script...
[   26.095864] testvm1.both.org sssd[be[implicit_files]][1007]: Starting up
[   26.290539] testvm1.both.org systemd[1]: Condition check resulted in Login and scanning of iSCSI devices being skipped.
[   26.291075] testvm1.both.org systemd[1]: Reached target Remote File Systems (Pre).
[   26.291154] testvm1.both.org systemd[1]: Reached target Remote File Systems.
[   26.292671] testvm1.both.org systemd[1]: Starting Notify NFS peers of a restart...
[   26.294897] testvm1.both.org systemd[1]: iscsi.service: Unit cannot be reloaded because it is inactive.
[   26.304682] testvm1.both.org hello.sh[1010]: ###############################
[   26.304682] testvm1.both.org hello.sh[1010]: ######### Hello World! ########
[   26.304682] testvm1.both.org hello.sh[1010]: ###############################
[   26.306569] testvm1.both.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[   26.306669] testvm1.both.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=hello comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[   26.306772] testvm1.both.org systemd[1]: hello.service: Succeeded.
[   26.306862] testvm1.both.org systemd[1]: Finished My hello shell script.
[   26.584966] testvm1.both.org sm-notify[1011]: Version 2.4.3 starting

这证实了 hello.service 单元在 network-online.target 之后启动。这正是您想要的。您可能还在启动过程中看到了“Hello World”消息。另请注意,时间戳比以前的启动时间晚了大约六秒。

定义启动顺序的最佳方法

本文更详细地探讨了使用 systemd 和单元文件以及日志的 Linux 启动,并发现了在服务文件中引入错误时会发生什么。作为系统管理员,我发现这种类型的实验有助于我了解程序或服务在中断时的行为,而有意破坏事物是在安全环境中学习的好方法。

正如本文中的实验所证明的那样,仅仅将服务单元添加到 multi-user.targetgraphical.target 都不能定义其在启动顺序中的位置。它仅仅确定单元是否作为图形环境的一部分启动。实际情况是,启动目标 multi-user.targetgraphical.target——以及它们的所有 Wants 和 Requires——几乎是并行启动的。确保单元以特定顺序启动的最佳方法是确定它所依赖的单元,并将新单元配置为“想要”和“在...之后”它所依赖的单元。

资源

互联网上有大量关于 systemd 的信息,但其中许多信息简洁、晦涩甚至具有误导性。除了本文中提到的资源外,以下网页还提供了有关 systemd 启动的更详细和可靠的信息。

  • Fedora 项目有一个很好的、实用的 systemd 指南。它几乎包含了您需要知道的一切,以便使用 systemd 配置、管理和维护 Fedora 计算机。
  • Fedora 项目还有一个很好的速查表,其中交叉引用了旧的 SystemV 命令和可比较的 systemd 命令。
  • 有关 systemd 的详细技术信息以及创建 systemd 的原因,请查看 Freedesktop.orgsystemd 描述
  • Linux.com 的“更多 systemd 乐趣”提供了更高级的 systemd 信息和技巧

还有 Lennart Poettering(systemd 的设计者和主要开发者)为 Linux 系统管理员编写的一系列深入的技术文章。这些文章写于 2010 年 4 月至 2011 年 9 月之间,但它们现在和当时一样具有现实意义。关于 systemd 及其生态系统的大部分其他优秀著作都基于这些论文。

接下来阅读什么

学习喜爱 systemd

systemd 是所有进程之母,负责将 Linux 主机启动到可以进行高效工作的状态。

(特约撰稿人)
2020 年 4 月 16 日
David Both
David Both 是一位开源软件和 GNU/Linux 倡导者、培训师、作家和演讲者。自 1996 年以来,他一直从事 Linux 和开源软件方面的工作,自 1969 年以来一直从事计算机方面的工作。他是“系统管理员 Linux 哲学”的坚定拥护者和传播者。

3 条评论

journalctl 的 -u 选项也可用于将日志条目缩小到单个 systemd 单元:journalctl -u hello.service

是的,可以。谢谢。

我将在以后的文章中更深入地介绍 journalctl,但在本文中,我也确实希望与 hello.service 相关的日志条目的上下文也可见。

回复 ,作者:ersen

我正在使用 Fedora Project,我非常满意

© . All rights reserved.