使用 systemd 来管理启动项
systemd 启动

> 
> 了解 systemd 是怎样决定服务启动顺序，即使它本质上是个并行系统。
> 
> 
> 


![](/data/attachment/album/202105/18/105928u3r3593k3z38ly5k.jpg "Penguin with green background")


最近在设置 Linux 系统时，我想知道如何确保服务和其他单元的依赖关系在这些依赖于它们的服务和单元启动之前就已经启动并运行了。我需要更多 systemd 如何管理启动程序的相关知识，特别是在本质上是一个并行的系统中如何是决定服务启动顺序的。


你可能知道 SystemV（systemd 的前身，我在这个系列的 [第一篇文章](/article-12214-1.html) 中解释过）通过 Sxx 前缀命名启动脚本来决定启动顺序，xx 是一个 00-99 的数字。然后 SystemV 利用文件名来排序，然后按照所需的运行级别执行队列中每个启动脚本。


但是 systemd 使用单元文件来定义子程序，单元文件可由系统管理员创建或编辑，这些文件不仅可以用于初始化时也可以用于常规操作。在这个系列的 [第三篇文章](https://opensource.com/article/20/5/systemd-units) 中，我解释了如何创建一个挂载单元文件。在第五篇文章中，我解释了如何创建一种不同的单元文件 —— 在启动时执行一个程序的服务单元文件。你也可以修改单元文件中某些配置，然后通过 systemd 日志去查看你的修改在启动序列中的位置。


### 准备工作


先确认你已经在 `/etc/default/grub` 文件中的 `GRUB_CMDLINE_LINUX=` 这行移除了 `rhgb` 和 `quiet`，如同我在这个系列的 [第二篇文章](https://opensource.com/article/20/5/systemd-startup) 中展示的那样。这让你能够查看 Linux 启动信息流，你在这篇文章中部分实验中需要用到。


### 程序


在本教程中，你会创建一个简单的程序让你能够在主控台和后续的 systemd 日志中查看启动时的信息。


创建一个 shell 程序 `/usr/local/bin/hello.sh` 然后添加下述内容。你要确保执行结果在启动时是可见的，可以轻松的在 systemd 日志中找到它。你会使用一版携带一些方格的 “Hello world” 程序，这样它会非常显眼。为了确保这个文件是可执行的，且为了安全起见，它需要 root 的用户和组所有权和 [700 权限](https://chmodcommand.com/chmod-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 文件系统层级结构](https://opensource.com/life/16/10/introduction-linux-filesystems)（FHS）上的任意位置。我把它放在 `/usr/local/bin` 目录下，这样它可以直接在命令行中执行而不必在打命令的时候前面带上路径。我发现我创建的很多 shell 程序需要从命令行和其他工具（如 systemd）运行。


### 服务单元文件


创建服务单元文件 `/etc/systemd/system/hello.service`，写入下述内容。这个文件不一定是要可执行的，但是为了安全起见，它需要 root 的用户和组所有权和 [644](https://chmodcommand.com/chmod-644/) 或 [640](https://chmodcommand.com/chmod-640/) 权限。



```
# 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”（单发）类型的服务多次而不会有问题。此类服务适用于服务单元文件启动的程序是主进程，必须在 systemd 启动任何依赖进程之前完成的服务。


共有 7 种服务类型，你可以在 [systemd.service(5)](http://man7.org/linux/man-pages/man5/systemd.service.5.html) 的手册页上找到每一种（以及服务单元文件的其他部分）的详细解释。（你也可以在文章末尾的 [资料](file:///Users/xingyuwang/develop/TranslateProject-wxy/translated/tech/tmp.bYMHU00BHs#resources) 中找到更多信息。）


出于好奇，我想看看错误是什么样子的。所以我从 `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` 文件来看看会得到怎样的结果。


### 启动服务


现在你已经准备好启动这个新服务，通过检查状态来查看结果。尽管你可能之前已经重启过，你仍然可以启动或重启这个单发服务任意次，因为它只运行一次就退出了。


继续启动这个服务（如下所示），然后检查状态。你的结果可能和我的有区别，取决于你做了多少试错实验。



```
[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` 启动然后服务结束了。你也可以看到脚本的输出。该输出是根据服务的最近调用的日志记录生成的，试试看多启动几次这个服务，然后再看状态命令的输出就能理解我所说的。


你也应该直接查看日志内容，有很多种方法可以实现。一种办法是指定记录类型标识符，在这个例子中就是 shell 脚本的名字。它会展示前几次重启和当前会话的日志记录。如你所见，我已经为这篇文章做了挺长一段时间的研究测试了。



```
[root@testvm1 ~]# journalctl -t hello.sh
<剪去>
-- 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
<剪去>
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 的日志内容 —— 当我写这篇时是 109183 行。这是一个需要整理的大量数据。你可以使用页面的搜索功能，通常是 `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` 服务的日志记录，你可以指定时间来缩小范围。举个例子，我将在我的测试虚拟机上以 `10:54:00` 为开始时间，这是上述的日志记录开始的分钟数。注意 `--since=` 的选项必须加引号，这个选项也可以写成 `-S "某个时间"`。


日期和时间可能在你的机器上有所不同，所以确保使用能匹配你日志中的时间的时间戳。



```
[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
<剪去>

```

`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
<剪去>
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
<剪去>
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...
<剪去>
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
<剪去>

```

你可以看到 systemd 启动了 `hello.service` 单元，它执行了 `hello.sh` 脚本并将输出记录在日志中。如果你能在启动阶段抓到它，你也应该能看见，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.target`、`multi-user.target` 和 `graphical.**target`）中并行启动尽可能多的服务和其他的单元类型。你应该刚刚看过最近一次开机的日志记录，它应该和上面我的日志看上去类似。


注意，systemd 在它到达到基本系统目标（`basic.target`）后不久就启动了你的测试服务。这正是你在在服务单元文件的 `WantedBy` 行中指定的，所以它是对的。在你做出修改之前，列出 `/etc/systemd/system/multi-user.target.wants` 目录下的内容，你会看到一个指向服务单元文件的软链接。服务单元文件的 `[Install]` 段指定了哪一个目标会启动这个服务，执行 `systemctl enable hello.service` 命令会在适当的 `targets.wants` 路径下创建软链接。



```
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` 命令，链接将从两个 `targets.wants` 目录中删除。之后我只需要重新启用这个服务然后重启电脑。


启动 `graphical.target` 下的服务有个需要注意的地方，如果电脑启动到 `multi-user.target` 阶段，这个服务不会自动启动。如果这个服务需要 GUI 桌面接口，这或许是你想要的，但是它同样可能不是你想要的。


用 `-o short-monotonic` 选项来查看 `graphical.target` 和 `multi-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...
<剪去>
[   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]: ###############################
<剪去>
[   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...
<剪去>
[   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.

```

尽管单元文件的 `WantedBy` 部分包含了 `graphical.target`，`hello.service` 单元在启动后大约 19.5 或 19.6 秒后运行。但是 `hello.service` 在 `multi-user.target` 中开始于 17.24 秒，在 `graphical target` 中开始于 19.43 秒。


这意味着什么呢？看看 `/etc/systemd/system/default.target` 这个链接。文件内容显示 systemd 先启动了默认目标 `graphical.target`，然后 `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](http://Freedesktop.org) 的文章《[在网络启动后运行服务](https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/)》中提到，目前没有一个真正的关于网络何时算作“启动”的共识。然而，这篇文章提供了三个选项，满足完全可用网络需求的是 `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” 消息在启动阶段出现。还需要注意的是，在启动时记录出现的时间戳比之前要晚了大约 6 秒。


### 定义启动序列的最好方法


本文章详细地探讨了 Linux 启动时 systemd 和单元文件以及日志的细节，并且发现了当错误被引入单元文件时候会发生什么。作为系统管理员，我发现这类实验有助于我理解程序或者服务出故障时的行为，并且在安全环境中有意破坏是一种学习的好方法。


文章中实验结果证明，仅将服务单元添加至 `multi-user.target` 或者 `graphical.target` 并不能确定它在启动序列中的位置。它仅仅决定了一个单元是否作为图形环境一部分启动。事实上，启动目标 `multi-user.target` 和 `graphical.target` 和所有它们的 `Wants` 以及 `Required` 几乎是并行启动的。确保单元在特定位置启动的最好方法是确定它所依赖的单元，并将新单元配置成 `Want` 和 `After` 它的依赖。


### 资源


网上有大量的关于 systemd 的参考资料，但是大部分都有点简略、晦涩甚至有误导性。除了本文中提到的资料，下列的网页提供了跟多可靠且详细的 systemd 入门信息。


Fedora 项目有一篇切实好用的 systemd 入门，它囊括了几乎所有你需要知道的关于如何使用 systemd 配置、管理和维护 Fedora 计算机的信息。


Fedora 项目也有一个不错的 备忘录，交叉引用了过去 SystemV 命令和 systemd 命令做对比。


关于 systemd 的技术细节和创建这个项目的原因，请查看 [Freedesktop.org](http://Freedesktop.org) 上的 systemd 描述。


[Linux.com](http://Linux.com) 的“更多 systemd 的乐趣”栏目提供了更多高级的 systemd 信息和技巧。


此外，还有一系列深度的技术文章，是由 systemd 的设计者和主要开发者 Lennart Poettering 为 Linux 系统管理员撰写的。这些文章写于 2010 年 4 月至 2011 年 9 月间，但它们现在和当时一样具有现实意义。关于 systemd 及其生态的许多其他好文章都是基于这些文章：


* [Rethinking PID 1](http://0pointer.de/blog/projects/systemd.html)
* [systemd for Administrators，Part I](http://0pointer.de/blog/projects/systemd-for-admins-1.html)
* [systemd for Administrators，Part II](http://0pointer.de/blog/projects/systemd-for-admins-2.html)
* [systemd for Administrators，Part III](http://0pointer.de/blog/projects/systemd-for-admins-3.html)
* [systemd for Administrators，Part IV](http://0pointer.de/blog/projects/systemd-for-admins-4.html)
* [systemd for Administrators，Part V](http://0pointer.de/blog/projects/three-levels-of-off.html)
* [systemd for Administrators，Part VI](http://0pointer.de/blog/projects/changing-roots)
* [systemd for Administrators，Part VII](http://0pointer.de/blog/projects/blame-game.html)
* [systemd for Administrators，Part VIII](http://0pointer.de/blog/projects/the-new-configuration-files.html)
* [systemd for Administrators，Part IX](http://0pointer.de/blog/projects/on-etc-sysinit.html)
* [systemd for Administrators，Part X](http://0pointer.de/blog/projects/instances.html)
* [systemd for Administrators，Part XI](http://0pointer.de/blog/projects/inetd.html)




---


via: <https://opensource.com/article/20/5/manage-startup-systemd>


作者：[David Both](https://opensource.com/users/dboth) 选题：[lujun9972](https://github.com/lujun9972) 译者：[tt67wq](https://github.com/tt67wq) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
While setting up a Linux system recently, I wanted to know how to ensure that dependencies for services and other units were up and running before those dependent services and units start. Specifically, I needed more knowledge of how systemd manages the startup sequence, especially in determining the order services are started in what is essentially a parallel system.

You may know that SystemV (systemd's predecessor, as I explained in the [first article](https://opensource.com/article/20/4/systemd) in this series) orders the startup sequence by naming the startup scripts with an SXX prefix, where XX is a number from 00 to 99. SystemV then uses the sort order by name and runs each start script in sequence for the desired runlevel.

But systemd uses unit files, which can be created or modified by a sysadmin, to define subroutines for not only initialization but also for regular operation. In the [third article](https://opensource.com/article/20/5/systemd-units) in this series, I explained how to create a mount unit file. In this fifth article, I demonstrate how to create a different type of unit file—a service unit file that runs a program at startup. You can also change certain configuration settings in the unit file and use the systemd journal to view the location of your changes in the startup sequence.

## Preparation

Make sure you have removed `rhgb`

and `quiet`

from the `GRUB_CMDLINE_LINUX=`

line in the `/etc/default/grub`

file, as I showed in the [second article](https://opensource.com/article/20/5/systemd-startup) in this series. This enables you to observe the Linux startup message stream, which you'll need for some of the experiments in this article.

## The program

In this tutorial, you will create a simple program that enables you to observe a message during startup on the console and later in the systemd journal.

Create the shell program `/usr/local/bin/hello.sh`

and add the following content. You want to ensure that the result is visible during startup and that you can easily find it when looking through the systemd journal. You will use a version of the "Hello world" program with some bars around it, so it stands out. Make sure the file is executable and has user and group ownership by root with [700 permissions](https://chmodcommand.com/chmod-700/) for security:

```
#!/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 "###############################"
```


Run this program from the command line to verify that it works correctly:

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


This program could be created in any scripting or compiled language. The `hello.sh`

program could also be located in other places based on the [Linux filesystem hierarchical structure](https://opensource.com/life/16/10/introduction-linux-filesystems) (FHS). I place it in the `/usr/local/bin`

directory so that it can be easily run from the command line without having to prepend a path when I type the command. I find that many of the shell programs I create need to be run from the command line and by other tools such as systemd.

## The service unit file

Create the service unit file `/etc/systemd/system/hello.service`

with the following content. This file does not need to be executable, but for security, it does need user and group ownership by root and [644](https://chmodcommand.com/chmod-644/) or [640](https://chmodcommand.com/chmod-640/) permissions:

```
# 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
```


Verify that the service unit file performs as expected by viewing the service status. Any syntactical errors will show up here:

```
[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 ~]#
```


You can run this "oneshot" service type multiple times without problems. The oneshot type is intended for services where the program launched by the service unit file is the main process and must complete before systemd starts any dependent process.

There are seven service types, and you can find an explanation of each (along with the other parts of a service unit file) in the [systemd.service(5)](http://man7.org/linux/man-pages/man5/systemd.service.5.html) man page. (You can also find more information in the [resources](#resources) at the end of this article.)

As curious as I am, I wanted to see what an error might look like. So, I deleted the "o" from the `Type=oneshot`

line, so it looked like `Type=neshot`

, and ran the command again:

```
[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 ~]#
```


These results told me precisely where the error was and made it very easy to resolve the problem.


Just be aware that even after you restore the `hello.service`

file to its original form, the error will persist. Although a reboot will clear the error, you should not have to do that, so I went looking for a method to clear out persistent errors like this. I have encountered service errors that require the command `systemctl daemon-reload`

to reset an error condition, but that did not work in this case. The error messages that can be fixed with this command always seem to have a statement to that effect, so you know to run it.

It is, however, recommended that you run `systemctl daemon-reload`

after changing a unit file or creating a new one. This notifies systemd that the changes have been made, and it can prevent certain types of issues with managing altered services or units. Go ahead and run this command.

After correcting the misspelling in the service unit file, a simple `systemctl restart hello.service`

cleared the error. Experiment a bit by introducing some other errors into the `hello.service`

file to see what kinds of results you get.

## Start the service

Now you are ready to start the new service and check the status to see the result. Although you probably did a restart in the previous section, you can start or restart a oneshot service as many times as you want since it runs once and then exits.

Go ahead and start the service (as shown below), and then check the status. Depending upon how much you experimented with errors, your results may differ from mine:

```
[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 ~]#
```


Notice in the status command's output that the systemd messages indicate that the `hello.sh`

script started and the service completed. You can also see the output from the script. This display is generated from the journal entries of the most recent invocations of the service. Try starting the service several times, and then run the status command again to see what I mean.

You should also look at the journal contents directly; there are multiple ways to do this. One way is to specify the record type identifier, in this case, the name of the shell script. This shows the journal entries for previous reboots as well as the current session. As you can see, I have been researching and testing for this article for some time now:

```
[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 ~]#
```


To locate the systemd records for the `hello.service`

unit, you can search on systemd. You can use **G+Enter** to page to the end of the journal entries and then scroll back to locate the ones you are interested in. Use the `-b`

option to show only the entries for the most recent startup:

```
[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.
```


I copied a few other journal entries to give you an idea of what you might find. This command spews all of the journal lines pertaining to systemd—109,183 lines when I wrote this. That is a lot of data to sort through. You can use the pager's search facility, which is usually `less`

, or you can use the built-in `grep`

feature. The `-g`

(or `--grep=`

) option uses Perl-compatible regular expressions:

```
[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 ~]#
```


You could use the standard GNU `grep`

command, but that would not show the log metadata in the first line.

If you do not want to see just the journal entries pertaining to your `hello`

service, you can narrow things down a bit by specifying a time range. For example, I will start with the beginning time of `10:54:00`

on my test VM, which was the start of the minute the entries above are from.** **Note that the `--since=`

option must be enclosed in quotes and that this option can also be expressed as `-S "<time specification>"`

.

The date and time will be different on your host, so be sure to use the timestamps that match the times in your journals:

```
[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>
```


The `since`

specification skips all of the entries before that time, but there are still a lot of entries after that time that you do not need. You can also use the `until`

option to trim off the entries that come a bit after the time you are interested in. I want the entire minute when the event occurred and nothing more:

```
[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)
```


If there were a lot of activity in this time period, you could further narrow the resulting data stream using a combination of these options:

```
[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 ~]#
```


Your results should be similar to mine. You can see from this series of experiments that the service executed properly.

## Reboot—finally

So far, you have not rebooted the host where you installed your service. So do that now because, after all, this how-to is about running a program at startup. First, you need to enable the service to launch during the startup sequence:

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


Notice that the link was created in the `/etc/systemd/system/multi-user.target.wants`

directory. This is because the service unit file specifies that the service is "wanted" by the `multi-user.target`

.

Reboot, and be sure to watch the data stream during the startup sequence to see the "Hello world" message. Wait … you did not see it? Well, neither did I. Although it went by very fast, I did see systemd's message that it was starting the `hello.service`

.

Look at the journal since the latest system boot. You can use the `less`

pager's search tool to find "Hello" or "hello." I pruned many lines of data, but I left some of the surrounding journal entries, so you can get a feel for what the entries pertaining to your service look like locally:

```
[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>
```


You can see that systemd started the `hello.service`

unit, which ran the `hello.sh`

shell script with the output recorded in the journal. If you were able to catch it during boot, you would also have seen the systemd message indicating that it was starting the script and another message indicating that the service succeeded. By looking at the first systemd message in the data stream above, you can see that systemd started your service very soon after reaching the basic system target.

But I would like to see the message displayed at startup as well. There is a way to make that happen: Add the following line to the `[Service]`

section of the `hello.service`

file:

`StandardOutput=journal+console`


The `hello.service`

file now looks like this:

```
# 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
```


After adding this line, reboot the system, and watch the data stream as it scrolls up the display during the boot process. You should see the message in its little box. After the startup sequence completes, you can view the journal for the most recent boot and locate the entries for your new service.

## Changing the sequence

Now that your service is working, you can look at where it starts in the startup sequence and experiment with changing it. It is important to remember that systemd's intent is to start as many services and other unit types in parallel within each of the major targets: `basic.target`

, `multi-user.target`

, and `graphical.target`

. You should have just seen the journal entries for the most recent boot, which should look similar to my journal in the output above.

Notice that systemd started your test service soon after it reached the target basic system. This is what you specified in the service unit file in the `WantedBy`

line, so it is correct. Before you change anything, list the contents of the `/etc/systemd/system/multi-user.target.wants`

directory, and you will see a symbolic (soft) link to the service unit file. The `[Install]`

section of the service unit file specifies which target will start the service, and running the `systemctl enable hello.service`

command creates the link in the appropriate "target wants" directory:

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


Certain services need to start during the `basic.target`

, and others do not need to start unless the system is starting the `graphical.target`

. The service in this experiment will not start in the `basic.target`

—assume you do not need it to start until the `graphical.target`

. So change the `WantedBy`

line:

`WantedBy=graphical.target`


Be sure to disable the `hello.service`

and re-enable it to delete the old link and add the new one in the `graphical.targets.wants`

directory. I have noticed that if I forget to disable the service before changing the target that wants it, I can run the `systemctl disable`

command, and the links will be removed from both "target wants" directories. Then, I just need to re-enable the service and reboot.

One concern with starting services in the `graphical.target`

is that if the host boots to `multi-user.target`

, this service will not start automatically. That may be what you want if the service requires a GUI desktop interface, but it also may not be what you want.

Look at the journal entries for the `graphical.target`

and the `multi-user.target`

using the `-o short-monotonic`

option that displays seconds after kernel startup with microsecond precision:

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


Some results for `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.
```


And some results for `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.
```


Despite having the `graphical.target`

"want" in the unit file, the `hello.service`

unit runs about 19.5 or 19.6 seconds into startup. But `hello.service`

starts at about 17.24 seconds in the `multi-user.target`

and 19.43 seconds in the graphical target.

What does this mean? Look at the `/etc/systemd/system/default.target`

link. The contents of that file show that systemd first starts the default target, `graphical.target`

, which then pulls in the `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]#
```


Whether it starts the service with the `graphical.target`

or the `multi-user.target`

, the `hello.service`

unit runs at about 19.5 or 19.6 seconds into startup. Based on this and the journal results (especially the ones using the monotonic output), you know that both of these targets are starting in parallel. Look at one more thing from the journal output:

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


Both targets finish at almost the same time. This is consistent because the `graphical.target`

pulls in the `multi-user.target`

and cannot finish until the `multi.user target`

is reached, i.e., finished. But **hello.service** finishes much earlier than this.

What all this means is that these two targets start up pretty much in parallel. If you explore the journal entries, you will see various targets and services from each of those primary targets starting mostly in parallel. It is clear that the `multi-user.target`

does not need to complete before the `graphical.target`

starts. Therefore, simply using these primary targets to sequence the startup does not work very well, although it can be useful for ensuring that units are started only when they are needed for the `graphical.target`

.

Before continuing, revert the `hello.service`

unit file to `WantedBy=multi-user.target`

(if it is not already.)

## Ensure a service starts after the network is running

A common startup sequence issue is ensuring that a unit starts after the network is up and running. The Freedesktop.org article [ Running services after the network is up](https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/) says there is no real consensus on when a network is considered "up." However, the article provides three options, and the one that meets the needs of a fully operational network is

`network-online.target`

. Just be aware that `network.target`

is used during shutdown rather than startup, so it will not do you any good when you are trying to sequence the startup.Before making any other changes, be sure to examine the journal and verify that the `hello.service`

unit starts well before the network. You can look for the `network-online.target`

in the journal to check.

Your service does not really require the network service, but you can use it as an avatar for one that does.

Because setting `WantedBy=graphical.target`

does not ensure that the service will be started after the network is up and running, you need another way to ensure that it is. Fortunately, there is an easy way to do this. Add the following two lines to the `[Unit]`

section of the `hello.service`

unit file:

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


Both of these entries are required to make this work. Reboot the host and look for the location of entries for your service in the journals:

```
[ 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
```


This confirms that the `hello.service`

unit started after the `network-online.target`

. This is exactly what you want. You may also have seen the "Hello World" message as it passed by during startup. Notice also that the timestamp is about six seconds later in the startup than it was before.

## The best way to define the start sequence

This article explored Linux startup with systemd and unit files and journals in greater detail and discovered what happens when errors are introduced into the service file. As a sysadmin, I find that this type of experimentation helps me understand the behaviors of a program or service when it breaks, and breaking things intentionally is a good way to learn in a safe environment.

As the experiments in this article proved, just adding a service unit to either the `multi-user.target`

or the `graphical.target`

does not define its place in the start sequence. It merely determines whether a unit starts as part of a graphical environment or not. The reality is that the startup targets `multi-user.target`

and `graphical.target`

—and all of their Wants and Requires—start up pretty much in parallel. The best way to ensure that a unit starts in a specific order is to determine the unit it is dependent on and configure the new unit to "Want" and "After" the unit upon which it is dependent.

Resources

There is a great deal of information about systemd available on the internet, but much is terse, obtuse, or even misleading. In addition to the resources mentioned in this article, the following webpages offer more detailed and reliable information about systemd startup.

- The Fedora Project has a good, practical
[guide to systemd](https://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html). It has pretty much everything you need to know in order to configure, manage, and maintain a Fedora computer using systemd. - The Fedora Project also has a good
[cheat sheet](https://fedoraproject.org/wiki/SysVinit_to_Systemd_Cheatsheet)that cross-references the old SystemV commands to comparable systemd ones. - For detailed technical information about systemd and the reasons for creating it, check out
[Freedesktop.org](http://Freedesktop.org)'s[description of systemd](http://www.freedesktop.org/wiki/Software/systemd). [Linux.com](http://Linux.com)'s "More systemd fun" offers more advanced systemd[information and tips](https://www.linux.com/training-tutorials/more-systemd-fun-blame-game-and-stopping-services-prejudice/).

There is also a series of deeply technical articles for Linux sysadmins by Lennart Poettering, the designer and primary developer of systemd. These articles were written between April 2010 and September 2011, but they are just as relevant now as they were then. Much of everything else good that has been written about systemd and its ecosystem is based on these papers.

[Rethinking PID 1](http://0pointer.de/blog/projects/systemd.html)[systemd for Administrators, Part I](http://0pointer.de/blog/projects/systemd-for-admins-1.html)[systemd for Administrators, Part II](http://0pointer.de/blog/projects/systemd-for-admins-2.html)[systemd for Administrators, Part III](http://0pointer.de/blog/projects/systemd-for-admins-3.html)[systemd for Administrators, Part IV](http://0pointer.de/blog/projects/systemd-for-admins-4.html)[systemd for Administrators, Part V](http://0pointer.de/blog/projects/three-levels-of-off.html)[systemd for Administrators, Part VI](http://0pointer.de/blog/projects/changing-roots)[systemd for Administrators, Part VII](http://0pointer.de/blog/projects/blame-game.html)[systemd for Administrators, Part VIII](http://0pointer.de/blog/projects/the-new-configuration-files.html)[systemd for Administrators, Part IX](http://0pointer.de/blog/projects/on-etc-sysinit.html)[systemd for Administrators, Part X](http://0pointer.de/blog/projects/instances.html)[systemd for Administrators, Part XI](http://0pointer.de/blog/projects/inetd.html)

## 3 Comments