systemd 排障手册:从 failed 到定位根因的 7 个命令
systemctl status xxx 显示 failed,然后呢?
大多数人会去翻 /var/log/syslog,翻很久,找不到。这篇按顺序给出 7 个命令,基本能在十分钟内定位到根因。
第 1 步:看完整状态,不要只看最后一行
systemctl status nginx.service --no-pager -l
关键看三块:
Loaded:后面的路径 —— unit 文件在哪,有没有maskedActive:后面括号里的状态 ——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
按流程走:
status -l→exit-codejournalctl -u gitea -o cat→Unable to create lock file: permission denied- 看 unit →
User=git,WorkingDirectory=/var/lib/gitea ls -ld /var/lib/gitea→ 权限是root:root- 原因:前一天我用
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才消停。
发表评论