Skip to content

Usage of Systemd on Linux

简介

Systemd 是 linux 上新一代的系统和服务管理软件。它的优缺点如下。

优点:

  • 用描述性的 DSL 替代传统的 rc 系统的脚本,启动速度快
  • 多种功能更集成化
  • 使用 cgroup 来管理进程(组),避免了传统 rc 系统的父进程非正常退出时其子进程失控的问题
  • 内建 journal 功能

缺点(见仁见智):

  • 多种功能的紧密集成,破坏了组件可替换的 Unix 传统
  • 集成了过多的功能
  • 二进制 journal 文件损坏后,处理相对麻烦
  • systemd 由以下几个部分构成(路径随版本会有差异)

systemd 由以下几个部分构成(路径随版本会有差异)

组件或功能说明
/sbin/init符号链接到 /lib/systemd/systemd,系统的 pid=1 进程,是最重要的组件功能
/lib/systemd/systemd-journald处理 journal (即 log)。需要注意的是,它会接受 syslog
/lib/systemd/systemd-udevd代替原本的 udevd。它监听内核消息,执行 udev 规则来处理设备文件
/lib/systemd/systemd-logind处理用户登录、会话,支持 multi-seat 功能。例如 X 登录及控制台登录
/lib/systemd/systemd –usersystemd 用户实例,用户服务管理进程。这是本文档要描述的核心部分,也是我们将使用的主要功能

用户服务管理进程

用户服务管理进程是管理用户的服务进程的进程。以传统的 rc 系统为例,简要描述功能和优缺点

sysvinit

  • /sbin/init 进程启动后,会读取 /etc/inittab,根据规则直接管理一些进程。这些进程退出后,其对应程序会被重新运行(respawn)
  • 根据一些特殊规则,init 会响应一些事件,例如键盘组合键
  • init 有状态 (runlevel),init 会在不同 runlevel 执行指定的脚本 (/etc/rcN.d),实际上会派生很多服务进程
  • 通过 runlevel 脚本启动的服务,init 不直接管理,服务进程异常退出不会 respawn

daemontools

  • daemontools  对 sysvinit 进行补充。它的主进程 svscan 可以从 inittab 中由 init 启动并直接管理,也可以由 runlevel 脚本启动,需要
  • 注意的是,runlevel 脚本启动的 svscan 退出后不会重新运行
  • svscan 扫描指定的目录下的子目录(通常是 /etc/service/[subdir]),每个子目录启动一个 supervise 进程,这个进程会监控子目录,执行子目录下的 run 脚本
  • supervise 使用 wait() 系统调用,在子进程退出后 respawn。所以 run 脚本最后要用 exec 执行程序,执行的程序不能 daemon 化
  • supervise 使用该子目录下的特别文件来与 svscan 、命令行工具 svc交互
  • supervise 被杀死后,其管理的子进程会失控
  • svscan 和 supervise 是弱耦合的,svscan 被杀死不影响 supervise,可以重新启动 svscan,这是优势,也是劣势
  • svstat 这个状态查看工具,没有内建的列出服务的功能,需要用一些 unix 工具从文件系统上查,容易出错
  • 由于 supervise 会在子目录中创建一些文件,在一些情况管理操作中是“不干净的”,处理较复杂
  • 适合保持那些无状态的或者幂等的服务,对于有状态或非幂等的服务,daemontools 并不适合

systemd 用户实例的配置

通常,用户的第一个会话创建时,由 /lib/systemd/systemd-logind 启动 /lib/systemd/systemd –user;

最后一个会话结束后,/lib/systemd/systemd –user 进程退出。

如果想让 /lib/systemd/systemd –user 在系统启动时启动,之后持续运行,很简单

# loginctl enable-linger user1 [user2 ...]
# loginctl enable-linger root tiger

但是,systemd 用户实例的默认设置往往不够,无法达成一些特殊要求,例如

  • 被杀死后自动重启
  • 调整各种 limit 值,例如可打开的文件描述符数量

