systemd vs supervisord

spacewander

过去我们项目组的应用都是用 supervisord 托管的。最近因为某些因素,无法使用 supervisord,因此考虑改用 systemd。
作为主流 Linux 发行版的默认选项,之前多多少少用过一点 systemd。不过这次需要上生产环境,所以抽空深入研究一番。

为什么要用 supervisord?

  1. 实现进程的分组管理,比如支持一同启动/停止多个生产者/消费者实例。

  2. 进程崩溃的时候可以重启

要想改用 systemd,需要看下 systemd 如何应对这两个问题。
(如无指明,在本文中,supervisord 的配置项在 [program:x] 下面,而 systemd 的配置项则位于 [Service]

进程控制

无论 supervisord 还是 systemd,都采用 ini 作为配置文件的格式。跟 supervisord 不同的是,systemd 每个程序都要单独开一个 unit 文件。
supervisord 可以同时启动/停止配置文件中所以的进程(或者某个进程组配置中的进程)。systemd 可以用依赖来实现这一点。下面例子中,我们
就创建了一个可以同时管理的进程组:

; group.target
[Unit]
Description=Application
Wants=prog1.service prog2.service
; prog1.service
[Unit]
Description=prog1
PartOf=group.target

[Service]
ExecStart=/usr/bin/prog1
Restart=on-failure
; prog2.service
[Unit]
Description=prog2
PartOf=group.target

[Service]
ExecStart=/usr/bin/prog2
Restart=on-failure

systemctl start group.targetprog1prog2 也会带起来。systemctl restart group.targetprog1prog2 也会跟着重启。

相对来说,supervisord 的做法更加直观一些。

如果要更改 supervisord 的配置文件,supervisord 需要运行 supervisorctl reread 才会生效。
而 systemd 则需要 systemctl daemon-reload。半斤八两吧。
不过 supervisord 有一个好。如果你不知道哪些程序的配置改变了,简单地执行 supervisorctl update,所有涉及的进程都会被重启。
而 systemd 貌似做不到这一点。

systemd 可以指定 stop 操作时可以选择的命令(ExecStop=)。另外它还提供了 ExecReload=,可以自定义调用 systemctl reload xxx 重新读取程序配置文件时的操作。
supervisord 不支持 reload 指定进程。同时对于 stop 操作,它只允许你选择要发送哪种信号...

supervisord 的 stopwaitsecs 可以控制 stop 操作后等待程序退出的耐心(以秒衡量)。待给定的耐心都消耗完毕后,supervisord 才会痛下杀手,发送 SIGKILL。
systemd 对应的配置项是 TimeoutStopSec=。systemd 会给多一次机会,在下最后通牒之前,会先发送 SIGTERM,过另一个 TimeoutStopSec 之后才发送 SIGKILL。

进程重启

为了避免在 supervisord 和 systemd 两套术语间迷糊,请允许我抛弃所有术语,用自己的话描述。
life-cycle

从上图可以看到,进程在 RUNNING 之前,会有一个 STARTING 的过程。在 STARTING 过程中,进程可能会读取配置文件,进行初始化。
这一过程中的错误处理,跟 RUNNING 状态的应当有所不同。
以上是 supervisord 的想法。
所以 supervisord 提供了单独的 startretries 配置项,用来配置 STARTING 阶段的重启次数。
systemd 对此没有特殊处理。

一个程序,从 RUNNING 到 EXITED,有两种可能:正常退出或异常退出…(废话)
这两种情况,是通过配置的退出码来区分的。对于 supervisord,这个配置项是 exitcodes。systemd 则通过 SuccessExitStatus 来控制。
有趣的是,exitcodes 的默认值是 0,2,不知道为何它会认为 2 也是正常的退出码。

如果配置了 autorestart = true,只要程序退出,supervisord 都会把它启动起来。相对的,如果配置的是 autorestart = unexpected,则只有
异常退出才会重启。这两个选项,在 systemd 里对应 Restart=alwaysRestart=on-failure。systemd 还提供了 Restart=on-success(只有正常
退出才重启)和 Restart=on-abort(只有收到异常信号才重启)。

对于重启次数,supervisord 没有作限定。因为重启一个程序时,supervisord 会先让它处于 STARTING 状态。这个状态的持续时间,是由配置项
中的 startsecs 决定的,默认 1 秒。如果是不可恢复的错误,程序就不可能成功进入到 RUNNING 状态。当然也许存在这样的情况,程序运行 1 秒
后,就会崩溃。那么它就会陷于不停重启的无间地狱。

systemd 对此一如既往,提供了 N 多选项以供采用。你可以用 RestartSec 控制每次重启的间隔,可以用 StartLimitIntervalStartLimitBurst 设定
给定周期内能够重启的次数。比如指定 StartLimitInterval=1sStartLimitBurst=3,就可以实现跟 supervisord 一致的默认重启策略。

比较完最基本的两种功能,让我们继续看看,两者在一些小细节上的对对碰。

控制实例数

supervisord 可以用 numprocs 来控制单个程序对应的实例数。systemd 也可以做到这一点,虽然有点麻烦(某种意义上,更加强大)。
systemd 会把以 @ 结尾的 service 文件当作模板,在运行时根据给定的参数展开成多个实例。

具体实现方式见:http://0pointer.de/blog/proje...

日志

supervisord 能够重定向被托管的程序的 stdout 和 stderr 到日志文件中,并提供日志切割服务。systemd 也支持这一点,尽管它的实现有很大的不同。

根据鄙人的经验,基于定期检查的日志切割服务,不是个好的选择。
一旦遇上突发高峰,有可能会出现日志无法及时切割的情况;而调小检查间隔,大部分情况下都在无意义地空转。(说的就是你,logroated)
好在无论是 supervisord,还是 systemd,提供的切割服务都是实时的。每当写入内容会超过上限时,就会自动切割。

systemd 的日志服务是通过 journald 组件实现的。你可以在 /etc/systemd/journald.conf 中配置它。
journald 默认的日志存储形式是 Storage=auto。这个选项比较奇妙,如果你创建了 /var/log/journal 文件夹,那么它就会把日志写到这个文件夹下。否则不进行持久化。

持久化后的日志是这个样子的:

/var/log/journal/c4010ceea79847afbedecb60a775db96/
├── system.journal
├── user-1000.journal
└── user-65534.journal

第一次看到这样的目录结构,说不定你会大吃一惊。journald 设计者脑洞不是一般的大。从这个结构上,根本看不出应用日志在哪里嘛。
不,完全没有这样的必要,因为所有的程序的日志都会写到一块去。不分彼此,全变成一团浆糊。随便一提,日志默认都是压缩的。

要看日志,你得用 journalctl。比如看 prog1.service 的日志,需要 journalctl -u prog1.service。要看特定时期的日志,需要 journalctl --since $timestamp --until $timestamp

这么前卫的设计我可接受无能。这种 journalctl 控制一切的方式,导致 systemd 日志无法集成到传统的日志收集工具中。
程序员工具箱中各种 text base 处理工具,对此也大眼瞪小眼,只能对着 journalctl 低三下四,接受对方的小脾气。

journald 提供了三个配置项,RuntimeMaxFileSize=RuntimeMaxFiles=。顾名思义,就是单个日志文件大小和允许的日志数。
另外,RuntimeMaxUse=RuntimeKeepFree= 可以控制总大小的上限。

supervisord 在这方面做的要好得多。通过 stdout/stderr_logfile_maxbytesstdout/stderr_logfile_backups,你可以规划每一个程序的日志文件的切割粒度。
不同程序的日志不会挤一起,产生日志少的程序也不会被产生日志多的程序干扰。

systemd vs supervisord

除了以上几点外,还有一些没有具体提到的功能。
比如 supervisord 通过 priority 配置进程启动顺序,以及 systemd 对应的 Before/After 依赖机制。
比如 supervisord 的 events 功能,和与之相对应的 systemd 的 notify 机制。
比如 supervisord 可以管理 fastcgi(真有人这么做吗)。
比如 systemd 提供的基于 cgroup 的资源限制。
由于没有使用经验,对这些功能就不作一一比较了。

是时候结题了。
systemd 和 supervisord 各有长短,不存在哪一方绝对的碾压。
systemd 跟 Linux 紧密结合,所需的依赖少,其提供的保障自然比 supervisord 更可靠。然而在强大的能力背后,也有配置复杂、不易上手等问题。
supervisord 偏于应用层,却因此有独特的用武之地。
举个例子,许多人会往 docker 打包里面封入一份 supervisord,让它来做 PID 1,以此稍微增强下健壮性。
换 systemd 做同样的事,就像用园艺剪刀裁纸,即使能够顺利完成,也难免事倍功半。毕竟这样的方式跟 systemd 的设计是背道而驰的。

阅读 8.7k

spacewander
这个专栏什么都有,大部分都是关于Linux或后端开发的

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.

5.4k 声望
484 粉丝
0 条评论
你知道吗?

make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.

5.4k 声望
484 粉丝
宣传栏