Kubernetes 集群节点漂移排查实录
凌晨两点十四分,告警群里连蹦出七条 NodeNotReady。等我打开电脑,其中五个节点已经自己恢复了,剩下两个卡在 NotReady。这种"漂移"式的故障最麻烦——现场自己消失了一半,很容易让人误判成网络抖动然后不了了之。这次我们决定查到底。
第一步:确认 NotReady 的判定路径
NodeNotReady 是 kube-controller-manager 根据 NodeLease 或 node status 心跳超时打上的 Condition。默认 --node-monitor-grace-period 是 40s。所以问题一定是:kubelet 在 40s 内没有成功上报。
先区分两种可能:kubelet 进程挂了,或者进程活着但上报失败。
systemctl status kubelet
journalctl -u kubelet --since "02:10" --until "02:25" | grep -iE "error|timeout|fail"
结果是 kubelet 一直在跑,日志里刷屏的是同一句:
E0728 02:16:03.482911 1284 kubelet_node_status.go:140]
"Failed to register node" node="node-07" err="Post https://10.0.0.3:6443/api/v1/nodes: context deadline exceeded"
第二步:apiserver 是好的
同集群其他节点上报正常,apiserver 的 metrics 里 apiserver_request_duration_seconds 也没有异常抬升。所以不是控制面的问题,是这两个节点到控制面的链路问题。
但 ping 通、telnet 6443 也通。这就有意思了。
第三步:真正的元凶——元数据服务
线索来自 kubelet 日志里另一条不太起眼的记录:Failed to get cloud provider instance。我们用的是 in-tree 的云厂商 provider,它在节点启动和定期刷新时会去访问 100.100.100.200(阿里云元数据服务)拉取实例信息。
而 kubelet 的 setNodeStatus 在某些代码路径上是同步调用云 provider 的。当元数据服务返回 429(限流)时,这个调用会重试并阻塞,进而拖住整个心跳上报。
验证:
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" --max-time 3 \
http://100.100.100.200/latest/meta-data/instance-id
sleep 0.2
done
20 次里有 11 次返回 429。而正常节点是 20/20 的 200。
为什么会限流
回溯变更记录,前一天晚上有个同事上线了一个 DaemonSet,里面用 metadata 接口做实例标签同步,每个 Pod 每 10s 轮询一次,而且没有做本地缓存。节点数 × Pod 数一乘,元数据服务的 QPS 直接冲到了配额上限。
元数据服务的限流是按实例维度还是按 VPC 维度,不同云厂商实现不一样;这次的表现是整个可用区里的部分实例一起被限,说明有共享的配额池。
处理与规避
- 立即把那个 DaemonSet 的轮询间隔从 10s 改成 5min,并加了进程内缓存——告警随即停止。
- 中期:给节点打上静态标签,避免运行时反复查元数据。能用
--node-labels解决的就不要动态拉。 - 长期:升级到 external cloud-controller-manager,把云 provider 的逻辑从 kubelet 进程里摘出去。这样即使 provider 卡住,也不会影响 kubelet 的心跳。
复盘里最该记住的一条
不是元数据服务限流这个具体结论,而是"部分节点故障 + 部分节点正常"这个模式本身就指向共享依赖。当时我们花了将近四十分钟在检查这两个节点的网络和磁盘上,方向从一开始就偏了。下次遇到类似形态,第一件事应该是列出"这些节点和正常节点之间,共享了什么"。