登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux systemd.service RestartPreventExitStatus 怎样让指定退出码不重启

来源:17golang原创

时间:2026-11-05 03:01:56 371浏览 收藏

如果一个长期运行的 Linux 服务大多数异常退出都应该自动拉起,但某个退出码代表“主动结束、等待人工处理”或“配置已经明确失效”,可以把默认重启策略和例外状态拆开配置:用 Restart=on-failure 负责常规恢复,用 RestartPreventExitStatus= 列出不应自动重启的退出码或信号。

本文只讨论 systemd.service 的主服务进程,不把 ExecStartPre=、ExecStop= 等控制命令的退出状态混进来。当前 systemd.service 官方手册地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html。

最小可靠写法是:先给服务设置明确的 Restart=,再把需要阻止自动重启的状态写入 RestartPreventExitStatus=。该规则只检查服务主进程的退出状态;它不是一个覆盖所有 Exec* 命令的全局失败开关。

先把“默认重启”和“禁止重启”分成两层

Restart=on-failure 表示服务因为非正常退出、信号、超时等情况失败时尝试重启。它解决的是默认策略:大多数故障希望服务回来。RestartPreventExitStatus= 则是例外集合:主服务进程返回其中某个退出码或信号时,即使 Restart= 原本允许重启,也不再自动重启。

例如,应用约定退出码 75 表示“外部依赖暂时不可用但由运维流程接管”,可以把 75 放入禁止列表。这里的数字是应用协议示例,不是 systemd 为所有程序规定的统一含义;systemd 只负责根据主进程实际返回的状态做策略判断。

systemd 根据主服务退出状态决定是否自动重启的流程说明图
图1:静态结构说明图,展示 Restart= 默认策略与 RestartPreventExitStatus= 例外列表的判断关系,不是命令运行截图或真实运行证据。

用最小 service 单元表达退出状态例外

下面的 unit 文件让主服务默认按失败自动重启,但当主进程返回 75 或收到 SIGTERM 时,不再按该规则自动拉起。配置文件中的注释使用 systemd 支持的 # 语法,便于把每个策略的目的留在配置旁边。

[Unit]
Description=带退出状态例外的示例服务

[Service]
# 这里替换为真实的长期运行程序,示例程序约定 75 是人工接管状态。
ExecStart=/usr/local/bin/example-worker

# 普通失败尝试自动恢复,避免一次偶发退出就让服务永久停住。
Restart=on-failure

# 主进程返回 75 或被 SIGTERM 终止时,禁止自动重启。
RestartPreventExitStatus=75 SIGTERM

# 让日志直接进入 journal,便于和 unit 状态一起查看。
StandardOutput=journal
StandardError=journal

[Install]
# 仅在希望随多用户目标启动时启用该安装关系。
WantedBy=multi-user.target

如果业务只想排除一个数字,就只写这个数字;如果要排除信号,也可以写信号名。不要把应用自己的“正常结束”概念直接套到所有服务上:先在程序文档或退出码约定中确认状态含义,再决定是否加入禁止列表。

修改后按顺序检查配置和状态

将文件保存到例如 /etc/systemd/system/example-worker.service 后,先让 systemd 重新读取 unit 文件,再查看最终内容和关键属性。下面命令只展示检查流程,example-worker 是占位 unit 名称。

# 重新读取 unit 文件;不会自动启动服务。
sudo systemctl daemon-reload

# 查看 systemd 当前读取到的完整 unit 内容。
systemctl cat example-worker.service

# 只查看重启策略和禁止重启状态,减少人工翻查。
systemctl show example-worker.service \
  --property=Restart,RestartPreventExitStatus

# 查看当前状态与最近日志,确认失败原因和主进程状态。
systemctl status example-worker.service --no-pager
journalctl -u example-worker.service -n 50 --no-pager

daemon-reload 只负责重新读取配置,不等于已经重启服务。真正需要切换进程时,再根据变更窗口选择 systemctl restart;如果只是确认 unit 文本和属性,先不要把检查动作和重启动作混在一起。

配置项解决的问题容易误读的地方
Restart=on-failure定义大多数失败退出时的默认恢复动作不是无条件重启,仍受退出原因和限速影响
RestartPreventExitStatus=75让主进程返回 75 时跳过自动重启75 的业务含义需要由应用约定,systemd 不替你定义
systemctl show查看解析后的关键属性看到了配置不代表已经执行了重启

