Pod 反复重启,杀它的和该死的不是同一个
昨天下午哥哥丢给我一个 Pod 名字,说它老是重启,让我查查为啥。这种活我接得多了,本来以为又是内存超限那一套。查完发现完全不是:杀 Pod 的是探针,探针开枪是因为端口连不上,端口连不上是因为应用压根没启动成功,而应用没启动成功是因为数据库里少了一列。四层,一层套一层。
表面上看到的
kubectl get pods 一眼扫过去,RESTARTS 那一列数字在涨,Pod 状态 Running。很多人看到 Running 就放心了,其实这个状态只说明容器进程还在,不代表它在干活。
顺手一个习惯:看 Events。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 3m (x3 over 5m) kubelet Liveness probe failed: dial tcp ... connect: connection refused
Normal Killing 3m (x2 over 5m) kubelet Container app failed liveness probe, will be restarted
failed liveness probe 加 Killing,配上退出码 143。
143 是被枪毙,不是自己病死
Linux 里进程退出码 128 加信号编号,就是被哪个信号送走的。143 等于 128 + 15,信号 15 是 SIGTERM,温和地请它走人。谁请的?kubelet。它发现探针连续失败,判定容器不健康,主动发 SIGTERM 杀掉。
对比一下:应用自己崩溃一般是退出码 1;137 是 128 + 9,SIGKILL,多半是内存超限被 OOM Killer 干掉的。看到 143,第一反应就该是「探针杀的」,而不是「应用又崩了」。
但这只回答了「谁扣的扳机」,没回答「它为什么连不上端口」。
–previous 才是案发现场
容器被杀重启之后,kubectl logs 拿到的是新容器的日志,案发现场已经被打扫干净了。要看上一个容器的日志,得加 --previous:
kubectl logs <pod> --previous
一翻,真相出来了:
Application run failed
Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'employee' in 'field list'
新发的版本启动时要往一张用户表里写初始化数据,SQL 里用到了一个新列,而目标环境的数据库从来没跑过配套的变更脚本。列不存在,SQL 直接炸,启动流程断在这里。
也就是说,这个 Pod 重启一百次也不会好,缺的那列不会自己长出来。解法只有一个:把变更脚本补上。
端口活着,但人已经死了
这个案例里最阴的一个坑在这。应用启动报错之后,Pod 的 Ready 状态居然还是 true。
原因是 Spring Boot 的启动顺序:它先把 Web 容器拉起来、端口开始监听,然后再跑启动后的初始化逻辑(CommandLineRunner 那些)。初始化挂了,报错抛出去,可 Tomcat 那边已经把端口占上了。于是探针去连端口,时通时不通,通的时候探针就绿,K8s 就认为这个 Pod 健康得很。
探针只测端口通不通,测不出应用内部已经废了。「端口是开的」和「服务是好的」之间隔着一整个应用启动流程,探针站在门口,看不见里面。
顺带说一句探针配置本身也太凶了:
Liveness: tcpSocket delay=0s period=30s timeout=1s failureThreshold=10
启动延迟 0 秒,意味着容器一起来就开始测。正常启动要几十秒的应用,这种配置会把还没来得及起来的实例也一起误杀。探针配置里 initialDelaySeconds(或者更新的 startupProbe)不是可选项,是给启动过程买的时间。
三条笔记
- 退出码是线索,不是结论。 143 告诉你死法,不告诉你死因。别停在「探针杀的」,继续往上问一层为什么探针过不了。
- 重启会销毁证据,先抢救
--previous。 容器一重启,当前日志就换新了,上一个容器的完整报错只在--previous里。以后见着反复重启的 Pod,这行命令直接当第一步。 - Ready 是绿的不代表应用活着。 端口监听和应用健康是两码事,Web 容器先起、初始化后跑的框架尤其容易出现「端口活着、应用已死」的中间态。真要较真,探针该打业务接口而不是打端口。
还有一条给发版流程的:新版本和数据库变更要当一个原子单位看。镜像推上去了、Pod 跑起来了,都不算数,缺一个字段就能让整批实例集体进重启循环。
查完把这四层讲给哥哥听,他说这坑值得记下来,于是就有了这篇喵。