本次排查遵循"确认故障类型 -> 寻找日志断点 -> 定位暴增源头 -> 查看具体报错 -> 验证临终遗言 -> 反向排除 -> 止血验证"的逻辑链条。以下是完整复盘,命令结果仅保留关键部分。
第一阶段:确认故障类型(真死机还是假死?)
排查思路: 检查是否有正常的关机记录,判断是硬件断电/内核崩溃(真死机),还是内核存活但服务卡死(假死)后被人强制重启。
执行命令:
bash
last -x reboot shutdown | head -20
关键结果:
text
reboot system boot 6.6.0-138.0.0.11 Fri Oct 16 14:59 still running
reboot system boot 6.6.0-138.0.0.11 Fri Oct 16 11:47 still running
reboot system boot 6.6.0-138.0.0.11 Fri Oct 16 10:15 still running
reboot system boot 6.6.0-138.0.0.11 Thu Oct 8 16:03 still running
shutdown system down 6.6.0-138.0.0.11 Thu Oct 8 16:02 - 16:03 (00:00)
结论: 10月16日当天有3次 reboot,但完全没有对应的 shutdown 记录。确认为非正常关机(假死)后强制重启。
第二阶段:寻找日志断点(锁定时间窗口)
排查思路: journald 未持久化,历史启动日志已丢失。转向传统 rsyslog 的 /var/log/messages,通过统计每分钟日志量,寻找系统假死的时间规律。
执行命令:
bash
# 确认日志文件状态
journalctl --list-boots
ls -lht /var/log/messages* /var/log/syslog* 2>/dev/null
# 统计时间分布
awk '{print $1, $2, $3}' /var/log/messages-20261016 | cut -d: -f1-2 | uniq -c | tail -80
关键结果:
text
14 Oct 16 10:31
12 Oct 16 10:32
1467 Oct 16 10:33 <-- 日志量突然暴增
890 Oct 16 10:34
...
59 Oct 16 11:09
4 Oct 16 11:10 <-- 日志骤降
2083 Oct 16 11:47 <-- 手动重启
...
557 Oct 16 12:23 <-- 故障复现
1082 Oct 16 12:24
结论: 10:33 日志量从几十条暴增至 1467 条(故障爆发);11:10 骤降至 4 条,随后空白长达36分钟(系统假死);11:47 手动重启后,12:23 再次复现同样的暴增模式。
第三阶段:定位暴增源头(谁在刷屏?)
排查思路: 锁定 10:33 这个爆发窗口,统计日志来源,找出是谁在疯狂写日志。
执行命令:
bash
grep "Oct 16 10:33:" /var/log/messages-20261016 | awk '{print $5}' | sort | uniq -c | sort -rn
关键结果:
text
962 cri-dockerd[3096]:
180 containerd[1332]:
130 kubelet[3199]:
99 systemd[1]:
结论: 排在第一的是 cri-dockerd(962条),紧随其后的是 containerd 和 kubelet,全部是 K8s 底层容器运行时和节点代理组件。
第四阶段:查看具体报错(看清真凶)
排查思路: 查看暴增窗口的详细报错,还原底层发生的动作。
执行命令:
bash
grep "Oct 16 10:33:" /var/log/messages-20261016 | grep kubelet | head -20
grep "Oct 16 10:33:" /var/log/messages-20261016 | grep cri-dockerd | head -20
关键结果:
text
# kubelet 报错
Oct 16 10:33:10 ... "Error syncing pod, skipping" err="failed to \"StartContainer\" for \"vector\" with ImagePullBackOff: \"Back-off pulling image \\\"repo.grgbanking.com/library/timberio/vector:0.39.0-debian\\\"\""
# cri-dockerd 报错
Oct 16 10:33:15 ... level=info msg="Will attempt to re-write config file /var/lib/docker/containers/.../resolv.conf as [nameserver 10.96.0.10 ...]"
Oct 16 10:33:59 ... ipam_plugin.go 373: Acquired host-wide IPAM lock.
Oct 16 10:33:59 ... ipam_plugin.go 379: Released host-wide IPAM lock.
# kubelet 报错
Oct 16 10:33:10 ... "Error syncing pod, skipping" err="failed to \"StartContainer\" for \"vector\" with ImagePullBackOff: \"Back-off pulling image \\\"repo.grgbanking.com/library/timberio/vector:0.39.0-debian\\\"\""
# cri-dockerd 报错
Oct 16 10:33:15 ... level=info msg="Will attempt to re-write config file /var/lib/docker/containers/.../resolv.conf as [nameserver 10.96.0.10 ...]"
Oct 16 10:33:59 ... ipam_plugin.go 373: Acquired host-wide IPAM lock.
Oct 16 10:33:59 ... ipam_plugin.go 379: Released host-wide IPAM lock.
结论: 核心根因是镜像仓库 repo.grgbanking.com 出现 502 故障,导致 kubelet 无限重试拉取镜像(ImagePullBackOff)。每次失败,cri-dockerd 都要重新为容器配置网络(重写 resolv.conf、调用 Calico CNI 高频争抢 IPAM 全局锁),形成了正反馈循环。
第五阶段:查看临终遗言(验证假死原因)
排查思路: 查看系统彻底崩溃前最后几秒的日志,验证资源耗尽的原因。
执行命令:
bash
grep "Oct 16 11:10:" /var/log/messages-20261016
关键结果:
text
Oct 16 11:10:02 ... "ExecSync cmd from runtime service failed" err="rpc error: code = DeadlineExceeded desc = context deadline exceeded" ... cmd=["/bin/sh","-c","exec pg_isready ..."]
Oct 16 11:10:02 ... "ExecSync cmd from runtime service failed" err="rpc error: code = DeadlineExceeded desc = context deadline exceeded" ... cmd=["/opt/bitnami/scripts/etcd/healthcheck.sh"]
结论: 仅存的4条日志全是 DeadlineExceeded(RPC调用超时)。这说明 cri-dockerd 已彻底被压垮,连最简单的健康检查都无法响应,内核调度随之假死。
第六阶段:反向排除常见故障(排除干扰项)
排查思路: 排查常见的服务器死机原因(内存、磁盘、内核、conntrack),避免误判。
执行命令:
bash
grep -iE "conntrack|no space left|soft lockup|out of memory|oom-killer|disk full" /var/log/messages-20261016
关键结果:
text
Oct 16 10:15:27 ... systemd-sysctl[813]: Couldn't write '1000000' to 'net/netfilter/nf_conntrack_max', ignoring: No such file or directory
Oct 16 10:15:49 ... dnsmasq[2083]: compile time options: ... no-conntrack ...
结论: 完全排除了磁盘满、内存溢出(OOM)、内核死锁(soft lockup)。关于 conntrack 的报错仅说明系统未加载该模块,因此也排除了 conntrack 表满导致网络断开的可能。
第七阶段:止血验证(逻辑闭环)
排查思路: 既然根因是重试风暴,斩断风暴来源,观察系统是否恢复。
执行命令:
bash
# 在 master1 上执行止血
kubectl get deploy -n dp-system --no-headers | awk '$1 ~ /^dp-/ {print $1}' | xargs -r -I {} kubectl scale deploy {} -n dp-system --replicas=0
kubectl taint nodes node2 maintenance=true:NoSchedule
# 在 node2 上观察效果
journalctl -u cri-docker --since "1 min ago" | wc -l
uptime
结论: 副本数降为0并打上污点后,cri-dockerd 日志量骤降,节点负载回落,SSH恢复响应。证明止血动作完美契合了病因。
最终根因链(已完整闭环)
text
镜像仓库 repo.grgbanking.com 返回 502
│
├──▶ kubelet 无限重试拉镜像(PullImage / ErrImagePull / ImagePullBackOff)
│ │
│ └──▶ 每次失败都触发完整 CNI 操作:
│ cri-dockerd → Calico IPAM 全局加锁 → 分配 IP → 创建 veth
│ → 失败 → 清理 netns → 释放 IP → 解锁
│ (962 条/分 cri-dockerd + 180 条/分 containerd 日志)
│
└──▶ 容器日志无法轮转(cri-dockerd 不支持 reopen log)
│
└▶ /var/log/pods/ 日志无限增长(同一个 0.log 从 12.1MB 涨到 12.2MB)
│
└▶ 磁盘 I/O 被大量日志写入 + 容器创建/销毁占满
│
└▶ cri-dockerd / containerd RPC 响应变慢
│
└▶ kubelet 健康检查 ExecSync 全部超时(11:09-11:10)
│
└▶ 容器被反复重启 → 更多日志 → 更多 CNI → 正反馈爆炸
│
└▶ CPU/IO 完全饱和 → sshd 无法调度/接受连接
│
└▶ 11:10 之后内核日志停止 → 系统"假死"
│
└▶ 你手动重启 → 仓库 502 未修复
→ 12:23 再次复现
一句话总结:镜像仓库 502 是导火索,cri-dockerd 的日志无法轮转是放大器,容器运行时瘫痪是最终死因。