背景
某个 Python 服务的 Pod 频繁重启,大约每小时 2 次。虽然 K8S 的自动重启可以保证服务从外部看依旧很流畅,但是这个问题还是要解决。
排查
K8S Pod 重启的主要原因
- 应用程序崩溃或错误:Pod 内的主进程崩溃或因错误而退出,会触发重启。
- 资源限制:如内存或 CPU 限制超出,导致 Pod 被杀死和重启。
- 健康检查失败:配置的 Liveness 或 Readiness 探针失败,Kubernetes 尝试重启 Pod 以恢复服务。
- 节点问题:底层节点出现硬件或网络问题可能导致 Pod 重新调度。
对应的需要进行的检查就是:
- 查看 sentry 和 error log,看是否有报错没有 catch 住
- 检查资源监控报表,查看是否有资源接近 limit 的临界点后又快速下降的情况,这意味着 Pod 重启了
- 检查健康检查的日志,也可以检查 Pod 的 describe,查看 Pod event,里面会有记录最近的事件,里面一般会记录由于健康检查导致的重启
- 这个也可以在 Pod 的 describe 中检查,节点更换也会记录在 event 中
特殊问题
这次遇到的问题在上面都没有问题,是一个特殊的情况,Google 和 ChatGPT 4.0 都解决不了,看来得自己看下。
还原一下现场
服务是使用 Python 写的,由于 Python 没有真正的多线程,所以这里使用 shell 脚本在一个 Pod 中启动了两个线程来启动两个服务。
- 一个服务是 grpc + worker 的服务,用于处理逻辑;
- 一个线程是一个长连的 websocket,用于处理通知信息。
处理所有 exception
首先先检查了 error log,发现 websocket 出现过断连报错,但是有重连机制,所以并不影响使用。由于没有 catch 的报错会导致 Pod 重启,所以先把所有的 websocket 启动逻辑外包一层 try catch。
发布测试环境后,依旧很稳定地 1 个小时左右就重启 2 次。而且也不再有 error 报错日志了,只有被记录的 info 日志。
查看最后打印的日志
分析 Pod 杀死前最后的 log,发现所有的 Pod 结束前都是打印出了 Stop requested, not retrying 的字样。
- 查看代码,发现逻辑中有一个为了防止 wss 连接查询时间过长的超时设置,机制是 30 次就会断连
- 这段逻辑被复用到了常驻的 wss client 中,导致其在 30 次后就会自己关闭,关闭会导致这个进程的结束,最后导致 Pod 重启
- 30 次信息的处理也和重启频率非常稳定相符
在 wss 长连中去掉了这个逻辑后,重启数量大幅减少,到了每 3 小时一次,但是还是有重启。
再次重复上面的步骤。
这次看到报错:
RecursionError: maximum recursion depth exceeded while calling a Python object这里显示的递归调用层级过高,Python 默认是 999 次递归调用,可以修改这个数字,但是一般情况不推荐。
报错堆栈有提到日志 JSON 解析报错、启动报错等,都检查排除了。最后确定是 wss 断连重启本来的新开线程的方式被人改成了调用自己的 start 函数,导致了递归调用,改回了新开线程的方式后,重启问题彻底解决。
思考
- K8S 中用 shell 启动多个线程的做法,很容易掩盖某个线程单独结束导致的 Pod 重启。这种属于自然地关闭,没有报错,比较难排查。最好还是不要在正式的服务中用这种方式启动,增加了维护的难度。
- 这里的几个问题几乎都是开发为了解决其他问题引入的,从中暴露出来在代码复用、线程管理和循环调用上处理的不足。最好的预防方式是开发和 reviewer 都能注意这些,但目前看成本比较高。
- AI 在这次问题的处理中表现不佳,即便是喂入了各种报错信息,提供了代码源文件,依旧没有找到问题的根源。解决问题可能靠 AI 不行,但是可以在 AI Review 的 prompt 中加入一些这些逻辑的检查。
Comments
Quiet notes for this article.