Systemd 用户实例的配置由 /etc/systemd/system 下的 user@ 配置实现,具体而言

  • /etc/systemd/system/user@1000.service.d/*.conf  影响 uid=1000 的用户的 systemd 用户实例
  • /etc/systemd/system/user@0.service.d/*.conf 影响 uid=0 即 root 的 systemd 用户实例
  • /etc/systemd/system/user@.service.d/*.conf 影响所有用户的 systemd 用户实例

如果这些目录 (user@.service.d 等) 不存在,可创建目录。以下举两个例子

/etc/systemd/system/user@1000.service.d/always.conf

[Service]
Restart=always

/etc/systemd/system/user@1000.service.d/limits.conf

[Service]
LimitNOFILE=100000

添加配置后,需要 systemctl daemon-reload

具体可配置参数可参考 man page (systemd.service 、systemd.resource-control 和 systemd.exec)

用户服务的配置

如前所述,systemd 用户实例用于启动用户服务。用户服务的配置有以下目录

  • /usr/lib/systemd/user,系统预装软件所需要的用户服务配置
  • /etc/systemd/user ,由系统管理员给所有用户添加的服务
  • ~/.config/systemd/user ,用户自定义的服务

这里给一个简单的例子

~tiger/.config/systemd/user/sleep.service

Description=a sleep example service
[Service]
ExecStart=/bin/sleep 1200
Restart=always
[Install]
WantedBy=default.target

启用使之生效,然后启动

$ systemctl --user enable sleep[.service]
Created symlink from /home/tiger/.config/systemd/user/default.target.wants
/sleep.service to /home/tiger/.config/systemd/user/sleep.service.
$ systemctl --user start sleep[.service]

停止和删除一个服务

systemctl --user stop sleep[.service]
systemctl --user disable sleep[.service]

修改系统自带服务

在上面的例子里,我们使用 user@.service.d 中的配置来修改 user.service 的行为。

如果要扩展或修改随系统安装的服务的行为(配置),不应该直接修改系统配置,而是创建一个以服务的配置文件名后追加 .d 的目录,然后在这个

目录下创建任意以 .conf 为后缀的配置文件,指定你需要增加或修改的选项。

你需要用 systemctl daemon-reload 让修改生效,然后重启服务。

见 http://www.freedesktop.org/software/systemd/man/systemd.unit.html

Systemd的问题

Systemd 管理进程默认是通过 cgroup。当 systemd –user 用户实例被杀掉(本身很稳定,只能是误操作或特意如此)时,其启动的服务进程都

将被杀掉。这和 daemontools 的行为不一样。

daemontools 是 svscan 启动 supervise,由 supervise 启动服务。当杀死 svscan 后,supervise 不受影响,服务不受影响。

systemd –user 的配置中,可以指定 KillMode 来修改行为。KillMode 是 systemctl stop [service] 的行为。KillMode 的值如下

KillMode行为
control-group同一 cgroup 的所有进程会被 kill
process仅此进程被杀死
mixed此进程被 SIGTERM,同 cgroup 的其它进程被 SIGKILL
none任何进程不会被杀

但 –user 用户实例只能使用 control-group 和 mixed 两种模式。

当 systemd –user 用户实例以 Restart=always 启动后,直接 kill 它的操作会使得它管理的处于 enabled 状态的所有服务重启(即使该服务没有指定 Restart=always,没有处于运行状态)。

其它配置问题

su - username 无法执行 systemctl –user 命令

systemctl –user enable some.service 这样的命令,连接到 systemd –user 所监听的 unix socket 上进行通信,而这个 unix socket 的位置是由 XDG_RUNTIME_DIR 环境变量决定的。

正常 ssh 登录,这个环境变量会正确设置。而 su - username 不会。这一个 systemd 的 feature/bug

(见 https://bugs.freedesktop.org/show_bug.cgi?id=70810 和 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=724731#250 )

临时解决办法是

export XDG_RUNTIME_DIR=/run/user/$(id -u)

然后执行 systemd 相关命令

Restart=always 不生效

问题:即使设置了 Restart=always,有时候服务还是不能重启。

为了防止过于快速的重启对系统造成压力,重启有一个默认的间隔 RestartSec;另外还有一个隐含的检查,如果在 10s 中有多于 5 次重启,则触 发 start limit,不能再自动重启(可以/必须手工重启)。如果服务进程的代码或者配置问题造成无法重启,通常会触发 10s 5次的限制。(参考  man systemd.unit 的 StartLimitInterval,以及 man systemd.service 的 RestartSec )

比较好的解决的办法是设置 RestartSec=2 或以上。

[Service]
Restart=always
RestartSec=2s500ms

man systemd.time 可查看指定时间的方式

另外一种情况是用 sysv 脚本启动的服务,默认为 RemainAfterExit=yes,即进程退出也是正常状态,不会触发 restart。解决方法

[Service]
Restart=always
RestartSec=2s500ms
RemainAfterExit=no
PIDFile=/path/to/pidfile

Enable 失败

systemctl enable 需要 service 文件中包含 [Install] 声明。

错误信息:Failed to execute operation: No such file or directory

问题:

  1. 在正常的搜索目录下不存在指定的service 文件
  2. 存在 service 文件,它是一个符号链接,且链接层次太多导致被判断不存在 (ELOOP)
  3. root下enable非/lib/systemd/ 下的服务,重启后会有这个问题。需要将root下的服务(进程1拉起的服务放到 /lib/systemd/system/ 下)

解决办法:

无需将部署目录的 service 文件链接至 ~/.config/systemd/user 下,直接 enable 部署目录的 service 文件

服务不能优雅退出,被强行杀死(SIGKILL)

服务stop(或者是restart),起始systemd会尝试用SIGTERM来让服务自己优雅退出,如果超时(默认90s)之后,服务仍未退出,那么他就会用SIGKILL杀死服务。可以配置Service段的TimeoutStopSec来延长时间,或者把这个值设置成0(不推荐)来禁止timeout,即永不超时。

服务的 stdout 和 stderr

见 man systemd.exec 的 StandardOutput= 和 StandardError= 例如,将 stderr 丢弃

[Service]
StandardError=null

删除有问题的unit服务

使用 systemctl reset-failed 命令来清理有问题的unit,systemd 并没有提供直接删除某个 unit 的功能。