🐧 第06章 · 故障排查
通用排查思路 + 本服务器案例
故障排查¶
这台服务器上跑着 Nginx、MySQL、Node 应用、GitLab、Xray、RustDesk……一旦出问题,该从哪里下手?这一章先给一套通用的排查方法论,再把它们落在本机真实案例上,帮你把"瞎试"变成"有章法"。
通用排查方法论:时间、范围、变化¶
故障排查的本质是缩小范围。三个问题最先问:
- 什么时候开始的? —— 是最近一次变更后出现的,还是突然冒出来?先想"最近改了什么"。
- 影响范围多大? —— 只是某个服务挂,还是整台机器?是特定用户,还是所有人?
- 系统的哪个层面? —— CPU、内存、磁盘、网络、应用、配置。
一套好用的排查顺序(从外到内、从粗到细):
# ① 机器还活着吗(CPU/内存/负载)
uptime && free -h
# ② 磁盘够吗
df -hT
# ③ 网络通吗
ping -c2 bangdream.xyz && ss -tlnp | head
# ④ 服务状态
systemctl list-units --state=failed --no-pager
systemctl status bangdream nginx mysql
# ⑤ 找日志
journalctl -u nginx --since "30 min ago" -e
tail -f /var/log/nginx/error.log 2>/dev/null
易错 很多人一上来就
tail日志,但没先确认"机器本身是否 OK"。先看机器整体(负载/内存/磁盘),再落到具体服务,最后才看日志,否则可能漏掉磁盘写满这种"服务看起来报错其实根因在别处"的 case。
分层排查:三个高频失败点¶
| 层面 | 症状 | 首查命令 |
|---|---|---|
| 磁盘 | 服务 500、写不进日志、数据库拒绝连接 | df -hT、df -i |
| 端口/进程 | 服务 active 但连不上、端口被占 | ss -tlnp、systemctl status |
| 日志 | 崩溃、重载、权限被拒 | journalctl -u、tail /var/log/... |
案例 1:Node 应用 500,其实是磁盘满了¶
某日 bangdream(端口 3000)返回 500。别看代码,先看机器:
df -h # `/` 100%?→ 根因
du -h --max-depth=1 /var | sort -rh | head # 谁占了
# 常见:journal 日志堆积,清一下
journalctl --vacuum-size=300M
df -h # 释放后再看服务是否恢复
curl -I http://127.0.0.1:3000 # 本机直连验证
易错
df显示 100% 时,应用报错五花八门,但根因往往就是这个。先df -h排除"写不进去"这个最被低估的坑。另外很多服务在磁盘满时会Cannot allocate memory或No space left on device,别被误导到内存/代码方向。
案例 2:端口被占,服务起不来¶
ss -tlnp | grep 3000 # 谁占着 3000
lsof -i :8090 # GitLab 反代端口是否被别的进程占用
kill $(lsof -t -i:3000) # 结束占用者
systemctl restart bangdream
案例 3:服务 active 但访问不通(nginx 反代)¶
Nginx 外层终止 TLS 并反代 Node,若 curl https://bangdream.xyz 返回 502/503,逐层查:
curl -I http://127.0.0.1:3000 # ① 后端 Node 本身通吗?
curl -I http://127.0.0.1:3010 # ② lovelive 后端
systemctl status bangdream # ③ Node 服务 up 吗
systemctl status nginx # ④ nginx up 吗
tail -n 50 /var/log/nginx/error.log # ⑤ nginx 报什么错(connect failed?)
journalctl -u bangdream -e # ⑥ Node 报什么错
502 的常见根因:后端进程挂了、后端端口没监听、nginx 反代 proxy_pass 写错端口。逐层 curl 内网判断"断在哪儿",比在 nginx 配置文件里瞎猜快得多。
案例 4:MySQL 连不上¶
systemctl status mysql
ss -tlnp | grep 3306 # MySQL 是否监听 127.0.0.1
mysql -u root -p -e "show processlist;" # 能否登进(先测认证)
journalctl -u mysql -e # MySQL 错误日志
易错 本机 MySQL 只监听
127.0.0.1:3306(不对外),这是安全的设计。外部应用连不上 MySQL 应先确认不是"试图从外部连"——正确做法是让 Node 应用在本地连,或通过 nginx/受控代理。
通用工具:journalctl 快速拦路虎¶
遇到任何服务问题,journalctl 几乎永远是第一步:
journalctl -u <服务> -n 100 # 最近 100 行
journalctl -u <服务> --since "1 hour ago" -e # 过去 1 小时(时间升序)
journalctl -u <服务> -p err # 只看错误级
journalctl --follow -u <服务> # 实时 tail(new 日志)
时间与服务恢复:重启前先取证¶
很多人遇到故障第一反应是 systemctl restart,但先看日志再重启,否则证据就没了:
# 先取证
journalctl -u <服务> -n 50 --since "30 min ago" > /root/service_err.log
# 再重启
systemctl restart <服务>
# 重启后仍要盯着是否复发
systemctl status <服务>
安全提示 重启会丢失现场信息(崩溃前的进程状态、临时文件)。先抓取日志/快照再重启,否则下次同样的"隐形 bug"还会再来。对数据库、写入型服务务必谨慎重启。
系统级快速体检脚本¶
可以把上面常用命令组合成一个快照,故障时直接跑:
echo "==== 负载/内存 ===="; uptime; free -h
echo "==== 磁盘 ===="; df -hT
echo "==== inode ===="; df -i
echo "==== 失败服务 ===="; systemctl --state=failed --no-pager
echo "==== 监听端口 ===="; ss -tlnp
echo "==== 网络连通(fail2ban 是否误封) ===="; fail2ban-client status sshd
易错点¶
- 不看机器整体直接翻日志:漏掉磁盘满、负载高这类跨层根因。
- 故障当日第一件事就
restart:现场证据丢了。 - 只看
systemctl status的 active 就下结论:active 不代表能正常服务,要用curl/探针验证。 - 试图从外部连本地监听的服务:
127.0.0.1只在本地通。
相关命令¶
uptime、free、df、ss、systemctl、journalctl、curl、lsof、ping、tail、fail2ban-client。