Systemd 服务管理

2018-08-27 kernel software

Systemd 提供了很多系统服务的基本功能,包括了系统的系统进程、启动级别、cgroup 管理、进程管理等等。

这里简单介绍进程管理功能。

简介

这里整理一下常用的配置参数。

Restart=always                       # 只要不通过stop停止,任何情况下都必须重启服务
RestartSec=5                         # 重启间隔,异常后等待5秒再启动,默认为100ms
RestartPreventExitStatus=12 SIGKILL  # 当退出码为12或者受到KILL信号时不再自动重启

自动重启

常见的大概有如下的几种场景。

始终重启

大概每 3 秒重启一次,时间不会是严格的 3 秒,可能会略有出入。

Restart=always
RestartSec=3

如果设置为 on-failure ,那么当退出码为 0 时,将不会再重启,此时应该设置为 always 模式。

限制重启次数

主要是为了防止程序异常,导致进程一直重复被拉起,可以限制某段时间内的重启次数。

systemd 可以限制重启的次数,当服务在 StartLimitInterval 内超过了 StartLimitBurst 次后,将不会再次拉起服务。默认的配置会从 /etc/systemd/system.conf 中继承相关参数。

DefaultStartLimitInterval=10s
DefaultStartLimitBurst=5

也就是说,如果在 10s 内超过了 5 次,那么就不会再重新拉起,此时会打印 start request repeated too quickly for XXX.service ,然后服务进入 failed 状态。

StartLimitBurst=0 时,实际上是不会限制重启次数的。

版本依赖

v229 版本之前,采用的是如下的配置,是在 Service 段中。

[Service]
StartLimitInterval=200s
StartLimitBurst=5

v230 之后,采用的是下面配置,注意,此时在 Unit 段中,当然为了向前兼容,也支持上述的方式。

[Unit]
StartLimitIntervalSec=200s
StartLimitBurst=5

注意

这里需要配合 RestartSec 参数,才可以,否则仍然可能会一直生效。

notify

systemd 提供了一种 notify 机制,用于服务向 systemd 发送当前状态信息,然后可以直接通过通用的 systemctl status XXX.service 命令查看。

为了方便使用,同时提供了 systemd-notify(1) 命令,以及相关的 C API 接口 sd_notify(3) ,当然,C 接口使用需要依赖 libsystemd-devel 库。

其中 C 接口的使用可以详细查看 SD_NOTIFY(3) 中的介绍,下面仅以脚本的方式作为示例。

示例脚本

新建测试脚本 /tmp/systemd/mytest.sh 以及服务配置文件 /etc/systemd/system/mytest.service

#!/bin/bash

echo "Current NOTIFY_SOCKET '${NOTIFY_SOCKET}', WATCHDOG_USEC '${WATCHDOG_USEC}'"
mkfifo /tmp/waldo
sleep 10    # activating (start)
systemd-notify --ready --status="First waiting for data..."
            # active (running) && Status: "Waiting for data..."

while : ; do
        read a < /tmp/waldo
        systemd-notify --status="Processing $a"

        # Do something with $a …
        sleep 10

        systemd-notify --status="Waiting for data..."
done
[Unit]
Description=My Test

[Service]
Type=notify
ExecStart=/tmp/systemd/mytest.sh

[Install]
WantedBy=multi-user.target

注意,脚本需要有可执行权限,否则会报错。

常用的操作如下。

# systemctl daemon-reload
# systemctl stop mytest.service
# systemctl start mytest.service
# systemctl status mytest.service

启动时,Active 状态处于 activating (start),如下。

 mytest.service - My Test
   Loaded: loaded (/etc/systemd/system/mytest.service; disabled; vendor preset: disabled)
   Active: activating (start) since Mon 2018-05-14 19:25:17 CST; 3s ago
 Main PID: 31635 (mytest.sh)
   Memory: 296.0K
   CGroup: /system.slice/mytest.service
           ├─31635 /bin/bash /tmp/systemd/mytest.sh
           └─31637 sleep 10

10 秒之后,也就是服务已经正常启动,同时设置了状态信息,此时会显示如下,注意其中的 Status 字段。

 mytest.service - My Test
   Loaded: loaded (/etc/systemd/system/mytest.service; disabled; vendor preset: disabled)
   Active: active (running) since Mon 2018-05-14 19:25:27 CST; 17s ago
 Main PID: 31635 (mytest.sh)
   Status: "Waiting for data..."
   Memory: 204.0K
   CGroup: /system.slice/mytest.service
           └─31635 /bin/bash /tmp/systemd/mytest.sh

通过 echo somedata | tee /tmp/waldo 命令向管道发送一些数据,那么会进入到数据处理过程,也就是处于如下状态。

 mytest.service - My Test
   Loaded: loaded (/etc/systemd/system/mytest.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-05-14 19:25:27 CST; 10min ago
 Main PID: 31635 (mytest.sh)
   Status: "Processing somedata"
   Memory: 296.0K
   CGroup: /system.slice/mytest.service
           ├─31635 /bin/bash /tmp/systemd/mytest.sh
           └─38956 sleep 10

实际上,该命令通过 NOTIFY_SOCKET 环境变量提供了一个 Unix Domain Socket 接口,在 CentOS 中,一般是在 /run/systemd/notify 路径下。

默认只接受主进程发送的请求,如果是单独起了一个进程发送请求,那么需要修改 NotifyAccess 的配置项。

总结

通过 notify 接口,可以明确告知 systemd 服务何时启动正常;可以确定当前服务的执行状态。

watchdog

默认是不开启的,可以通过 WatchdogSec 选项设置,此时会在启动服务的时候设置 WATCHDOG_USEC 环境变量,注意,单位是微秒。

systemd-notify 没有提供更新 watchdog 的方法,对于 sd_notify() 方法,直接周期调用 sd_notify(0,"WATCHDOG=1") 即可。

示例

[Unit]
Description=My Test

[Service]
Type=notify
ExecStart=/usr/bin/sleep 1000
WatchdogSec=60s

[Install]
WantedBy=multi-user.target

如果脚本里没有周期更新状态,那么超时之后会进入到 failed 状态,此时会在 /var/log/messages 中打印如下的日志信息。

May 14 20:00:24 systemd: Starting My Test...
May 14 20:00:24 mytest.sh: Current NOTIFY_SOCKET '/run/systemd/notify', WATCHDOG_USEC '60000000'
May 14 20:00:34 systemd: Started My Test.
May 14 20:01:35 systemd: mytest.service watchdog timeout (limit 1min)!
May 14 20:01:35 systemd: mytest.service: main process exited, code=killed, status=6/ABRT
May 14 20:01:35 systemd: Unit mytest.service entered failed state.
May 14 20:01:35 systemd: mytest.service failed.

当然,也可以通过如下的配置,在服务 Watchdog 异常之后,自动重新拉起。

[Service]
Restart=always
RestartSec=3

另外,当一个服务异常时,可以通过 OnFailure=notify-failed@%n 配置的方式向另外一个服务发送消息。

cgroup

在服务的配置文件中,可以通过 CPUQuota MemoryLimit 来设置 cgroup ,当然也可以通过如下方式临时配置。

# systemctl set-property --runtime uagent.service CPUQuota=5% MemoryLimit=30M

关于资源配置的选项可以通过 man 5 systemd.resource-control 方式查看,默认是没有开启审计的,所以通过 systemd-cgtop 没有显示具体的资源。