Pod Exit Code 137 深度剖析:区分 OOMKilled 与 Liveness 探针失败
叙事框架现象 → 排查过程 → 根因 → 修复 → 预防问题现象Kubernetes 中 Pod 频繁重启退出码 137。两种场景OOM Killer 触发或 Liveness 探针失败。区分方法kubectl describe pod 查看 Last State Reason 是否为 OOMKilled以及 Events 中是否存在 Liveness probe failed 事件。本文提供完整诊断流程和判断决策树。排查过程第一步查看 Pod 详细信息排查这类问题的首要任务是查看 Pod 的详细信息尤其是状态字段中的 Reason 和 Events 部分。关键命令是kubectl describe pod输出中两个关键信息值得关注。Last State 的 Reason 字段显示为Error而非OOMKilled。这是区分两种问题的核心指标——当 Kubernetes 因 Liveness 探针失败杀死容器时Reason 为Error当容器因超出内存限制被 OOM Killer 杀死时Reason 为OOMKilled。两者的 Exit Code 都是 137SIGKILL但 Reason 字段是唯一的区分依据。Events 中的 Liveness probe failed 记录kubelet 持续报告 “Liveness probe failed: timeout: request took 1.2s”。timeoutSeconds 配置为 1s而健康检查实际耗时 1.2s超出了探针的超时窗口。kubelet 在连续 3 次超时后failureThreshold3判定容器不健康并执行重启。这里有一个常见的认知误区很多人在看到退出码 137 时第一反应就是 OOM。但实际上只要 kubelet 决定杀死容器无论是因为 Liveness 探针失败还是其他原因都会发送 SIGKILL退出码都是 137。必须通过 describe 查看 Reason 字段才能确定。第二步查看崩溃前的应用日志既然确定了是 Liveness 探针失败导致的重启下一个问题是为什么健康检查会超时这需要通过kubectl logs --previous查看容器崩溃前的日志。应用日志暴露了清晰的时间线10:32:15 — GC 被触发剩余空闲内存仅 14MB10:32:15.625 — 健康检查耗时 1203ms已超 threshold1000ms10:32:16.012 — 健康检查端点无响应超过 1.5s10:32:16.200 — Full GC 完成释放了 78MBGC 停顿——特别是 Full GC——是导致健康检查超时的直接原因。JVM 在执行 Full GC 时会触发 Stop-The-World 暂停所有应用线程包括 HTTP 请求处理线程都会暂停。当这个暂停时长超过 Liveness 探针的 timeoutSeconds1s时kubelet 就会判定探针失败。在第二个 Pod 的日志中还能看到更多细节堆内存使用达到了 87.5%112MB/128MB接近 Xmx 上限。这触发了 Allocation Failure GC将对象从新生代晋升到老年代随后老年代占比持续上升至 69.5%最终触发 Full GC。更有趣的是最后一条日志包含了堆栈信息显示在容器终止前也曾触发过 OutOfMemoryError。这说明问题可能是复合型的——既有内存压力导致 GC 停顿引发了 Liveness 超时也间或触发了 OOM 条件。第三步检查资源使用情况接下来需要确认 Pod 的内存压力程度。kubectl top pod和kubectl top node揭示了资源瓶颈两个 Pod 的内存使用分别为 237Mi 和 245Mi而 memory limit 仅为 256Mi。这意味着 Pod 已经使用了超过 92% 的限额内存处于高水位运行状态。在这样的压力下JVM 的 GC 频率和停顿时间都会显著增加。更进一步kubectl describe node k8s-node-02显示该节点的内存分配率已经达到 91.2%总体处于紧张状态。虽然有 14 个 Pod 共享该节点但 payment-service 的内存使用占比仍然很高。第四步检查 Liveness Probe 配置最后一步是审查 Deployment 的 YAML 配置确认 Liveness 探针参数的合理性配置中三个参数直接导致了问题的发生timeoutSeconds: 1— 这是最致命的参数。1s 的超时时间对于 Java 应用来说过于严苛。JVM 在 Full GC 时会有数百毫秒到数秒的 Stop-The-World 停顿此时所有线程包括 HTTP 处理线程都会暂停。当 GC 停顿超过 1s 时健康检查必然超时。periodSeconds: 5— 探针检查频率过高。5s 的间隔意味着每 5 秒就要执行一次 HTTP 请求。在高 GC 压力下频繁的探针请求反而会加剧内存压力——每个探针请求都需要创建 HTTP 连接和响应对象进一步推高堆内存使用。initialDelaySeconds: 10— 启动延迟过短。Java 应用的 Spring Boot 启动通常需要 15-30s10s 的初始延迟意味着应用还没完全启动就要接受健康检查。虽然这个值不会直接导致周期性重启但会加剧启动阶段的失败率。内存配置也存在问题Xmx128MB limit256Mi。JVM 堆外还需分配元空间、线程栈、DirectByteBuffer 等实际 RSS 占用通常为堆的 1.5-2 倍。128MB 堆 堆外开销 ≈ 180-250MB RSS已经非常接近 256Mi 的 limit。只要有流量波动就可能突破限制。根因分析直接原因Liveness 探针 timeoutSeconds 过短timeoutSeconds1s 是针对毫秒级响应的后端服务设计的不适用于 Java 应用。Java 应用的 GC 停顿特别是 Full GC具有不确定性可能在数百毫秒到数秒之间波动。以下是关键数据点JVM 参数 -Xmx128m -Xms128m堆被固定在 128MB没有弹性空间应用实际内存使用237-245Mi包含堆内和堆外内存 limit256Mi使用率 92-96%GC 停顿时间1.2-1.8sFull GC 场景Liveness timeout1s当 GC 停顿超过 1s 时健康检查 HTTP 请求无法在超时窗口内返回。kubelet 连续 3 次检测到超时failureThreshold3后判定容器不健康并发送 SIGKILL。这里有一个容易被忽略的机制Liveness 探针的整个判定周期是periodSeconds × failureThreshold timeoutSeconds。在这个案例中理论最快判定时间为 5×3116s。但实际上每次探针的超时等待是独立的kubelet 每个周期都会等待完整的 timeoutSeconds1s才会放弃所以实际判定周期更接近(51)×318s。这意味着每 18s 就可能触发一次重启。间接原因内存 limit 设置过于紧张256Mi 的 memory limit 对于 Xmx128m 的 Java 应用来说过于紧张。原因在于 JVM 的内存占用不仅限于堆堆内内存128MB由 -Xmx 控制元空间20-50MB类元数据、常量池线程栈每个线程约 1MB默认应用通常有 20-50 个线程 20-50MBCode Cache约 20-30MBJIT 编译后的代码DirectBufferNetty/Kafka 等框架可能额外分配 10-50MB 堆外内存JVM 自身开销GC 相关数据结构、NIO buffers 等约 10-20MB估算下来总 RSS 约为 1283030202010238MB恰好落在实际的 237-245Mi 区间内。256Mi 的 limit 几乎没有余量。当流量高峰来临时堆外内存的瞬时抖动比如 Netty 分配 DirectByteBuffer、GC 期间的临时对象分配就足以突破 limit触发内核 OOM Killer 或导致频繁 GC。累计效应GC 压力 → 堆使用率 → 探针超时的正反馈循环更糟糕的是三个因素形成了一个正反馈循环堆使用率 90%内存 limit 紧张导致堆几乎没有空闲空间每次对象分配几乎都会触发 GCGC 频率增加频繁的 GC 意味着更多 Stop-The-World 停顿探针超时GC 停顿超过 timeoutSeconds 导致 Liveness 探针失败容器重启kubelet 杀死容器Pod 重启启动再压重启后流量重新涌入新 Pod 需要重新处理积压的请求堆使用率在启动后几分钟内再次攀升到 90%重复循环上述过程每 15-20 分钟循环一次这就是为什么 Pod 在 12 分钟内重启了 5 次。每次重启后JVM 需要重新加载类、初始化对象、预热 JIT这段时间的 CPU 和内存开销反而更高进一步加剧了资源压力。综合贡献比例将各因素的贡献比例量化因素贡献比例说明timeoutSeconds1 过短40%最直接触发点但若 GC 不停顿不会超时memory limit256Mi 太紧35%导致高内存使用率→频繁 GC→长停顿periodSeconds5 过于频繁15%加大了探针被 GC 停顿命中的概率JVM 启动预热时间长10%重启后资源消耗更大恶化循环根因是一个多因素叠加的系统性问题而非单一的配置失误。修复方案第一步评估现状在动手修改之前团队先收集了 payment-service 的正常运行基线数据平稳期的堆使用率50-70%约 64-90MB平稳期的 GC 停顿200msYoung GC高峰期的堆使用率85-95%约 109-122MB高峰期的 GC 停顿200ms-1.8s含 Full GC平均请求处理时间50-200ms最大请求处理时间p99.91.5s基于这些数据修复目标定为高峰期堆使用率降至 70% 以下GC 停顿时间控制在 500ms 以内Pod 内存使用留有 30% 以上的余量第二步调整 Liveness 探针参数Liveness 探针的配置需要匹配 Java 应用的实际行为模式而不是追求越快越好timeoutSeconds:1→5periodSeconds:5→15initialDelaySeconds:10→30timeoutSeconds: 1→5给健康检查请求留出足够的等待时间。Java 应用在 Full GC 时可能停顿数百毫秒到 2 秒5s 的超时窗口可以覆盖 99% 的场景。这个值不是越短越好——探针的本意是检测容器是否卡死而不是检测响应速度。periodSeconds: 5→15将检查间隔从 5s 拉长到 15s。降低探针频率有两个好处一是减少探针请求本身对应用造成的负载每次探针请求都需要建立 HTTP 连接、解析请求、返回响应二是降低 GC 停顿恰好命中探针检测窗口的概率。initialDelaySeconds: 10→30Spring Boot 应用从启动到完全就绪通常需要 20-30s加载配置、初始化数据源、注册路由等。30s 的初始延迟确保应用在第一次被检查之前已经完成启动并进入稳定状态。第三步优化资源限制资源限制的调整需要同时考虑业务负载峰值和 JVM 自身的内存模型memory: 256Mi → 512Mi cpu: 500m →1-Xmx128m→-Xmx256mmemory limit 256Mi→512Mi新的限制为应用提供了充足的内存弹性空间。Xmx256m 堆外开销 ≈ 350-400MB 实际占用512Mi 的 limit 留有约 25-35% 的余量能很好地应对流量波动。cpu limit 500m→1之前的 CPU limit 500m 只相当于半个核。这带来了容器 CPU Throttling 的风险——当应用在 GC 或处理高峰时需要更多 CPU 资源时可能会被 cgroup 节流从而延长 GC 停顿时间。将 CPU limit 提升到 1 核减少 CPU Throttling 对 GC 停顿的影响。Xmx 128m→256m堆大小的提升与 memory limit 的提升是配套的。之前 128MB 堆在 256Mi 总限制下占比过高堆外开销吃掉了大部分余量。现在调整后堆占总限制的 50%更合理的分配比例。第四步上线部署修复后的 Deployment 配置变更如下修复后的 YAML关键变更点memory limit: 256Mi → 512Micpu limit: 500m → 1timeoutSeconds: 1 → 5periodSeconds: 5 → 15initialDelaySeconds: 10 → 30验证结果即时指标新版本部署后首先看到的是 Pod 状态的明显改善两个 Pod 在部署后 35 分钟内保持 Running 状态Restart Count 为 0。内存使用稳定在 176-182Mi在 512Mi 的 limit 下使用率约 34-36%CPU 使用率从之前的 300-342m 降到了 143-156m——这看起来矛盾但实际上合理容器不再反复重启JVM 避免了频繁的 Full GC 和类加载开销整体效率更高。与修复前的对比数据指标修复前修复后改善Pod 重启次数每 12 分钟 4-5 次0100%内存使用237-245Mi (92-96%)176-182Mi (34-36%)降幅 25%CPU 使用298-342m143-156m降幅 50%GC 停顿1.2-1.8s200ms降幅 80%持续观察部署后连续观察了 2 小时0-30 分钟Restart Count0稳定运行30-60 分钟经过了一次小流量高峰堆内存使用峰值 320Mi未触发 GC 超时60-120 分钟完全稳定无异常事件期间发生了一次 Full GC停顿时间为 450ms在 5s 的 timeout 窗口内远远安全团队复盘问题解决后团队在复盘会议上总结了本次故障的根因和改进项刘老师总结了三条核心教训资源限制 review 要加入 CI 流程不能只看代码质量Deployment 的资源配置必须经过 review。很多团队只关注代码评审而忽略了 YAML 配置的审查。Liveness 探针参数模板化各团队使用不同的探针参数有些过于激进。建议制定标准模板根据应用类型Java/Go/Node使用不同的默认参数。监控 Pod 重启频率当前告警只针对 Pod 状态变化缺少对某命名空间下 Pod 重启频率的聚合告警。前者只告诉你某个 Pod 挂了后者能告诉你系统层面有问题。避坑建议不要只看 Exit Code 判断 OOM137 和 143 都可能是 OOM 或 Liveness 探针导致的。必须通过kubectl describe pod查看Last State.Reason字段OOMKilled是 OOMError通常是 Liveness 探针失败。只看退出码等于盲人摸象。Liveness 探针 timeoutSeconds 不宜过短对于 Java 应用建议从 5s 起步根据压测数据逐步收窄。timeoutSeconds1s 只适用于毫秒级响应的服务如 Go、静态服务不适用于有 GC 行为的 JVM 应用。GC 停顿是不确定的极端情况下可能达到数秒。memory limit 要给堆外内存留余量JVM 的实际 RSS 堆 元空间 线程栈 Code Cache DirectBuffer JVM 开销。设置 memory limit 时建议在 Xmx 基础上乘以 1.5-2 倍作为下限。例如 Xmx256m 时limit512Mi。periodSeconds 和 failureThreshold 要联动配置kubelet 判定 Pod 不健康的总时长 periodSeconds × failureThreshold timeoutSeconds。这个总时间代表了业务能容忍的不健康窗口。时间太短如 5×3116s会在短暂的 GC 停顿就触发重启时间太长如 30×55155s则会影响故障恢复速度。建议 Spring Boot 应用至少设置 30s 以上的容忍窗口。initialDelaySeconds 要大于应用启动时间Spring Boot 应用的启动时间通常在 15-30s。initialDelaySeconds 应设置为 30s 或通过就绪探针readinessProbe来配合确保应用完全就绪后再接受 Liveness 检查。initialDelaySeconds 设置过短会导致 Pod 在启动阶段就被反复杀死。设置 Pod Disruption BudgetPDB避免滚动更新中断当需要更新 Deployment 部署修复时必须先设置 PDB。如果只设置了 2 个副本而没有 PDB滚动更新可能导致两个 Pod 同时被重建造成服务短暂不可用。结合 readinessProbe 和 livenessProbe 区分健康维度readinessProbe 控制 Pod 是否接收流量失败时从 Service 摘除livenessProbe 控制 Pod 是否需要重启失败时 kubelet 杀死容器。在 GC 停顿期间readinessProbe 超时只是暂时摘除流量而 livenessProbe 超时会导致 Pod 重启。对于 Java 应用建议 readinessProbe 的超时设置比 livenessProbe 更保守。附完整命令清单# 1. 查看 Pod 状态kubectl get pods-npayment kubectl get pods-npayment-owide kubectl get deploy payment-service-npayment# 2. 查看事件kubectl get events-npayment --sort-by.lastTimestampkubectl describe pod payment-service-xxx-npayment# 3. 查看崩溃前日志kubectl logs--previouspayment-service-xxx-npayment kubectl logs--tail50payment-service-xxx-npayment# 4. 查看资源使用kubectltoppod-npayment kubectltopnodekubectl describenodek8s-node-02# 5. 查看 Deployment 配置kubectl get deploy payment-service-npayment-oyaml|grep-A20livenessProbe:kubectl get deploy payment-service-npayment-oyaml|grep-A5resources:# 6. 修复后验证kubectl get pods-npayment kubectltoppod-npayment kubectl describe pod payment-service-xxx-npayment|grepRestart Countkubectl get events-npayment --sort-by.lastTimestamp# 7. 部署修复kubectl apply-fdeployment-fixed.yaml kubectl rollout status deploy payment-service-npayment

相关新闻

最新新闻

日新闻

周新闻

月新闻