Open wuxiushi opened 6 years ago
原文链接
过去我们项目组的应用都是用 supervisord 托管的。最近因为某些因素,无法使用 supervisord,因此考虑改用 systemd。 作为主流 Linux 发行版的默认选项,之前多多少少用过一点 systemd。不过这次需要上生产环境,所以抽空深入研究一番。
实现进程的分组管理,比如支持一同启动/停止多个生产者/消费者实例。 进程崩溃的时候可以重启 要想改用 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.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 文件夹,那么它就会把日志写到这个文件夹下。否则不进行持久化。
持久化后的日志是这个样子的:
/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_maxbytes 和 stdout/stderr_logfile_backups,你可以规划每一个程序的日志文件的切割粒度。 不同程序的日志不会挤一起,产生日志少的程序也不会被产生日志多的程序干扰。
除了以上几点外,还有一些没有具体提到的功能。 比如 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 的设计是背道而驰的。
根据场景选择最合适自己的,说个故事此前一个需求rsyslog独立,程序本身支持systemd较好。想也没想就使用systemd来管理,但是后来陆续接了几个新程序,开发追求简单,日志处理依赖外部第三方。如果仅仅记录一下那还好,但是还要切割轮转等。初衷是使用systemd替代supervisord,减少配置降低复杂度,现在反倒更麻烦了,最后决定继续使用supervisord。
systemd vs supervisord
原文链接
过去我们项目组的应用都是用 supervisord 托管的。最近因为某些因素,无法使用 supervisord,因此考虑改用 systemd。 作为主流 Linux 发行版的默认选项,之前多多少少用过一点 systemd。不过这次需要上生产环境,所以抽空深入研究一番。
为什么要用 supervisord?
实现进程的分组管理,比如支持一同启动/停止多个生产者/消费者实例。 进程崩溃的时候可以重启 要想改用 systemd,需要看下 systemd 如何应对这两个问题。 (如无指明,在本文中,supervisord 的配置项在 [program:x] 下面,而 systemd 的配置项则位于 [Service])
进程控制
无论 supervisord 还是 systemd,都采用 ini 作为配置文件的格式。跟 supervisord 不同的是,systemd 每个程序都要单独开一个 unit 文件。 supervisord 可以同时启动/停止配置文件中所以的进程(或者某个进程组配置中的进程)。systemd 可以用依赖来实现这一点。下面例子中,我们 就创建了一个可以同时管理的进程组:
相对来说,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 文件夹,那么它就会把日志写到这个文件夹下。否则不进行持久化。
持久化后的日志是这个样子的:
第一次看到这样的目录结构,说不定你会大吃一惊。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 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 的设计是背道而驰的。