前言
过去我们项目组的应用都是用 supervisord
托管的。最近因为某些因素,无法使用 supervisord
,因此考虑改用 systemd
。
systemd
作为主流 Linux
发行版的默认选项,之前多多少少用过一点 systemd
。不过这次需要上生产环境,所以抽空深入研究一番。
为什么要用supervisord
?
- 实现进程的分组管理,比如支持一同启动/停止多个生产者/消费者实例。
- 进程崩溃的时候可以重启
要想改用 systemd
,需要看下systemd
如何应对这两个问题。
(如无指明,在本文中,supervisord
的配置项在 [program:x]
下面,而 systemd
的配置项则位于 [Service]
)
进程控制
无论 supervisord
还是 systemd
,都采用 ini
作为配置文件的格式。跟 supervisord
不同的是,systemd
每个程序都要单独开一个unit
文件。
supervisord
可以同时启动/停止配置文件中所有的进程(或者某个进程组配置中的进程)。systemd
可以用依赖来实现这一点。下面例子中,我们
就创建了一个可以同时管理的进程组:
1 | ; group.target |
systemctl start group.target
,prog1
和 prog2
也会带起来。systemctl restart group.target
,prog1
和 prog2
也会跟着重启。
相对来说,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
两套术语间迷糊,请允许我抛弃所有术语,用自己的话描述。
从上图可以看到,进程在 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=always
和 Restart=on-failure
。systemd 还提供了 Restart=on-success
(只有正常
退出才重启)和 Restart=on-abort
(只有收到异常信号才重启)。
对于重启次数,supervisord 没有作限定。因为重启一个程序时,supervisord 会先让它处于 STARTING 状态。这个状态的持续时间,是由配置项
中的 startsecs
决定的,默认 1 秒。如果是不可恢复的错误,程序就不可能成功进入到 RUNNING 状态。当然也许存在这样的情况,程序运行 1 秒
后,就会崩溃。那么它就会陷于不停重启的无间地狱。
systemd 对此一如既往,提供了 N 多选项以供采用。你可以用 RestartSec
控制每次重启的间隔,可以用 StartLimitInterval
和 StartLimitBurst
设定
给定周期内能够重启的次数。比如指定 StartLimitInterval=1s
,StartLimitBurst=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
文件夹,那么它就会把日志写到这个文件夹下。否则不进行持久化。
持久化后的日志是这个样子的:
1 | /var/log/journal/c4010ceea79847afbedecb60a775db96/ |
第一次看到这样的目录结构,说不定你会大吃一惊。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_maxbytes
和 stdout/stderr_logfile_backups
,你可以规划每一个程序的日志文件的切割粒度。
不同程序的日志不会挤一起,产生日志少的程序也不会被产生日志多的程序干扰。
开机自启
systemd
支持开机自启, 而supervisor
开机自启需依赖其他程序实现, 其本身也是被监控的对象
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
的设计是背道而驰的。
本文转载自: segmentfault