首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
K8s探针实践:Java服务假死场景下liveness与readiness配置指南
📅 2026/10/16 4:05:08
✍️ 爱科研究院
👁 阅读 3,247
1. “假死”事故复盘为什么K8s探针才是最后一道防线先说一个我印象特别深的场景。某个核心订单系统的 Java 服务在某次大促前突然出现大量超时但 Pod 状态一直显示 Running进程也还在端口也通着。监控面板上 CPU 不高内存也没满可接口就是一直卡住。我们当时的第一反应是重启一下但手动重启了几次每次恢复十几分钟后又开始卡顿。后来查日志才发现是某个外部依赖的连接池被耗尽所有线程都阻塞在获取连接上服务表面活着实际上已经完全丧失了处理能力。这种看着没死、实际上已经完蛋的状态业内一般叫假死Hang。它比崩溃更棘手因为崩溃了 K8s 能通过容器退出码感知到触发重启而假死状态下容器进程还在K8s 默认情况下根本不知道业务已经不可用流量还是会继续打进来用户侧就是持续的报错和超时。我当时就意识到一个问题如果当时配置了有效的 liveness/readiness 探针这个故障根本不需要人介入K8s 会在几十秒内自动把坏实例摘掉并重启。这就是探针的核心价值——它不是锦上添花而是 Java 应用部署到 K8s 之后故障自动恢复能力的基础设施级保障。这篇文章基于我自己在多个项目里的实践把这套东西从头到尾拆开讲两个探针各自管什么、Java 应用怎么实现健康检查接口、YAML 里那些参数到底怎么配才算合理、以及探针本身可能带来的误杀风险。适合正在把 Java 服务往 K8s 上迁移的运维和开发同学也适合已经上了 K8s 但探针配置一直能用就行的朋友。2. liveness 与 readiness探针之间的分工搞混了会上线即雪崩2.1 容器“活着”和“能接流量”是两件完全不同的事很多刚接触 K8s 的同学会把 liveness 和 readiness 混为一谈觉得都是健康检查配一个就行。这是非常危险的误解。**liveness probe存活探针**回答的问题是这个容器要不要被重启如果探针失败Kubelet 会杀掉容器然后根据 Pod 的 restartPolicy 决定是否重建。它面向的是容器内部的致命问题——死锁、内存泄漏导致的不可恢复异常、goroutine 或者线程池彻底耗尽等。readiness probe就绪探针回答的问题是这个 Pod 现在能不能接收流量如果探针失败K8s 会把该 Pod 从 Service 的 Endpoint 列表中摘除但不会重启容器。它面向的是暂时不可用但还有救的状态比如启动过程中依赖还没就绪、缓存还没预热完、或者某个下游依赖暂时连不上但重试一会儿能恢复。用一个生活化的类比liveness 相当于你家的总电闸一旦跳闸整个房间断电需要重新合闸才能恢复readiness 相当于房间门口的请稍候指示灯亮了说明里面还在准备客人先别进来但灯没坏只是暂时不接待。如果只装了总电闸客人进来会撞上还在打扫的客厅如果只装了指示灯房间真的短路起火了也没人管。2.2 Spring Boot Actuator 健康分组为什么推荐拆开Java 应用里现在绝大多数都是 Spring Boot社区推荐的标配做法是通过 Actuator 暴露健康检查端口然后在 K8s 里配置 HTTP 探针。但这里有个关键细节Actuator 的/actuator/health是聚合了所有健康指示器的总状态数据库、Redis、MQ、磁盘空间全在里面。这个端点适合给 README 看但不适合直接拿来同时当 liveness 和 readiness。Spring Boot 从 2.3 开始支持健康分组Health Groups可以单独定义 liveness 和 readiness 两个分组只包含指定的健康指示器。我的习惯是这样的management: endpoint: health: probes: enabled: true group: readiness: include: db, redis, mq, diskSpace liveness: include: ping这里面的逻辑是liveness 分组只做最轻量的存活检查ping指示器readiness 分组才去检查关键依赖和资源情况。为什么这样拆因为 liveness 代表的是这个进程是否已经坏到必须重启它不应该因为数据库抖动就被触发。如果数据库只是瞬时故障readiness 会先把流量摘掉给数据库恢复的时间Pod 本身不用死。但如果你把 liveness 也绑在数据库上数据库一抖动K8s 就疯狂杀 Pod重启之后数据库还没恢复又杀一次最终整个服务的 Pod 全部处于 CrashLoopBackOff那才是真正的雪崩。2.3 错误的合并写法为什么会导致上线即事故我再举一个实际踩过的例子。某团队为了避免探针太多太复杂直接把 liveness 和 readiness 都指向了/actuator/health。当时他们的健康指示器里有 Elasticsearch 连接而 ES 集群在大促当天因为磁盘水位过高变成了 yellow 状态。Actuator 的聚合结果瞬间变为 DOWN于是所有 Pod 的 liveness 同时失败K8s 开始批量重启。结果非常难看一批 Pod 被杀重启过程中流量全部压到剩下的 Pod剩余 Pod 的 ES 健康检查也一样通过不了又被杀整个服务在几分钟内滚雪球式崩溃。如果当时 liveness 用独立的 ping 分组最多只是 readiness 摘流量、ES 恢复后自动接回根本不需要重启任何容器。所以我的第一个强烈建议就是拆组别偷懒。3. Java 应用探针实现对比HTTP、TCP、exec 命令怎么选3.1 HTTP 探针最常用但容易被“表面健康”欺骗HTTP 探针是 Java 应用部署里用得最多的一种方式。它的原理很简单Kubelet 定期向容器的某个 HTTP 端点发请求返回 2xx~3xx 就是健康其他状态码或者超时都算失败。对 Spring Boot 应用来说标准动作是把 Actuator 端口暴露出来然后探针指向/actuator/health/readiness和/actuator/health/liveness。这里有一个容易被忽略的点Actuator 的端口不要和业务端口混用建议单独开一个management.server.port并在 Deployment 里单独加一个管理端口容器这样业务请求量和健康检查请求互相不会干扰。HTTP 探针最大的问题是只能探测到应用层能响应请求这个层面。如果你把探针路径写成了一个不带实际依赖检查的静态接口比如/ping返回 pong那它其实只能证明 Web 容器还活着线程池是否阻塞、连接池是否耗尽完全感知不到。所以我一直强调HTTP 探针的路径本身的设计很关键不能图省事写死一个返回 200 的固定路径。3.2 TCP 探针只能证明端口存活基本等于没有业务检查TCP 探针的做法是让 Kubelet 尝试连接容器的某个 TCP 端口能连上就算健康。这种方式配置最简单但对 Java 应用来说信息量极低。Java 进程一旦起来监听端口就是一直存在的哪怕线程池已经全部阻塞、Full GC 卡了半分钟端口照样能连上。所以 TCP 探针只适合一些非常底层的场景比如验证 sidecar 容器有没有起来或者应用进程还在不在。拿它当主要业务探针基本就是自欺欺人。3.3 exec 命令探针适合做 JVM 级深层检查exec 探针是让 Kubelet 在容器内执行一条命令以命令的退出码判断健康状态。对 Java 应用来说它可以做到比 HTTP 更深层的检查比如直接看 JVM 的线程状态、堆内存使用率。一个常见方案是写一个脚本用jstack抓线程快照检查是否存在长时间 BLOCKED 的线程或者用jstat看老年代使用率是否超过阈值。但 exec 探针也有明显缺点每次探针执行都会在容器内拉起一个子进程频繁执行会额外消耗 CPU 和内存而且容器基础镜像如果没有 JDK 工具脚本会直接报错。我们后来基本只在调试阶段用 exec 探针生产环境还是回归 HTTP。3.4 三种方式的对比与选型探针类型检查层面对 Java 应用的灵敏度配置成本注意事项HTTP应用层 HTTP 响应取决于端点实现低端点必须真实反映业务可用性TCP传输层端口连通低最低只能证明进程在不能证明业务可用exec容器内命令退出码高可深入 JVM中需要 JDK 工具子进程开销要控制我现在的默认组合是readiness 用 HTTP 探针指向 Actuator 的readiness分组liveness 用 HTTP 探针指向 Actuator 的liveness分组。只有在极其特殊的内核级故障场景下才考虑加一个 exec 探针做补充诊断。4. 可直接复用的探针配置参考时序参数、JVM 参数与优雅停机配合4.1 一组可以直接抄的 Deployment 配置第 4 章给你一套我实际在多个项目里用过的配置可以直接参考改造。整套配置的核心思路是liveness 要宽松readiness 要准确启动要留足时间停止要优雅。apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 strategy: rollingUpdate: maxUnavailable: 0 maxSurge: 1 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: terminationGracePeriodSeconds: 60 containers: - name: order-service image: registry.example.com/order-service:1.4.2 ports: - containerPort: 8080 # 业务端口 - containerPort: 8081 # Actuator 管理端口 env: - name: JAVA_OPTS value: -XX:MaxRAMPercentage75.0 -XshowSettings:vm -XX:UseG1GC readinessProbe: httpGet: path: /actuator/health/readiness port: 8081 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health/liveness port: 8081 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3 startupProbe: httpGet: path: /actuator/health/readiness port: 8081 initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 604.2 时序参数的语义initialDelaySeconds、periodSeconds、timeoutSeconds、failureThreshold很多人把探针参数抄下来就完事但不知道每个参数改动的意义这里逐个讲清楚。initialDelaySeconds是容器启动后多少秒才开始第一次探针。Java 应用启动本来就要时间Spring Boot 项目动辄几十秒如果你把它设成 5 秒进程还在初始化类探针就已经开始打请求这会让 Pod 在启动阶段就被误杀。我的建议是结合应用实际启动耗时来定比如应用平均 25 秒起来initialDelaySeconds 给到 30 秒。periodSeconds是探针的执行频率。频率越高发现故障越快但 Kubelet 的压力也越大。readiness 我习惯用 10 秒liveness 用 15 秒既不会太迟钝也不会太密集。timeoutSeconds是单次探针请求的容忍超时。这里有一个 Java 应用常见的坑如果业务代码里有慢查询或者阻塞调用探针请求本身也会被拖住。timeoutSeconds 太小比如 1 秒一个 GC 停顿稍微久一点就会导致探针失败太大比如 10 秒那故障响应速度会被拉长。我在生产环境一般给 3 秒。failureThreshold是连续失败多少次才判定失败。Kubelet 有默认值 3但很多线上环境建议把它调大一点尤其是 liveness。原因很简单单次探针失败可能只是因为网络瞬时抖动或者容器里的 Java 进程有一次较长的 GC 暂停这种偶发情况不应该直接触发重启。但调大之后真实故障的响应时间也会变长需要权衡。注意liveness、readiness、startup 三个探针的语义完全不同。startupProbe 用来判断应用是否启动完成启动完成后自动失效后续交给其他两个探针接管。对启动慢的 Java 应用建议单独配置一个宽松的 startupProbe避免启动阶段被 readiness 或 liveness 反复打爆。4.3 JVM 参数配合别让容器内存限制变成探针误杀推手探针只负责发现病真正避免病还得靠 JVM 参数。Java 应用跑在容器里最经典的坑就是 JVM 不认容器内存限制。在 JDK 8u131 之前JVM 默认看到的可用内存是宿主机内存不是容器 Limits 里配置的内存。比如你给容器限了 2G但宿主机有 64GJVM 会按 64G 去计算默认堆大小结果堆还没涨满容器就被 OOMKilled 了。Pod 里出现 OOMKilled 之后容器重建如果探针参数又比较激进就可能出现刚重启又被 OOM探针还没成功就给 liveness 判了死刑的连环重启。现代 JDK 的处理方式是使用-XX:MaxRAMPercentage这类参数而不是写死-Xmx。我自己习惯给-XX:MaxRAMPercentage75.0这样 JVM 会根据容器内存限制动态分配堆既避免堆太大导致 OOM也避免堆太小浪费资源。叠加容器本身再预留一部分内存给堆外和 JVM 自身才不容易触发容器级 OOM。4.4 终止宽限期探针之外的优雅停机配合探针管的是什么时候重启terminationGracePeriodSeconds 管的是重启的时候怎么走。当 liveness 判定失败Kubelet 会向容器主进程发送 SIGTERM 信号然后等待一段时间超时才强制 SIGKILL。Spring Boot 应用收到 SIGTERM 后应该通过spring.lifecycle.timeout-per-shutdown-phase控制优雅停机时间让在途请求处理完、连接池回收、注册中心摘除。这里有一个经验terminationGracePeriodSeconds 不能小于 readiness 探针的故障发现时间 应用优雅停机所需时间。如果你设置 30 秒应用优雅停机需要 45 秒那么每次重启都会有一部分在途请求被硬中断。我见到的生产配置一般是 60~90 秒配合 Spring Boot 的优雅停机配置整体体验会好很多。5. 探针误杀与自伤那些把应用“健康”探坏的隐蔽场景5.1 liveness 探针依赖慢查询一次雪崩的完整复盘这套配置在纸上很完美落地之后仍然踩了不少坑。我印象最深的一次是我们把一个报表服务的 liveness 探针绑在了自定义的数据库健康检查上。当时想的是如果数据库挂了这个服务活着的意义也不大不如重启。结果数据库确实没挂只是某个大查询把连接池占满了所有健康检查请求排队等数据库连接等不到就超时liveness 判定失败Kubelet 开始重启 Pod。重启之后新的 Pod 还在启动阶段旧 Pod 的数据库连接又被释放了一部分看起来恢复了但流量一进来大查询又跑起来连接池再次占满健康检查又一次超时又被重启。整个过程无限循环。这个案例的教训是liveness 只应该关心进程自身的健康状态不要把它和外部依赖强绑定。外部依赖的健康状态应该由 readiness 负责因为它出问题时的正确反应是摘流量、保留进程、等待恢复。5.2 readiness 探针依赖第三方接口导致抖动还有一个场景某支付回调服务的 readiness 探针里包含了对第三方支付平台的连通性检查。第三方平台偶尔会有几百毫秒的响应延迟探针配置的 timeoutSeconds 又是 2 秒于是 periodically readiness 失败Pod 被摘流量然后马上又恢复又被加回流量Pod 状态反复横跳。从监控看就是一条条锯齿状的红线服务整体吞吐量被拖低用户还有零星报错。正确的做法是把这类外部依赖检查挪到业务代码的降级逻辑里而不是放进 readiness 的 Group 里。readiness 关注的是本应用是否准备好服务而不是整个世界是否正常。世界出问题应用应该通过熔断、降级去适应而不是把自己从 Service 里摘掉。5.3 资源配额悬崖读写延迟突刺和探针超时Java 应用在高并发下经常出现 GC 停顿尤其是没有合理设置堆大小的时候Full GC 可能停顿好几秒。在这几秒里HTTP 探针请求也会被阻塞在事件循环或者线程池里超过 timeoutSeconds 就失败。如果 failureThreshold 又比较小偶发性 Full GC 就会变成 liveness 的连续失败最后杀掉的不是故障 Pod而是一个只是因为 GC 暂停而来不及回答的 Pod。我后来调整的方案是一方面把堆内存相关参数配好减少 Full GC另一方面把 liveness 的 failureThreshold 调到 3timeoutSeconds 放到 3 秒。同时把 liveness 的端点做得足够轻只做存活判断不走任何事务性逻辑。5.4 定位误杀问题的完整排查链路当你发现 Pod 频繁被重启、但业务日志里没有明显错误时不要急着调探针参数。按下面的链路一步步排查第一步确认重启原因。通过kubectl describe pod pod-name看 Events通常能看到Liveness probe failed或者Readiness probe failed的记录。如果看到OOMKilled那就是资源问题不是探针本身的问题。第二步确认探针失败是不是由 JVM 停顿引发。去看监控里的 GC 耗时曲线。如果 Full GC 频率高、耗时长就先去优化 JVM 参数。第三步确认是不是依赖服务抖动。查服务日志里健康检查端点的响应时间看有没有第三方超时。这能把问题定位到 readiness 分组的哪些健康指示器上。第四步确认是不是探针参数过严。用时间戳对齐探针失败时间点、GC 停顿时间点、业务日志异常时间点三者如果高度重合就是典型的误杀。这套链路我们后来沉淀成了标准排查文档每次出现异常重启先走一遍再动手改配置。6. 探针生效性验证与监控配套让自动恢复能力可观测6.1 用故障注入验证探针确实“干事”配置写得再好不验证等于白配。我推荐在测试环境做一次故障注入演练步骤很简单向 Pod 内执行kubectl exec把应用的线程池直接耗尽。可以用一段测试代码循环去获取不释放的连接模拟之前那种假死场景。观察 K8s 事件流确认 readiness 在几十秒内变红流量被摘除。继续观察确认 liveness 最终失败Pod 被回收新 Pod 自动创建。全程记录从故障发生到新 Pod 正常的时长作为自动恢复能力的基准线。对于 OOM 场景可以在容器内启动一个不断分配内存的进程把容器内存打爆。对于启动卡死场景可以在应用里加一个环境变量开关让启动流程走到一半就 block。每一次演练都记录下来形成一份故障恢复演练清单。6.2 从事件和指标验证自动恢复过程自动恢复之后监控上要能看到完整的证据链不然恢复过程对运维来说就是个黑盒。K8s 本身会记录关于探针失败和重启的事件kubectl get events可以查到Liveness probe failed、Restarting container之类的记录。如果想留作事后分析可以用工具把事件流采集到日志系统里。从指标层面建议以 Pod 重启次数、探针失败次数、服务可用 Endpoint 数三个维度做仪表盘。Pod 重启次数上升说明 liveness 在起作用Endpoint 数波动说明 readiness 在起作用如果两者频繁同时波动就要回到第 5 章的排查链路去看是不是探针配错了。6.3 围绕探针的告警给自动恢复留出阈值空间自动恢复并不意味着不需要告警。我的经验是告警阈值不要和探针的 failureThreshold 同步。探针允许 3 次失败才重启那告警也应该至少连续观察到 3 次以上失败才发出否则探针本身还没触发告警先刷屏了。更合理的做法是设置短时间、多次重启的告警。比如 10 分钟内 Pod 重启次数超过 3 次才触发通知因为单次重启很可能是可恢复的瞬时故障而连续快速重启往往意味着探针配置或者应用本身有问题需要人工介入。另外可以加一个滚动窗口内无可用 Endpoint的监控指标。如果一个 Service 背后所有的 Pod readiness 都失败说明应用处于完全不可服务状态这个告警的级别要比单个 Pod 探针失败高得多。配套的恢复操作也更快直接对工作负载执行 rollout restart比等 K8s 一颗一颗地靠 liveness 慢慢重启要高效得多。我自己的习惯是每次上线前除了检查镜像和资源配额还会专门花 10 分钟过一遍探针配置和监控仪表盘链路。这套组合拳跑过几次故障演练之后自动恢复就不再是一个理论上应该有效的配置而是一套真正能兜底的机制。你上手配置的时候如果条件允许最好也把同样的事情做一遍——先让它在测试环境里故障一次再看它自己爬起来。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/16 4:05:08
Odoo远程连接PostgreSQL配置指南:从监听、认证到排错
2026/10/16 4:05:08
Typeoff深度测评:免费平替Typeless,双平台体验与功能对比
2026/10/16 4:05:08
Autopsy数字取证实战:从镜像分析到证据报告全流程
2026/10/16 5:10:12
uiautomatorviewer报错排查:从ADB到UI dump的完整思路
2026/10/16 5:10:12
Java实现彩色PDF转黑白二值化:PDFBox渲染与图片处理实战
2026/10/16 5:10:12
标签解耦框架:如何让显著目标检测同时保住定位、结构与边界
2026/10/16 5:10:12
iptables实战:局域网共享上网与端口映射配置全解析
2026/10/16 5:10:12
学术论文投稿全攻略:从选刊到审稿意见回复的实战指南
2026/10/16 5:05:12
四轴飞控硬件设计全解析:STM32原理图到PCB实战避坑指南
2026/10/16 0:04:49
HCIA-AI V3.5 模拟题库从零搭建:知识点标签、组卷算法与错题分析
2026/10/16 0:04:49
游戏项目Shader清理实战:静态扫描与动态验证双轨并行
2026/10/16 0:04:49
比特币白皮书深度拆解:从双重支付到共识机制的核心设计
2026/10/16 1:17:36
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/16 1:17:35
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/16 1:17:35
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/16 1:17:35
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/15 5:07:59
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/15 8:10:01
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)