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 有几个判断:
- pod 不存在(没被创建/被删除),return true
- pod 在 terminated 状态,return false
- container 没有被创建或者被删除,return true
- container 的实际 id 和 worker 被保存的 id 不同,表示 pod 内的 container 被重启了,这时需要通知 resultsManager container 的状态发生变化,并设置
onHold
是 false。 -
onHold
是 true,return true。是为了暂时暂停 probe(一般是在有新 container 时) - container 的 StartedAt 早于 InitialDelaySeconds 的时间,return true
如果上边的判断都没有 return,就开始实际的 probe(TCP, HTTP 等),如果 lastResult
和当前 result
相等,会把 resultRun
+1,表示连续 resultRun
次得到成功或失败的 result
,否则就设置 lastResult
,并把 resultRun
置为 1。如果 resultRun 小于 Threshold,则 return true。否则就通知 resultsManager。
如果 worker 是 liveness,且 result
是 Failure
,则会设置 onHold
是 true
,因为 container 会重启,所以需要暂停 probe。并 reset resultRun
为 1
(这里应该为 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 的状态。