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

从 D-state 造成的 high load 到 Erlang 内存分配调优

在 Tubi,我们有个电影/电视剧的 metadata 服务,叫 Content,是一个 Elixir(Erlang) 写的服务。当请求 Tubi 的时候,这个服务会负责把需要的 metadata,比如标题、描述、图片等返回给客户端,是一个比较关键的服务。最近线上因为客户端发送了过多的请求,导致服务器的 (normalized) load 很快地从 0.5 升高到 1.3,同时 latency 升高,最终通过扩容来解决。请求量增加导致 load 升高很正常,... Continue →