K8s 节点假死故障排查过程复盘

本次排查遵循"确认故障类型 -> 寻找日志断点 -> 定位暴增源头 -> 查看具体报错 -> 验证临终遗言 -> 反向排除 -> 止血验证"的逻辑链条。以下是完整复盘,命令结果仅保留关键部分。

第一阶段:确认故障类型(真死机还是假死?)

排查思路: 检查是否有正常的关机记录,判断是硬件断电/内核崩溃(真死机),还是内核存活但服务卡死(假死)后被人强制重启。

执行命令:

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 的日志无法轮转是放大器,容器运行时瘫痪是最终死因。

相关推荐
一只小鱼儿吖1 小时前
短效代理:高频网络任务的“临时通行证”
网络
我科绝伦(Huanhuan Zhou)11 小时前
Linux 通用压力测试脚本合集:磁盘 + 网络
linux·网络·压力测试
聚美智数12 小时前
网站HTTP状态查询-SEO优化
网络·网络协议·http
无人生还别怕13 小时前
如何安装任意版本docker
运维·docker·容器
treesforest13 小时前
数据包是如何找到回家路的?
网络·智能路由器·nat·ip归属地查询
有毒的教程14 小时前
Linux底层存储路由配
linux·运维·网络
程序员老赵14 小时前
Docker 部署 Redmine:轻松搭建私有化项目管理平台
docker·容器·开源
是真的小外套14 小时前
第9章:网络层安全协议 IPSec 学习笔记
网络·网络协议·tcp/ip
小楼昨夜又东风12615 小时前
一文理清K8s PVC挂载完整流程
容器·rpc·kubernetes