systemd 排障手册:从 failed 到定位根因的 7 个命令

Linux · 7 分钟阅读· 35 次阅读

systemctl status xxx 显示 failed,然后呢?

大多数人会去翻 /var/log/syslog,翻很久,找不到。这篇按顺序给出 7 个命令,基本能在十分钟内定位到根因。

第 1 步:看完整状态,不要只看最后一行

systemctl status nginx.service --no-pager -l

关键看三块:

  • Loaded: 后面的路径 —— unit 文件在哪,有没有 masked
  • Active: 后面括号里的状态 —— failed (Result: exit-code) / timeout / signal
  • 最下面的 Process: ... ExecStart=... (code=exited, status=1/FAILURE)

Result 决定排查方向:

Result 含义 去哪看
exit-code 进程自己退出了 程序自己的日志
timeout 启动超时 Type= 配置、启动时间
signal 被信号杀掉 可能是 OOM
resources 资源限制 ulimit、cgroup
start-limit-hit 重启太频繁被限制 查为什么反复崩

第 2 步:拿程序自己的输出

journalctl -u nginx.service -n 100 --no-pager -o cat

-o cat 只输出消息体,去掉时间戳和进程名,对于多行日志(比如 Java 堆栈、Python traceback)可读性好很多。

如果 journalctl 是空的,说明程序的日志没进 journal——要么它自己写文件了(去看 /var/log/nginx/error.log),要么 unit 里设了 StandardOutput=null。

第 3 步:看这次启动前后的系统事件

journalctl -b -p err --no-pager

-b 本次开机,-p err 只看 error 及以上。

这一步经常能发现「其实是别的东西先挂了」:磁盘满了、网络没起来、依赖的挂载点失败。

第 4 步:查依赖

systemctl list-dependencies --reverse nginx.service
systemctl show nginx.service -p Requires -p Wants -p After -p Before

真实案例:一个服务总在开机时失败,手动 systemctl start 就正常。查依赖发现它 After=network-online.target,但 network-online.target 在网络慢的时候 90 秒就超时了,服务起来时 DNS 还没就绪。

修复方式是给服务加 Restart=on-failure 和 RestartSec=10,让它自己重试,而不是死等。

第 5 步:确认是不是 OOM

journalctl -k | grep -i -E "killed process|out of memory"
dmesg | grep -i oom

OOM 杀进程时,systemd 只会告诉你 Result: signal,status=9/KILL。真正的死因在内核日志里。

如果确认是 OOM,看是谁吃了内存:

systemd-cgtop -m

按 cgroup 排内存,比 top 直观。

第 6 步:看 unit 文件本身

systemctl cat nginx.service
systemctl show nginx.service | grep -E "ExecStart|User|WorkingDirectory|Environment"

几个常见坑:

  • User= 指定的用户没有权限读配置/写目录
  • WorkingDirectory= 不存在(systemd 会直接失败,报 status=200/CHDIR)
  • Environment= 里的变量带空格没加引号
  • ExecStart 用了相对路径

status=200/CHDIR、status=203/EXEC 这两个退出码特别值得记:

  • 200/CHDIR:工作目录进不去
  • 203/EXEC:可执行文件不存在、没有执行权限、或者是脚本缺少 shebang / 用了 CRLF 换行

第 7 步:复现并观察

前六步还没有结论,就用前台方式跑:

# 临时把服务放到前台,看它到底报什么
sudo systemctl stop myapp
sudo -u myapp /usr/local/bin/myapp --config /etc/myapp/config.yml

注意用户要用 User= 里那个。权限问题和环境变量问题,90% 在这里现形。

如果这一步能跑起来,但 systemd 起不来,那就是 unit 文件的问题;如果这一步也起不来,那就是程序本身的问题。这一步能把排查范围一刀切开。

一个真实案例

某天 Gitea 起不来:

gitea.service: Main process exited, code=exited, status=1/FAILURE

按流程走:

  1. status -l → exit-code
  2. journalctl -u gitea -o cat → Unable to create lock file: permission denied
  3. 看 unit → User=git,WorkingDirectory=/var/lib/gitea
  4. ls -ld /var/lib/gitea → 权限是 root:root
  5. 原因:前一天我用 rsync 恢复备份,-a 保留了源端的属主

修复:

sudo chown -R git:git /var/lib/gitea

整个过程两分钟。如果没有 journalctl -o cat,我可能要在 syslog 里翻半小时。

附:两个长期有用的设置

# 1. 崩溃自动重启,但有次数限制
Restart=on-failure
RestartSec=5
StartLimitBurst=5
StartLimitIntervalSec=60

# 2. 把标准输出送进 journal,并加上标识
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

第二条能让 journalctl -t myapp 直接过滤出这个程序的日志,不用记 unit 名。

最后

systemd 让人反感的地方是它把很多东西藏起来了,但它的排错信息其实比 SysV init 时代丰富得多——前提是你知道去哪看。

这七步的顺序是有意义的:从「它怎么死的」到「谁导致了它死」,每一步都在缩小范围。跳过任何一步都可能白翻半小时日志。

评论 (1)

值
值班工程师

第4步查依赖那个案例感同身受,我们也有个服务只在开机时挂,最后发现是DNS没就绪,加了Restart和After才消停。

发表评论

验证码

提交后需站长审核,通过后才会显示。