systemd 服务起不来?五步排查法

交付给一线运维:一条服务重启了十几次、systemctl start 只回一句 Job for app.service failed,

journalctl 里翻半天只有一行 exited。其实 systemd 的失败信息非常规范,按五步走,

从"现象分类"到"读到根因",五分钟内能定位到具体那一行配置。

一、先分清三种"起不来"

不同失败形态,排查入口完全不同,先归类再动手:

  1. 根本起不来 :systemctl start 立刻返回失败,进程连创建都没成功。多为 ExecStart 路径、权限、解释器问题,退出码常见 203/EXEC。
  2. 起来又马上退出 :能启动一秒,然后进程自己崩。表现为 Restart=always 不断重启,状态里是 activating (auto-restart),多半是应用自身配置或依赖问题。
  3. 起来了但状态不对 :Active: active (running),可端口不通、健康检查失败。这是 Type= 与就绪判定不匹配,或进程 daemon 化后 systemd 跟丢了。

二、第 1 步:systemctl status 一眼读四行

bash 复制代码
systemctl status app.service --no-pager

重点看四行:

  • Loaded: ------ unit 文件路径是否正确,是否被 masked、disabled。
  • Active: ------ 失败会直接标注 (Result: exit-code) 或 (Result: timeout)。
  • Process: ------ 实际执行的命令行,以及 status=203/EXEC 这类关键退出码。
  • 末尾日志 ------ 只有最近 10 行,够用来判断方向,但不足以定根因。

203/EXEC 基本等于"这个命令 systemd 跑不起来":路径写错、没有执行权限、shelbang 坏了、脚本是 CRLF 换行。

三、第 2 步:journalctl -u 拿全量日志

bash 复制代码
# 看这个服务最近 100 行
journalctl -u app.service -n 100 --no-pager

# 只看本次开机、只看错误级别
journalctl -u app.service -b -p err --no-pager

# 缩小到出问题的时间窗
journalctl -u app.service --since "10 min ago" --no-pager

三句高频报错的含义:

  • Unit app.service not found. ------ 文件没放在 /etc/systemd/system/,或放完没执行 systemctl daemon-reload。
  • Start request repeated too quickly. ------ 重启太密被 systemd 熔断,需要 systemctl reset-failed app.service 再试。
  • Address already in use ------ 端口被别的进程占了,用 ss -lntp 找占用者。

四、第 3 步:核对 unit 文件本身

bash 复制代码
systemctl cat app.service                  # 看最终生效内容(含 drop-in 覆盖)
systemd-analyze verify /etc/systemd/system/app.service   # 语法与依赖检查
systemctl show app.service -p ExecStart -p Environment   # 看 systemd 眼中的真实值

unit 文件里最容易写错的五处:

  1. ExecStart 必须写绝对路径 ,且不能带 shell 语法(管道、&&、重定向都要包一层 bash -c)。
  2. Type= 选错 。前台常驻用 simple(默认),会 fork 后退出的老程序要 forking + PIDFile,oneshot 只跑一次不能常驻。
  3. EnvironmentFile= 路径写错会让变量全空;写成 EnvironmentFile=-/etc/app/env(前面加 -)则文件缺失不报错。
  4. User= / Group= 指定的账号对 WorkingDirectory 和日志目录没有写权限,进程会直接退出。
  5. 依赖写反 :需要网络的要 After=network-online.target 且 Wants=network-online.target,只写 After=network.target 常常起太早。

改完 unit 文件必须:

bash 复制代码
systemctl daemon-reload
systemctl restart app.service

五、第 4 步:查环境差异与资源限制

最常见的"我手工跑明明可以"------因为交互 shell 和 systemd 的环境完全不同:

  • 不加载 /etc/profile :PATH 只有极简几条,java、node 找不到。解决:ExecStart 写全路径,或在 unit 里显式 Environment=PATH=/usr/local/bin:/usr/bin。
  • WorkingDirectory 默认是 /:相对路径配置一律找不到,必须显式指定。
  • 资源上限 :LimitNOFILE、MemoryMax、TasksMax 触顶会直接把进程打掉,journalctl 里能看到 oom 或 Failed to fork。
  • 沙箱与 SELinux :ProtectSystem=strict、PrivateTmp=true 会让服务看不到宿主机路径,表现为"文件明明存在却报 no such file"。
  • 看真实上限 :systemctl show app.service -p LimitNOFILE -p MemoryMax。

六、第 5 步:排除端口与依赖冲突

bash 复制代码
ss -lntp | grep -E ':8080|:3306'      # 端口是否被占
systemctl list-dependencies app.service --reverse | head

如果服务启动后秒退,且日志停在"connecting to db...",那就是依赖的下游没起来。此时不要给应用加 Restart=always 硬扛------重启掩盖的是依赖顺序问题 ,应该在 unit 里用 After= / Requires= 声明清楚,或在应用层加重试与退避。

七、避坑清单

  1. 改完 unit 不 daemon-reload:改动完全不生效,白排查半天。
  2. 用 Restart=always 掩盖根因 :日志被反复重启刷满,真正那一行错误被冲掉。先 systemctl stop 手动跑一次。
  3. ExecStart 里用 shell 语法 :管道、&&、$VAR 都不被解析,必须 bash -lc '...'。
  4. Type=forking 却没配 PIDFile :systemd 不知道主进程是谁,状态一直 activating 直到超时。
  5. TimeoutStartSec 太短:大应用初始化要 2 分钟,默认 90 秒直接判失败,应显式放宽。
  6. 脚本 CRLF 换行 :从 Windows 传上来的启动脚本会报 203/EXEC,dos2unix 一下。
  7. 别在 /usr/lib/systemd/system/ 里直接改 :升级会被覆盖,自定义放 /etc/systemd/system/,覆盖用 drop-in(/etc/systemd/system/app.service.d/override.conf)。

小结

口诀:看状态分类型 → 翻日志找报错 → 核 unit 查语法 → 比环境与限额 → 排端口与依赖。

systemd 的报错信息其实很诚实,它只是被"重启刷屏"和"环境不一致"藏起来了。

把服务先停掉手动跑一次、把 unit 文件当成代码来 review,绝大多数"起不来"都能一眼看破。

相关推荐
OpenTiny社区1 小时前
多会话 + 流式输出怎么落地?TinyRobot Kit 对话数据层深度解析
前端·vue.js
派小心.1 小时前
网站统计采样数据的误差核对
大数据·服务器·前端·数据库·人工智能·小程序·数据分析
变与不变8062 小时前
防抖节流详解
开发语言·前端·javascript
用户72526496629692 小时前
浏览器为什么加载不了超大图片
前端·javascript
酷炫码神3 小时前
Day 2|CSS 盒模型:把昨天的骨架,变成能看的页面
前端·css
Amos_Web3 小时前
我做了一个 Chrome 插件,专治“这篇文章我明明看过,但重点去哪了?”
前端·浏览器
abcyuu3 小时前
用友 U8 Cloud 生产制造模块深度解析:从 BOM 建立到成本核算的技术实现
前端·数据库·制造
零基础1233 小时前
为什么 Agent 用 SQLite 数据库?
前端·人工智能·sqlite·开源
xuyanzhuqing3 小时前
如何在一周内将一个巨石工程实现国际化
前端