k8s pod unhealthy 后不能正常重启 debug

之前遇到一个问题,当一个 pod 从 healthy 到 unhealthy 后,经常不能够正常启动。

比如 init delay 是 0,健康检查 period 是 10s,unhealthy threshold 是 3,那么应该在20s内启动即可,但实际发现不到 20s 就会被 terminate。通过观察 kubelet 的 log 发现,Liveness 的 probe 出现了2次就会被 kill:

Apr 19 02:28:32 ip-xxx kubelet[2449]: I0419 02:28:32.999475    2449 prober.go:111] Liveness probe for "xxx(21431e82-42fa-11e8-a986-02683fc2eb42):backend-xxx" failed (failure): Get http://ip:8080/status: dial tcp ip:8080: getsockopt: connection refused
Apr 19 02:28:44 ip-xxx kubelet[2449]: I0419 02:28:44.005553    2449 prober.go:111] Liveness probe for "xxx(21431e82-42fa-11e8-a986-02683fc2eb42):xxx" failed (failure): Get http://ip:8080/status: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
Apr 19 02:28:44 ip-xxx kubelet[2449]: I0419 02:28:44.310592    2449 kuberuntime_manager.go:550] Container "xxx" ({"docker" "5bea0c1cb416247a6e91649049e79d2214be9ef18d34e17948cc663e7003291c"}) of pod xxx(21431e82-42fa-11e8-a986-02683fc2eb42): Container failed liveness probe.. Container will be killed and recreated.

最终通过阅读 kubernetes 的代码发现,是因为 prober.worker 的 resultRun 在一次失败后,被 reset 为一个错误的值(应该为 0,但实际为 1),导致新的 pod 起来后,resultRun 已经是 1 了,那么当 liveness 失败 n-1 次就会把 pod kill 掉了。修复在 https://github.com/kubernetes/kubernetes/pull/62853

probe 工作流程 #

初始化 #

在 kubelet 初始化时,会初始化 probeManager,在 kubelet 启动时,会在 syncLoop -> syncLoopIteration 中处理 kubetypes.PodUpdate 这个 channel 的消息,当收到 kubetypes.ADD 的事件(也就是是 pod 增加)时,会调用 kubelet.HandlePodAdditions,其中包含了 kl.probeManager.AddPod(pod)。probeManager 就是之前初始化的那个,它会根据 pod 的定义来创建 readiness 或者 liveness worker,每个 container 对应一个 worker,然后就会在一个新的 goroutine 里运行这个 worker。

prober.worker 初始化时,会根据 readiness 或 liveness 设置不同的 initialValue(对应于文档中,readiness 一开始是 failure,liveness 一开始是 success),在 worker.run() 中,会先 sleep probe period 内的随机值(比如周期是 10s,会 sleep 0-10s),然后按 period 进入 ticker 的 loop 中,也就是 worker.doProbe(),并根据 doProbe 的返回值判断是否结束(false 为结束循环)。

doProbe #

doProbe 有几个判断:

如果上边的判断都没有 return,就开始实际的 probe(TCP, HTTP 等),如果 lastResult 和当前 result 相等,会把 resultRun +1,表示连续 resultRun 次得到成功或失败的 result,否则就设置 lastResult,并把 resultRun 置为 1。如果 resultRun 小于 Threshold,则 return true。否则就通知 resultsManager。

如果 worker 是 liveness,且 resultFailure,则会设置 onHoldtrue,因为 container 会重启,所以需要暂停 probe。并 reset resultRun1(这里应该为 0,也就是我们碰到的 bug)。

当 pod 删除时,prober_manager 会调用 RemovePod 来进行回收,比如停止 worker。

probe 的结果如何能让 container 重启 #

resultsManager 的作用是用来通知 container 的事件变化,比如 kubelet 的 livenessManager。

在 kubelet 的 syncLoopIteration 中会对 updates 新的事件(比如 probe worker 发的事件)调用 HandlePodSyncs,最终调到 kl.podWorkers.UpdatePod,在 podWorkers.UpdatePod 中调用 podWorkers.managePodLoop,然后是 syncPodFn。而 syncPodFn 是在哪里初始化的呢?

是在 kubelet 初始化时设置klet.syncPod。于是调用 syncPodFn 就是调用 kubelet 的 syncPod,然后会调用 kl.containerRuntime.SyncPod,也就是 kubeGenericRuntimeManager(在 kubelet 初始化时会创建的) 的 syncPod,在 syncPod 的 computePodActions 中,通过 m.livenessManager.Get(containerStatus.ID) 就得到了 container 的状态。

 
11
Kudos
 
11
Kudos

Now read this

How does Plug.Builder work?

(本文涉及的源码需要有一些对macro的了解才比较容易看懂) 上回说到,我们可以利用Plug,搭配Cowboy这个web server来写一个简单的web app。而实际中我们不可能把所有的处理逻辑都放在一个Plug中,代码既不容易维护,也不能重复利用Plug。 正如Plug的名字(插头)一样,我们可以把一个个的Plug连起来,组成一个功能强大的Plug pipeline。就像你到国外旅行,电源适配器接口不对应,就可以买个转换器(其实就是一个插头),一头插着你的电源,... Continue →