后台服务
# 介绍
我们日常中用到的最多的 Unit 就是服务了(.service)。所以学会再当今最流行的 Systemd 启动模式下的后台服务的编写是十分有必要的。
以 ssh-deamon 服务的配置文件为例
systemctl cat sshd
------------
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
包含 Unit、Service、Install 三个 Section。
# 自动依赖
# 隐式依赖
隐式添加了以下依赖项:
- 比如设置了
Type=dbus
的服务,会自动获取dbus.socket
的Requires=
和After=
作为依赖关系 。 - 套接字激活的服务在激活
.socket
单元后通过自动After=
依赖项自动排序。服务还通过自动和依赖关系拉入.socket
列出的所有单元。Sockets=``Wants=``After=
# 默认依赖
除非设置DefaultDependencies=no
,否则将添加以下依赖项:
- 服务单元被加上
sysinit.target
中Requires=
和After=
类型的依赖项、basic.target
中After=
的依赖,shutdown.target
中Conflicts=
和Before=
的依赖。这些确保正常的服务单元拉入基本的系统初始化,并在系统关闭之前干净地终止。只有涉及提前启动或延迟系统关闭的服务才应禁用此选项。 - 实例化的服务单元(即
@
名称中带有“”的服务单元)默认分配一个每个模板切片单元(参见 systemd.slice (5) (opens new window)),以模板单元命名,包含特定模板的所有实例。此切片通常与所有模板实例一起在关闭时停止。如果不需要,DefaultDependencies=no
请在模板单元中设置,并定义您自己的每个模板切片单元文件,该文件也设置DefaultDependencies=no
,或Slice=system.slice
在模板单元中设置(或另一个合适的切片)。
# 参数列表
列举一些常用的参数,全部参数可见:Service Options (opens new window)
# Type
配置此服务单元的进程启动类型。 simple、exec、forking、oneshot、dbus、notify 或 idle 之一:
# simple
默认的,systemctl start
启动一个 simple的都不会提示失败,即使无法成功调用服务的可执行文件(例如,因为选定的 User=
不存在,或者服务可执行文件不存在等)。
# exec
systemctl start
启动一个 exec 的能够提示失败,即使无法成功调用服务的可执行文件(例如因为所选内容不存在,或者服务可执行文件不不存在)。
# forking
主进程退出后,子进程会继续执行任务,并且状态会变为 running。
# oneshot
类似于 simple,主进程退出后就结束了,但是提供了RemainAfterExit=
选项来配置,告诉服务管理器是不是有子进程继续在干活。注意,如果不使用此选项RemainAfterExit=
,服务将永远不会进入“ active
”单元状态,而是直接从“ activating
”转换为“ deactivating
”或“ dead
”,因为没有配置应连续运行的进程。特别是这意味着在这种类型的服务运行之后(并且RemainAfterExit=
未设置)它不会在之后显示为已启动,而是显示为已死。
# dbus
类似于 simple,但是该服务会在 D-Bus 总线上获得一个名称,如 BusName=
. 在获得 D-Bus 总线名称后,systemd 将继续启动后续单元。
# notify
类似于 exec,启动完成后会发送一个消息sd-notify (opens new window)。systemd 收到进程发来的消息后,会继续启动后续的 Unit。如果使用此选项,则 NotifyAccess=
应设置为打开对 systemd 提供的通知套接字的访问。如果 NotifyAccess= 缺失或设置为 none,它将被强制设置为 main。
# idle
# ExitType
有 main 和 cgroup 两种,默认是 main。
- 如果设置为
main
,根据Type=
中的特性来决定是否为退出 。因此,它不能与Type=oneshot
. - 如果设置为
cgroup
,只要 cgroup 中至少有一个进程没有退出,服务就会被认为正在运行。
# PIDFile
指定 PID 文件的输出位置,服务关闭后自动删除,type = forking 的推荐使用这个。
# ExecStart
启动此服务时执行的带有参数的命令。
# ExecReload
要执行的命令以触发服务中的配置重新加载。这里可以使用 $MAINPID
获取进程ID。
ExecReload=kill -HUP $MAINPID
# RestartSec
配置重新启动服务之前的睡眠时间。采用以秒为单位的无单位值,或时间跨度值,例如“5min 20s”。默认为 100 毫秒。
# Environment
指定环境变量,注意启动的时候系统的环境变量都还是不可用的
Environment=ONE='one' "TWO='two two' too" THREE=
EnvironmentFile 指定环境变量文件路径,一行一个。key、value形式。
# 命令行
本节介绍 ExecStart=、ExecStartPre=、ExecStartPost=、ExecReload=、ExecStop= 和 ExecStopPost= 选项的命令行解析以及变量和说明符替换。
多行命令可以用 \
进行换行
Environment=ONE='one' "TWO='two two' too" THREE=
ExecStart=/bin/echo ${ONE} ${TWO} ${THREE}
ExecStart=/bin/echo $ONE $TWO $THREE
ExecStart=echo / >/dev/null & \; \
ls
2
3
4
5
第三个中 echo 被传了5个参数 "/
", ">/dev/null
", "&
", ";
", "ls
".
# 示例
# sample 服务
下面的单元文件创建了一个运行 /usr/sbin/foo-daemon
守护进程的服务。 未设置 Type=
等价于 Type=``simple
默认设置。 systemd 执行守护进程之后, 即认为该单元已经启动成功。
[Unit]
Description=Foo
[Service]
ExecStart=/usr/sbin/foo-daemon
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
# oneshot 服务
Type=``oneshot
用于那些只需要执行一次性动作而不需要持久运行的单元, 例如文件系统检查或者清理临时文件。 此类单元, 将会在启动后一直等待指定的动作完成, 然后再回到停止状态。 下面是一个执行清理动作的单元:
[Unit]
Description=Cleanup old Foo data
[Service]
Type=oneshot
ExecStart=/usr/sbin/foo-cleanup
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
# 可停止的oneshot 服务
有时候, 单元需要执行一个程序以完成某个设置(启动), 然后又需要再执行另一个程序以撤消先前的设置(停止), 而在设置持续有效的时段中,该单元应该视为处于"活动"(active)状态, 但实际上并无任何程序在持续运行。 网络配置服务就是一个典型的例子。 此外,只能启动一次(不可多次启动)的一次性服务, 也是一个例子。
可以通过设置 RemainAfterExit=yes
来满足这种需求。 在这种情况下,systemd 将会在启动成功后将该单元视为处于"活动"(active)状态(而不是"停止"(inactive)状态)。 RemainAfterExit=yes
虽然可以用于所有 Type=
类型, 但是在实践中主要用于 Type=oneshot
和 Type=simple
类型。 对于 Type=oneshot
类型, systemd 一直等到服务启动成功之后,才会将该服务置于"活动"(active)状态。 所以,依赖于该服务的其他单元必须等待该服务启动成功之后,才能启动。 但是对于 Type=simple
类型, 依赖于该服务的其他单元无需等待, 将会和该服务同时并行启动。 下面的类似展示了一个简单的静态防火墙服务:
[Unit]
Description=Simple firewall
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/simple-firewall-start
ExecStop=/usr/local/sbin/simple-firewall-stop
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
# 典型的 forking 服务
许多传统的守护进程/服务后台(即fork,daemonize)在启动时自己在服务的单元文件中设置 Type=forking 以支持这种操作模式。 systemd 将在原始程序仍在运行时认为服务处于初始化过程中。一旦它成功退出并且至少有一个进程保留(并且 RemainAfterExit=no),则认为该服务已启动。
[Unit]
Description=Some simple daemon
[Service]
Type=forking
ExecStart=/usr/sbin/my-simple-daemon -d
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
# DBug 服务
对于需要在 D-Bus 系统总线上注册一个名字的服务, 应该使用 Type=``dbus
并且设置相应的 BusName=
值。 该服务不可以派生任何子进程。 一旦从 D-Bus 系统总线成功获取所需的名字,该服务即被视为初始化成功。 下面是一个典型的 D-Bus 服务:
[Unit]
Description=Simple DBus service
[Service]
Type=dbus
BusName=org.example.simple-dbus-service
ExecStart=/usr/sbin/simple-dbus-service
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
# 能够通知初始化已完成的服务
Type=``simple
类型的服务 非常容易编写, 但是, 无法向 systemd 及时通知 "启动成功"的消息, 是一个重大缺陷。 Type=``notify
可以弥补该缺陷, 它支持将"启动成功"的消息及时通知给 systemd 。 下面是一个典型的例子:
[Unit]
Description=Simple notifying service
[Service]
Type=notify
ExecStart=/usr/sbin/simple-notifying-service
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9