kubernetes – kubelet失联导致POD持续not ready

这周线上k8s踩到bug一枚,下面是整个问题的分析过程。

问题

重启kubelet节点的网络,导致node上的POD全部处于Ready: False状态,即无法对外提供服务。

线上k8s代码版本是1.15.5,一开始看的是1.15最新小版本,代码结构完全不同,查了半天都没查明白,汗。。。

node污点

node平时是没有污点的,一旦出现失联就会涉及到2种污点:

  • node.kubernetes.io/not-ready: Node is not ready. This corresponds to the NodeCondition Ready being “False”.

  • node.kubernetes.io/unreachable: Node is unreachable from the node controller. This corresponds to the NodeCondition Ready being “Unknown”.

更新node污点的同时,也会伴随更新Node的对应Condition(这里只关注NodeReady):

简单理解,就是node.kubernetes.io/not-ready会同时更新NodeReady Condition为False,node.kubernetes.io/unreachable会同时更新NodeReady Condition为Unknown,效果都是NoExecute也就是立即驱逐node上的POD。

node lifecycle controller

Controller Manager中的node lifecycle controller会周期性检查所有node的状态,默认间隔是5秒。

lifecycle已经监听了etcd中node的变化并同步其状态到内存,上述定时器定期扫描内存中的node信息,做出动作。

从函数注释提供了一些关键信息:

简单总结一下:

1)lifecycle内存中实时同步着etcd的node数据,是kubelet去更新的。

2)正常来说,kubelet会定期更新node信息,这样lifecycle会被动同步到node变化(理解为心跳)。

3)但是如果kubelet有一阵子没有上报node信息,那么lifecyle周期性检测的时候就会标记node为unknown状态。

4)如果unknown状态持续很久,那么lifecycle就会对node生效污点,驱逐上面的pod。

那么,node更新时间>多久才算unknown呢?从lifecycle代码里可以看到一段注释,


这个值默认是40秒。

当kubelet所在node网络重启时,在apiserver日志中看到了断连日志:


时间点是02:12:57。

过了一段时间,在cm日志(node lifecycle controller)中,我们看到lifecycle对node的状态进行了变更:


时间点是02:13:17,距离02:12:57正好40秒,也就说明lifecycle在扫描node时发现node心跳停止了40秒。

超过40秒没有心跳(Kubelet stopped posting node status)时,tryUpdateNodeHealth方法会对node更新所有Condition字段更新为Unknown:

经过更新后,currentReadyCondition就是Unknown,即node失联状态。

此后,判断node状态从ready: True迁移到了ready非True(实际是unknown),则对失联node进行一波处理:

1)创建了NodeNotReady的Event

2)更新node上所有POD的Condition Ready为False,因此看到如下日志:


本轮检测完成后5秒,新一轮的monitor检测开始,因此当前node状态已经是unknown了,所以进入了另外一个分支,对node进行污点标记:


即标记了node.kubernetes.io/not-ready:NoExecute,希望驱逐POD立即离开node。

此时,对应的日志输出也符合预期:

时间点是02:13:32,即上次monitor周期02:13:17之后的大约5秒。

此后5秒,monitor检查周期再次拉起,检查到kubelet已经更新过node心跳时间(被动同步来的),因此开始移除污点:

时间点是02:13:77,代码是:

1)在markNodeAsReachable就是删除node上的污点,打印打印了healthy again的日志。

2)但是并没有对上面的POD恢复ready: True。

进一步分析

按道理node曾经上过污点,虽然又取消了,但是kubelet应该可以收到这次历史变动,应该会对POD做驱逐动作。

其实只要Kubelet驱逐了POD,那么就不会出现这种POD处于ready:False的情况了,可为什么Kubelet没有驱逐呢?

我看了一下pod的tolerations,发现pod对node的上述noExecute污点有容忍时间:


也就是说能容忍污点5分钟,因此导致了POD没有被驱逐,这就是整个事情的来龙去脉了。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~