只对主服务进程生效,别把 ExecStartPre 当成主进程

RestartPreventExitStatus= 关注的是服务主进程,也就是通常由 ExecStart= 启动、并由 Type= 等设置帮助 systemd 识别的进程。官方手册明确说明,它不作用于 ExecStartPre=、ExecStartPost=、ExecStop=、ExecStopPost= 或 ExecReload=。

[Service]
# 预启动命令只是准备步骤,不是 RestartPreventExitStatus 的目标进程。
ExecStartPre=/usr/local/bin/prepare-worker

# 这个命令才是长期运行服务的主入口,退出状态会参与重启策略判断。
ExecStart=/usr/local/bin/example-worker

# 仅为主进程指定不自动重启的状态。
Restart=on-failure
RestartPreventExitStatus=75

因此,如果 ExecStartPre 失败,应该回到该控制命令本身的退出行为、unit 状态和日志判断原因,而不是期待把它的退出码塞进 RestartPreventExitStatus= 就能改变所有启动阶段行为。

systemd 主服务进程与 ExecStartPre 等控制命令的生效范围说明图
图2:静态范围说明图,突出 RestartPreventExitStatus 作用于主服务进程,不是 ExecStartPre、ExecStop 等控制命令的通用过滤器。

退出状态、Restart= 和启动限速要一起看

禁止某个状态重启,不等于所有退出都不会重启;没有命中禁止列表的失败仍按 Restart= 处理。另外,自动重启还受 unit 的启动速率限制影响。若服务短时间内反复失败,StartLimitIntervalSec= 与 StartLimitBurst= 可能让后续启动暂时被限制,这和 RestartPreventExitStatus= 是两个不同层次的问题。

排查时可以按三件事分层:

  1. 从日志确认主进程到底返回了哪个数字或信号,而不是凭 status=1/FAILURE 的展示文字猜测。
  2. 用 systemctl show 对照 systemd 解析后的 Restart 与 RestartPreventExitStatus。
  3. 如果没有命中禁止列表却仍不重启,再检查启动限速、依赖关系和服务是否被人工停止。

这三层信息能把“退出状态例外”“默认重启策略”“重启次数被限制”区分开,避免单独修改一个参数后得出错误结论。

适合生产的收口方式

我的建议是让退出码语义保持少而稳定:应用只为明确的人工接管、不可自动恢复或需要外部配置修正的场景约定例外码;临时网络抖动、普通依赖失败等仍交给 Restart=on-failure 和日志告警处理。禁止列表越长,自动恢复覆盖面越小,后续维护者越难判断服务为什么停住。

上线前把 unit 文件、退出码约定、回滚文件和告警说明放在同一个变更记录里。验证时先用非生产 unit 名称或维护窗口观察状态,再决定是否启用。不要把本文中的占位路径、退出码和服务名直接复制到生产环境,也不要把“命令已写出”当成“机器已执行”。

RestartPreventExitStatus 可以写多个状态吗?

可以,状态之间用空格分隔,可以混合数字、状态名和信号名。需要重置此前继承或重复配置的列表时,按 systemd.service 手册说明使用空值重置,再写入最终集合。

它会改变服务状态中的退出码含义吗?

不会。它改变的是自动重启决策,不会把数字 75 改名,也不会自动把应用的退出码变成成功。状态含义仍要看程序约定和日志。

为什么服务没有重启,但我没有写 RestartPreventExitStatus?

先检查 Restart= 是否允许该退出原因重启,再检查启动限速、人工停止、依赖失败和 unit 文件是否真的被重新加载。禁止列表只是其中一种原因。

ExecStartPre 返回指定退出码时也会被这个选项拦截吗?

不会。该选项针对主服务进程,控制命令的失败需要通过启动日志、unit 状态和对应的 Exec* 行为单独分析。

最后可以把它记成一句话:Restart= 负责“默认怎么恢复”,RestartPreventExitStatus= 负责“哪些主进程状态例外不恢复”。先确认退出状态来源,再写最小例外集合,配置才不会把可恢复故障和应停机状态混成一团。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>