分诊的顺序与记录
一句话总结
故障处理中的水平差距不在工具,而在顺序和记录。有了顺序,同一个地方就不会看两遍;有了记录,下一个人就不必从头开始。
为什么需要这个
凌晨 3 点接到报告,谁都会着急。一着急顺序就乱,顺序一乱,“刚才查过了吗?”这个问题就会反复出现。所以熟练的人会机械地遵循事先定好的顺序。判断要在收集完数据之后再做。
工作原理
顺序是从下往上。因为下层坏了,就没必要看上层。
- 名称 —— 并排执行
getent hosts <name>和dig +short <name>。两个结果不同,这个差异就是原因所在的位置。 - 可达 ——
ping -c 3 <IP>。失败了也不要马上下结论,可能是 ICMP 被屏蔽。 - 端口 ——
nc -zv -w 2 <IP> <port>。一定要区分并记录是 refused 还是 timeout。这一个词决定了下一步的排查方向。 - 绑定 —— 在目标主机上执行
ss -ltnp | grep <port>。如果 Local Address 是127.0.0.1,答案就在这里。 - 应答 ——
curl -o /dev/null -s -w '...'。同时查看状态码和各段时间。
每一步都要原样留下命令及其输出。 只写着“已确认”的记录对下一个人毫无价值。反过来,命令和输出附在一起,下一个人就能重新读那些输出,甚至得出不同的结论。
事后报告中至少要有以下五项。
| 项目 | 内容 |
|---|---|
| 症状 | 用户实际看到的现象(不是猜测) |
| 层 | 卡在哪一层 |
| 原因 | 附上作为依据的命令输出 |
| 处理 | 改了什么、怎么改的 |
| 防止再发 | 怎样才能让同样的事不再发生 |
每一层要敲什么
缩小“不行”的范围有一个顺序。从下往上,每上一级候选就少一些。
| 层 | 确认命令 | 在这里失败时 |
|---|---|---|
| 1. 名称 | getent hosts api.example.com |
DNS、/etc/hosts、搜索域 |
| 2. 路径 | ip route get 10.0.3.12 |
路由、默认网关 |
| 3. 可达 | ping -c1 10.0.3.12 |
ICMP 可能被屏蔽(失败了也继续下一步) |
| 4. 端口 | nc -zv 10.0.3.12 8080 或 curl -v telnet://… |
防火墙、安全组、没有监听 |
| 5. 服务 | curl -sv http://10.0.3.12:8080/healthz |
应用 |
重要的是第 3 步失败了也不要停下。ICMP 经常被屏蔽,“ping 不通所以是网络问题”常常是错的。第 4 步才是真正的判定。
nc 的结果分为三种,各自含义不同。
- connected —— 端口是开的。问题在第 5 步之上。
- Connection refused —— 数据包到达了,但没有人在监听。 服务没起来,或者绑定到了别的端口。不是防火墙问题。
- timeout —— 数据包消失了。是防火墙、安全组或路由那一侧的问题。
光是区分这三种,排查范围就大不相同。
确认在哪里监听
出现 Connection refused 时,到服务器一侧查看。
ss -ltnp | grep 8080
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("app",pid=42,fd=3))
↑ the trap is here
如果是 127.0.0.1:8080,只有自己能连到。 这就是容器内 curl localhost:8080 可以、外部却不行的典型原因。必须是 0.0.0.0:8080 或 [::]:8080,外部才能连上。
在应用配置中修改绑定地址(--host 0.0.0.0、bind = "0.0.0.0")。这一项错了,端口映射改得再对也没用。
Kubernetes 中还多一层
Pod 之间不通时,先怀疑网络策略。在设置了默认拒绝(default deny)的命名空间中,策略里没写的通信全部是 timeout。
kubectl -n <ns> get networkpolicy
kubectl -n <ns> describe networkpolicy <name> # read from/to
如果症状是“有时可以”,要看的不是策略,而是端点。三个 Pod 中只要有一个不是 Ready,发往它的请求就会失败,从外部看就是间歇性错误。
kubectl -n <ns> get endpoints <svc> # how many addresses are there?
在现场遇到的样子
假设原因只有一个。 实际故障往往是两个以上的原因叠加在一起。修好一个后说“没好啊”就转向别的方向,连已经修好的也会开始怀疑。所以把每一层都检查到底、把发现的都记下来,最终反而更快。
修完不验证。 改了 hosts 就要用 getent 再确认;改了绑定就要用外部地址再敲一遍。用同一条命令复现失败、再确认成功,才是处理的最后一步。
下一个实验要做的事
你会接到一个同时存在三个缺陷的故障。逐层缩小范围锁定原因,修复,并按规定的格式留下事后报告。