fangpsh's blog

Systemd Restart=always不一定always

整理笔记,发现之前遗留的todo,好像是 5、6 年前一次故障,服务没有自动拉起,因为大家都误解(不够了解)了 Systemd 的 Restart。

简单来说,不能简单的 认为在 Systemd Service Unit 中配置一个Restart=always 后,服务能够在任何失败的情况下自动重启。这样配置,如果遇到配置错误、服务依赖等各种情况,几秒内重启失败,直接进入失败状态,日志显示:repeated too quickly

这是因为还有另外几个参数,即 systemd 的默认行为,不允许无限重启的情况出现。

  1. RestartSec:重启前暂停几秒)
  2. StartLimitIntervalSec 和StartLimitBurst 联合控制,在 StartLimitIntervalSec 最多启动和StartLimitBurst次,如果失败,直接进入 failed 状态。

你可能会说吗,都没设置这些呀,对了,就是因为没配置(不知道配置),systemd 有默认值:

DefaultRestartSec=100ms
DefaultStartLimitInterval=10s
DefaultStartLimitBurst=5

每次重启前就等待 100ms,进入失败,再次尝试,再失败,再等待。如果启动后进入失败的速度非常快(配置错误、循坏依赖等情况),等于 100ms 重试一次,5次机会在 1s 内 肯定就耗完了。这之后,systemd 不会再尝试重启服务,除非经过了StartLimitInterval之后,你手工重启了服务。或者systemctl reset-failed 清空计数器。

可能我们理想的是 systemd 几秒钟尝试重启一次我们的服务,永远尝试下去,直到成功。

几种方式:

  1. 在默认systemd配置下, 指定RestartSec,例如等于 3 秒,3*5>DefaultStartLimitInterval(10s)
  2. 精细化控制每个 服务的RestartSec、StartLimitBurst、StartLimitInterval
    1. 例如将StartLimitInterval设置为 0,表示没有限制,无限时间内尝试重启
  3. 修改全局默认的 3 个参数(谨慎);
    1. DefaultRestartSec太大,服务可能无法立即恢复,SLA 是按秒计算的;
    2. 直接将DefaultStartLimitInterval设置为 0,可能导致大量CPU 时间浪费在重启服务上;

看完Hacker News 上的讨论Systemd: Enable indefinite service restarts (stapelberg.ch),我倾向于认为这种默认设置是对的,systemd 首先要保证重启时,服务要尽快拉起,RestartSec 需要尽可能小。在这个前提下,如果存在默认无限重启的策略,可能导致 CPU资源耗费在重启上。例如讨论中提到的移动端电池的问题。
在生产环境中,服务启动,可能还伴随着一系列的 IO 行为(清理日志、准备缓存等),或与其他服务通信(葱外部拉取配置,发送信息等),控制不好,非常容易变成 DoS。

所以当我们主动设置为无限重启时,也需要注意是否会带来副作用。


在互联网上搜索,有大量的用户落入这个想当然的逻辑,我怀疑这个设计上不符合用户直觉。

我猜想作者这么设计,是因为底层的流控算法思维习惯,往上变成了产品逻辑,从下往上思考,而不是从用户角度倒推。

如果 Systemd 采用另外一种逻辑:在StartLimitInterval 时间尝试StartLimitBurst 之后,不进入永久失败状态,而是等待StartLimitInterval 结束之后,在新的一轮中,再次尝试呢?或者说经过一轮重启失败后,等待的时间间隔可以让用户指定,是等待固定间隔,还是递增,亦或指数增长。

即当用户指定 Restart= 时,需要再配合上述间隔等待时间才可生效。