适用场景
本文适用于 Kubernetes 中业务 Pod 间歇性进入 CrashLoopBackOff、RestartCount 持续增长、服务短时间不可用,但应用日志里又看不到明确业务异常的场景。常见于 Spring Boot、Django、Go HTTP 服务、Node.js 服务等 Web 应用,尤其是刚接入健康检查、上线慢启动逻辑、数据库连接初始化较慢、或者高峰期 CPU 被打满之后。
这类问题的关键点是:livenessProbe 不是流量摘除工具,而是“进程是否需要被重启”的判断条件。如果探针配置得太激进,Kubelet 会把一个仍在恢复、仍可继续服务的进程反复杀掉,最终把短暂抖动放大成持续故障。
现象描述
线上常见现象包括:
kubectl get pod看到 Pod 状态反复在Running、Error、CrashLoopBackOff之间切换。RESTARTS持续增长,但应用日志没有 panic、异常退出或 OOM 记录。- 发布后前几分钟频繁重启,等待一段时间后偶尔恢复。
- 高峰期接口延迟升高后,部分 Pod 被连续重启,反而导致剩余副本压力更大。
kubectl describe pod事件中出现Liveness probe failed、Killing container。
示例:
kubectl -n prod get pod -l app=order-api -o wide
输出中重点看:
NAME READY STATUS RESTARTS AGE
order-api-6d8b9d9d7c-x7g2m 0/1 CrashLoopBackOff 12 18m
order-api-6d8b9d9d7c-pq4sm 1/1 Running 7 18m
如果多个副本同时出现重启,先不要急着重建 Deployment,要先确认重启原因是不是探针失败。
可能原因
常见原因可以分成几类:
initialDelaySeconds太短,应用还没完成启动和预热就开始检查。timeoutSeconds太短,健康检查接口偶发超过 1 秒就被判定失败。failureThreshold太低,短暂 GC、CPU 抖动、数据库慢连接被快速放大成重启。livenessProbe检查了依赖项,例如数据库、Redis、第三方接口,依赖抖动时误杀应用。- 健康检查接口本身太重,执行了 SQL、远程请求、复杂聚合或锁竞争。
- 没有配置
startupProbe,慢启动应用只能依赖 liveness 的延迟参数硬扛。 - CPU limit 太小导致容器被 throttling,健康检查线程无法及时响应。
排查思路
排查顺序建议如下:
- 先确认容器是被 Kubelet 杀掉,还是应用自己退出。
- 查看 Pod 事件,定位是否存在
Liveness probe failed。 - 查看上一次容器日志,确认被杀前应用状态。
- 检查探针配置,重点看延迟、超时、失败阈值和检查路径。
- 从 Pod 内部直接访问健康检查接口,确认接口耗时和返回码。
- 结合资源指标判断是否存在 CPU throttling、内存压力或启动慢。
- 区分
startupProbe、readinessProbe、livenessProbe的职责后再调整。
常用命令
1. 查看 Pod 重启情况
kubectl -n prod get pod -l app=order-api -o wide
关键字段:
READY:是否已经加入 Service 后端。STATUS:是否出现CrashLoopBackOff、Error、Running等状态切换。RESTARTS:容器重启次数,持续增长说明问题仍在发生。AGE:如果 Pod 刚创建就重启多次,优先怀疑启动阶段探针配置。
也可以按重启次数排序:
kubectl -n prod get pod --sort-by='.status.containerStatuses[0].restartCount'
2. 查看 Pod 事件
kubectl -n prod describe pod order-api-6d8b9d9d7c-x7g2m
重点看 Events:
Warning Unhealthy 2m10s kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing 2m10s kubelet Container order-api failed liveness probe, will be restarted
如果看到 failed liveness probe, will be restarted,说明容器不是自己崩溃,而是被 Kubelet 根据 liveness 结果重启。
3. 查看上一次容器日志
kubectl -n prod logs order-api-6d8b9d9d7c-x7g2m --previous --tail=200
--previous 用来查看上一个已退出容器实例的日志。重点搜索:
- 应用是否主动退出,例如
panic、SystemExit、Fatal。 - 是否有 OOM、连接池耗尽、数据库连接失败。
- 被杀前是否仍在处理请求,只是响应变慢。
- 是否刚完成启动就被探针打断。
如果日志最后没有明显异常,而 Pod 事件显示 liveness 失败,问题很可能在探针策略或健康检查接口本身。
4. 查看 Deployment 中的探针配置
kubectl -n prod get deploy order-api -o yaml
重点看:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
字段含义:
initialDelaySeconds:容器启动后延迟多久开始探测。periodSeconds:每隔多久探测一次。timeoutSeconds:单次探测超时时间。failureThreshold:连续失败多少次后判定失败。successThreshold:连续成功多少次后恢复成功,liveness 必须为 1。
上面这个配置的问题是:应用启动 10 秒后就开始 liveness,单次超时只有 1 秒,连续 3 次失败就会重启。对于启动慢、GC 明显或高峰期 CPU 紧张的服务,这个配置很容易误杀。
5. 在 Pod 内测试健康检查接口
kubectl -n prod exec -it order-api-6d8b9d9d7c-pq4sm -- sh
进入容器后执行:
time wget -S -O- --timeout=3 http://127.0.0.1:8080/health
time wget -S -O- --timeout=3 http://127.0.0.1:8080/ready
重点看:
- HTTP 状态码是否稳定为 2xx。
- 耗时是否经常超过
timeoutSeconds。 /health是否依赖数据库、Redis 或外部接口。- 容器内访问是否和探针配置的端口、路径一致。
如果 /health 偶尔需要 2 到 3 秒,但 timeoutSeconds 只有 1 秒,就会出现业务接口还能恢复、Pod 却被重启的情况。
6. 查看资源压力
kubectl -n prod top pod -l app=order-api
如果部署了 Prometheus,可以进一步看 CPU throttling:
rate(container_cpu_cfs_throttled_periods_total{namespace="prod", pod=~"order-api-.*"}[5m])
还可以看重启趋势:
increase(kube_pod_container_status_restarts_total{namespace="prod", pod=~"order-api-.*"}[15m])
如果重启和 CPU throttling 同时升高,说明探针超时可能只是表象,底层原因是 CPU limit 太紧或高峰期负载超过预期。
定位示例
某订单服务发布后开始间歇性 502。Nginx Ingress 日志显示后端连接偶发失败,业务 Pod 的 RESTARTS 不断增加。
第一步查看 Pod:
kubectl -n prod get pod -l app=order-api
发现 6 个副本中有 3 个副本重启次数超过 10 次。
第二步查看事件:
kubectl -n prod describe pod order-api-6d8b9d9d7c-x7g2m
事件中出现:
Liveness probe failed: Get "http://10.244.12.31:8080/health": context deadline exceeded
Container order-api failed liveness probe, will be restarted
第三步查看上一次日志:
kubectl -n prod logs order-api-6d8b9d9d7c-x7g2m --previous --tail=100
日志显示容器被杀前正在初始化缓存和连接池,没有应用主动退出记录。
第四步检查探针:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
同时发现 /health 会检查 MySQL 和 Redis。高峰期 Redis 偶发慢响应时,/health 超过 1 秒,liveness 连续失败后 Kubelet 重启容器。容器重启又导致副本减少,剩余副本压力上升,形成循环。
修复方案
1. 拆分健康检查职责
推荐把接口拆成两个:
/livez:只判断进程是否活着,不检查外部依赖。/readyz:判断实例是否可以接流量,可以检查关键依赖,但要控制耗时。
示例配置:
startupProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
解释:
startupProbe最多允许启动阶段等待5 * 24 = 120秒,启动成功前 liveness 不会生效。livenessProbe连续 6 次失败才重启,避免短暂抖动导致误杀。readinessProbe失败只会从 Service 后端摘除 Pod,不会重启容器。
2. 调整健康检查接口逻辑
/livez 应尽量轻量:
返回 200:HTTP 服务线程还能响应,请求处理主循环正常。
返回 500:进程内部已经进入不可恢复状态,例如主线程死锁、关键后台线程退出。
不要在 /livez 中做这些事情:
- 查询 MySQL。
- 访问 Redis。
- 请求第三方接口。
- 扫描大目录或读取大文件。
- 等待锁或执行复杂业务逻辑。
/readyz 可以检查依赖,但建议使用短超时和缓存结果。例如每 5 秒后台刷新一次依赖状态,接口只读取内存中的最近结果,避免探针请求本身打爆依赖。
3. 根据启动耗时设置 startupProbe
先用日志或指标确认真实启动耗时:
kubectl -n prod logs deploy/order-api --since=30m | grep -E "Started|listening|ready"
如果应用冷启动常见耗时为 60 秒,建议给 startupProbe 留出 2 倍左右缓冲。例如:
startupProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
这样应用有 120 秒启动窗口,不需要把 liveness 的 initialDelaySeconds 配得很大。
4. 资源不足时不要只改探针
如果 CPU throttling 明显,需要同时调整资源配置:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
探针超时不是永远通过加大 timeoutSeconds 解决。若服务本身已经因为 CPU 不足无法响应,应该扩容副本、提高 CPU limit、优化慢请求或降低启动阶段负载。
发布和验证
修改 Deployment 后执行:
kubectl -n prod apply -f order-api-deployment.yaml
kubectl -n prod rollout status deploy/order-api
观察新 Pod:
kubectl -n prod get pod -l app=order-api -w
确认 15 到 30 分钟内重启次数不再增长:
kubectl -n prod get pod -l app=order-api
查看事件中是否还存在 liveness 失败:
kubectl -n prod get events --sort-by='.lastTimestamp' | grep -E "order-api|Liveness|Killing"
如果有监控,建议同时确认:
increase(kube_pod_container_status_restarts_total{namespace="prod", pod=~"order-api-.*"}[30m])
结果应接近 0。还要观察 Ingress 5xx、业务接口 P95/P99 延迟和实例 ready 数量,避免只看 Pod 状态而忽略用户侧影响。
预防措施
- 新服务上线时同时定义
/livez、/readyz,不要一个/health负责所有检查。 - 慢启动服务优先使用
startupProbe,不要依赖很大的initialDelaySeconds。 - liveness 不检查外部依赖,依赖抖动应通过 readiness 摘流,而不是重启进程。
- 健康检查接口必须轻量、稳定、短路径,避免访问慢资源。
- 为
kube_pod_container_status_restarts_total配置告警,重启次数短时间增长要及时处理。 - 监控 CPU throttling 和内存使用,避免资源不足被误判为探针配置问题。
- 发布后观察 Pod 事件,
Liveness probe failed不应被当作普通重启噪声忽略。
总结
Kubernetes 中的 livenessProbe 适合处理进程卡死、不可恢复的内部异常,不适合承载数据库、缓存、第三方接口等依赖健康判断。依赖不可用时,正确动作通常是 readiness 失败并从 Service 后端摘除实例,而不是让 Kubelet 重启容器。
排查这类问题时,先看 kubectl describe pod 里的事件,再看 --previous 日志和探针配置。修复时把启动、存活、就绪三类检查拆开:startupProbe 负责慢启动保护,livenessProbe 负责不可恢复故障重启,readinessProbe 负责是否接收流量。这样才能避免一次短暂抖动被探针放大成连续重启事故。
Discussion
评论