资讯动态

Kubernetes探针完全指南:liveness/readiness/startup实战与避坑

发布时间:2026/10/16 13:16:38 来源:尧图企业网站定制
做 Kubernetes 运维久了你会慢慢发现一个真相大部分服务“看起来正常”实际上早就病入膏肓了。容器进程还在端口还通着监控面板上的 CPU、内存都正常可用户体验就是卡顿、报错、超时。这种情况下指望 Kubernetes 自己发现问题基本不可能除非你给它装上一双“眼睛”——探针Probe。今天我想把 livenessProbe 和 readinessProbe 这套东西彻底聊透。不只是把 YAML 参数背一遍而是结合我自己在真实环境里踩过的坑、调过的参、救过的命讲清楚探针的工作原理、参数选型逻辑以及怎么靠它把服务可用性从 99% 拉到 99.99%。内容适合刚入门 K8s 的开发者也更适合那些被“诡异故障”折磨过、想真正理解探针机制的运维同学。1. 探针到底是什么先从三个故障场景说起1.1 没有探针时服务是怎么“假死”的先别急着看配置。我在讲探针原理之前习惯先让团队想一个问题你部署了一个 Web 服务容器起来之后Kubernetes 怎么知道这个服务是“活的”还是“死的”答案很残酷——默认情况下它不知道。Kubernetes 只知道 Pod 里的容器进程是否还在。只要进程没退出它就认为这个容器是健康的哪怕这个进程已经陷入死锁、内存溢出、或者内部线程池全部耗尽对外请求全部卡住。这种情况就是典型的“假死”进程活着服务死了。我见过一个真实案例。某个内部系统用的是某个第三方 HTTP 框架运行几小时后会出现连接泄漏端口仍然监听curl也能握手成功但请求永远不会返回。没有探针的时候K8s 对此毫无感知流量照常打进来用户请求全部超时最后靠人工发现重启才恢复。整个过程里容器一直处于 Running 状态重启策略根本触发不了。这个例子非常典型地说明了进程存活不等于服务健康。探针的存在就是把“容器活着”这个粗粒度判断细化成“服务是否真正可用”的精细判断。1.2 三种探针的分工各管一段生命周期Kubernetes 提供了三种探针很多人一开始记不住它们有什么区别其实用生活里开餐馆来类比一下就通了。livenessProbe存活探针相当于餐厅的“食品安全检查”。检查的是这个店还活着吗如果发现厨师晕倒了进程卡死直接关门整顿重启容器。readinessProbe就绪探针相当于餐厅门口的“营业中”牌子。牌子没亮客人就不该进来流量不转发亮了代表厨师准备好了、菜能做了可以接待客人。startupProbe启动探针相当于餐厅开业的“装修期”。新店装修没完成之前不让客人进来也不催厨师干活等装修完了再正常营业。放在 K8s 的语境里更精确一点探针类型判断依据失效后动作典型场景livenessProbe容器是否还“活着”杀掉容器按重启策略重建死锁、内存泄漏、goroutine 泄漏readinessProbe服务是否“可用”摘除 Service 端点流量不转发依赖未就绪、缓存未加载、正在过载startupProbe进程是否完成初始化存活/就绪探针暂停工作启动耗时长的 Java 应用、AI 模型加载特别强调一点readinessProbe 失败不会重启容器只会把 Pod 从 Service 的 Endpoints 里摘掉。这是很多初学者最容易搞混的地方。它俩职责完全不同一个是“要不要杀掉你”另一个是“要不要给你流量”。1.3 三种检测机制exec、HTTP、TCP 怎么选探针要判断“健康与否”总得有检测手段。Kubernetes 提供了三种机制HTTP 请求httpGet向容器的某个 HTTP 端口发 GET 请求只要返回码在 200 到 399 之间就判定为健康。这是互联网应用最常用的方式因为它检测的是“应用层”能真实反映业务是否就绪。TCP 连接tcpSocket尝试建立 TCP 连接成功就算健康。适合那些没有 HTTP 接口的服务比如数据库、消息队列、缓存中间件。但它最多只能检测到“端口能连上”端口能连不代表业务逻辑正常这在后面会成为坑。Exec 命令exec在容器内执行一条命令以退出码是否为 0 作为判断标准。适合那些既不开 HTTP 端口、又不监听 TCP 的服务比如定时任务容器或者进程内部有自检接口的服务端。我自己的选型经验是凡是能做 HTTP 健康检查的一律优先用 httpGet。TCP 检查虽然简单但它看不到应用内部状态Exec 检查灵活但每次执行都会产生进程开销而且容器里不一定有完整的 shell 环境排查问题时会很难受。2. 探针参数深度拆解每个时间含义都要懂2.1 四个时间参数一次完整的探针检查生命周期探针的 YAML 配置里有一堆Seconds很多人只是照抄默认值出了问题根本不知道怎么调。这里我用一条时间线把它们串起来。假设场景是这样的livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3容器启动后探针不会立刻开始工作。K8s 会先等initialDelaySeconds初始延迟15 秒给应用留出初始化时间。这个延迟结束后探针开始第一次检查之后每periodSeconds周期10 秒执行一次。每次请求发出后K8s 最多等待timeoutSeconds超时2 秒。如果请求在 2 秒内没返回这次检查判定为失败。注意一次失败并不会立即触发动作而是连续失败达到failureThreshold失败阈值3 次才会执行探针对应的动作——对于 livenessProbe 来说就是重启容器。对应地每个探针还有一个successThreshold成功阈值默认值是 1意思是连续成功 1 次就算恢复健康。一般保持默认就好不需要改。来看一个具体的时间线计算。假设容器启动时刻T0initialDelaySeconds 15那么第一次探针在 T015 发起periodSeconds 10第二次在 T025第三次在 T035假设从 T015 开始连续失败第一次失败T015等待 2 秒超时后判定失败第二次失败T025第三次失败T035在 T035 这次失败之后kubelet 触发 kill 操作容器重启这也就是说从“探测开始”到“容器被杀”最坏情况下需要(failureThreshold - 1) * periodSeconds timeoutSeconds的时间。代入数值就是(3-1)*10 2 22 秒加上 initialDelaySeconds 的 15 秒容器从启动到被强制重启最快路径约 37 秒。2.2 为什么 initialDelaySeconds 是最容易配错的参数实话说我排查过大量探针导致的连环重启事故八成都能归结到 initialDelaySeconds 没配好。配小了应用还在做数据库连接池初始化、加载配置文件、建立缓存连接探针就来了。区区 2 秒的 timeout应用内一个慢查询就把健康检查拖超时连续 3 次之后容器被重启。重启之后又是一轮初始化又超时又重启……形成经典的 CrashLoopBackOff。配大了虽然不会有重启风险但 readinessProbe 的 initialDelaySeconds 如果设置过大会导致服务启动后迟迟不对外提供流量。我见过一个团队配了 120 秒结果应用其实 20 秒就就绪了白白浪费了 100 秒的等待时间。发布高峰期这种浪费会直接影响扩容和滚动更新的效率。正确做法不要靠猜。在本地环境里先用kubectl logs观察应用日志记录从进程启动到“对外服务就绪”的实际耗时在这个耗时基础上增加 20% 到 30% 的安全余量。Java 应用尤其注意JVM 启动、Spring 容器加载、连接池预热的耗时可能远超预期。2.3 成功阈值、失败阈值到底该怎么定failureThreshold 这个参数同样值得仔细想。它本质是一个“容忍度”设计目的是防止因为网络抖动或单次超时导致误杀。设置太小比如 1只要健康检查一次超时立刻重启容器。网络偶发抖动、负载瞬时升高都会触发重启导致服务频繁重建。设置太大比如 5 以上容器要连续失败很多次才被重启相当于容忍一个病态服务继续对外提供服务用户的体验已经崩了你却还在 “观察”。正常 HTTP 服务的健康检查接口通常响应很快毫秒级一次检查失败确实是异常信号。我一般把 livenessProbe 的 failureThreshold 设置在 2 到 3 之间readinessProbe 可以稍微宽松一些设置在 3 左右。因为 readinessProbe 失败只是暂时摘流量不应该太“激动”livenessProbe 则要相对灵敏避免让坏实例持续接收流量。另外注意一个隐藏细节livenessProbe 和 readinessProbe 如果配置完全一样就没有意义了。存活探针管的是容器进程层面的“活着”就绪探针管的是业务依赖层面的“就绪”两者检测的内容和频率应该有不同的设计。liveness 可以放宽周期比如 20 秒readiness 可以设得更密比如 5 秒这样才能快速感知依赖故障、及时摘除流量。3. 实战用探针组合拳构建高可用服务3.1 一套标准的 HTTP 探针配置先给出一份完整的 Deployment YAML。这是一个模拟的订单服务内部有/healthz接口会检查数据库连接池、本地缓存状态返回 JSON 格式状态码。apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: containers: - name: order-service image: registry.example.com/order-service:1.4.2 ports: - containerPort: 8080 name: http readinessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 2 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 30 periodSeconds: 15 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3这份配置里有几个细节值得讲。readinessProbe 的 periodSeconds 比 livenessProbe 更短。因为就绪状态变化需要被快速感知比如数据库挂了最好在 5 秒内把流量从故障实例摘掉避免用户请求打到坏实例上。而 liveness 关心的是“容器要不要重启”节奏可以慢一点15 秒足够了。initialDelaySeconds 分别是 10 和 30。这是我在模拟环境里观察日志后定出来的应用启动到 HTTP 端口可访问大约是 8 秒readiness 设 10 秒刚好但业务真正达到“健康”状态要 25 秒左右要预热缓存liveness 设 30 秒是比较安全的。如果你两个探针共用一个健康检查接口但 initialDelaySeconds 没有区分就可能出现“端口通了但内部还没就绪”的尴尬期。httpGet 的 port 字段可以用端口名字。比如port: http引用的是上面定义的容器端口名。这样以后改实际端口号时不需要同步改探针配置减少出错机会。3.2 startupProbe 的正确使用姿势解决启动慢的 Java 应用有些人想不通为什么有了 initialDelaySeconds还要搞一个 startupProbe 出来它是不是多余的原因在于 initialDelaySeconds 是固定的“死等”。你给多了浪费启动时间给少了又会导致存活探针误杀。但很多真实应用启动时间不是稳定的。同样是 Spring Boot 服务冷启动可能要 40 秒热启动只要 20 秒。如果 livenessProbe 的 initialDelaySeconds 按冷启动配置了 45 秒那么在热启动场景下它会延迟探活节奏拖慢发布速度如果按热启动配 20 秒冷启动时 liveness 就会误杀容器。startupProbe 的价值在于它成功之前livenessProbe 和 readinessProbe 全部不生效。而 startupProbe 自己可以用相对宽松的周期和阈值给慢启动应用足够的“宽限期”。看这个配置一个加载模型文件的应用启动可能需要 1 到 2 分钟startupProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 0 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 30 successThreshold: 1periodSeconds: 10乘以failureThreshold: 30意味着 startupProbe 给了应用最多 300 秒的启动时间。一旦 startupProbe 成功一次livenessProbe 和 readinessProbe 才开始接管。我个人强烈建议所有初始化耗时不确定的新服务都配上 startupProbe。它彻底解决了 “initialDelaySeconds 拍脑袋” 的问题。配置上failureThreshold 可以放大一点因为 startupProbe 只是在启动阶段生效不会导致频繁重启。比如 10 秒一次、连续 60 次等于允许 10 分钟内慢慢启动这对 AI 模型加载、大数据组件启动这类场景非常实用。3.3 滚动更新中的探针readinessProbe 是流量切换的开关探针的作用不只是在运行期。在滚动更新、扩容缩容这些场景里readinessProbe 可以说是控制流量切换的开关。想象一下滚动更新流程新 Pod 被创建执行 readinessProbe。探针一直失败新 Pod 状态仍然是Running但不接收流量旧 Pod 继续提供服务。探针成功新 Pod 的 IP 被加入 Service 的 Endpoints开始接收流量。继续创建下一个新 Pod直至所有副本更新完成。如果没配 readinessProbeK8s 会在 Pod 进入 Running 状态后立刻把流量切过去。假如你的应用需要预热 JIT 编译、加载远程配置、建连数据库流量切过去时应用还没准备好大量请求会直接报错。这里我吃过一个实亏。某一次发布因为配置中心临时抖动新 Pod 启动后配置文件拉不下来业务端口没有监听。由于当时没有配置 readinessProbeService 照样把流量转发给新 Pod。发布期间线上大量 502查了大半天才定位到是“没就绪的 Pod 接收了流量”。从那以后我给自己定了一条铁律凡是接入 Service 的 Deployment必须配置 readinessProbe否则不允许上生产。另外还有一个细节容易被忽略rolling update 的 maxUnavailable 与 readinessProbe 的关系。K8s 滚动更新的默认策略会先停掉一个旧 Pod再创建新 Pod。如果你的应用依赖数据库迁移或接口兼容性而 readinessProbe 检测又不够严格发布期间新旧版本都会在 Service 里流量可能打到不兼容的旧版本上。要缓解这个问题可以把 maxSurge 和 maxUnavailable 配置得保守一点比如同时最多多启动 1 个新 Pod同时最多停 1 个旧 Pod保证服务容量不出现断崖。4. 探针的排查实录与最佳实践4.1 我在生产环境遇到的四种探针故障故障一探针接口返回 500导致滚动更新永远无法完成某个服务的/healthz接口内部做了强依赖检查下游数据库一旦有延迟接口就返回 500。readinessProbe 一直失败新 Pod 永远进不了 Endpoints滚动更新卡住旧 Pod 也迟迟不缩容。这是很典型的“就绪探针太严”导致的问题。解决思路健康检查接口要区分“存活”和“就绪”的语义。存活检查只查进程和基础资源就绪检查才查外部依赖。更合理的做法是/healthz检查本地状态/readyz检查下游依赖。用不同路径分别挂到 liveness 和 readiness 上互不干扰。故障二TCP 探针把“半连接”误判为健康一个缓存中间件服务用的是 tcpSocket 探针后端进程发生线程池耗尽新的 TCP 连接虽然在操作系统层面能握手成功但应用层根本不处理任何命令。此时 TCP 探针依旧报告健康流量持续分发到故障节点。这类问题的根源是 TCP 探针检测不到应用层状态。建议这类服务如果能暴露 HTTP 健康检查接口就换成 httpGet实在做不到也要配合 metrics 和告警做兜底避免探针成为唯一的健康依据。故障三Exec 探针命令执行时间过长某个容器内没有 curl团队就用 exec 探针执行自定义 shell 脚本做检测。脚本里有一次远程 HTTP 调用响应稍微慢一点脚本执行时间就会超过 timeoutSeconds探针判定失败容器被反复重启。教训有两条第一exec 探针里的命令必须尽量轻量不要做网络调用最好是读文件、算资源、查本地端口这类毫秒级操作第二给探针脚本设置自己的内部超时如timeout 2 /bin/sh check.sh防止脚本挂死拖垮 kubelet 的资源。故障四livenessProbe 和依赖故障形成“死循环”服务启动后readinessProbe 因为数据库连接失败而持续失败这本身是安全行为——流量不会进来。但如果同时配置了 livenessProbe 也依赖数据库那就有麻烦了liveness 连续失败触发重启容器重启后再次初始化数据库还没恢复又失败又重启。整个 Pod 在 CrashLoopBackOff 里循环而问题源头是数据库重启容器毫无意义。最终方案是livenessProbe 永远不要检查外部依赖只检查本进程的存活状态。外部依赖挂了应该让服务继续运行、readiness 摘流量、等待依赖恢复。这样既避免了反复重启也保留了故障恢复的能力。4.2 排查命令与调试思路遇到探针问题第一件事不是看 YAML而是看事件和日志。我常用的排查命令整理成了一套组合拳# 查看 Pod 的详细状态和最近事件 kubectl describe pod pod-name # 查看探针是否真的在发请求日志里会有 /healthz 的记录 kubectl logs pod-name --tail50 # 进入容器手动模拟探针请求 kubectl exec -it pod-name -- curl -v http://127.0.0.1:8080/healthz # 查看容器是否被重启过以及重启次数 kubectl get pod pod-name -o jsonpath{.status.containerStatuses[0].restartCount} # 查看 Service 后端是否包含该 Pod 的 IP kubectl get endpoints service-name有两个小技巧值得分享。通过 describe 里的事件信息定位探针失败。如果是 liveness 失败事件里通常会出现Liveness probe failed: HTTP probe failed with statuscode: 500如果是 readiness 失败会出现Readiness probe failed字样。注意区分事件里出现的探针类型不要一看到 probe 失败就以为容器要被杀。用kubectl get endpoints验证流量摘除是否生效。当服务的某个 Pod 就绪探针失败后它的 IP 应该从 Endpoints 列表中消失。如果 IP 还在说明 readinessProbe 的配置没有按预期工作或者 Service 选择的标签名没有匹配到对应 Pod。这个命令在排查“探针失败但流量还在”的问题时几乎是百试百灵。4.3 探针设计的最佳实践清单最后把我这几年的配置经验和设计原则汇总成一张速查表发布前对照检查一遍能帮你少走很多弯路。关注点推荐做法理由探针接口语义liveness 查 /healthzreadiness 查 /readyz区分进程存活与应用就绪避免互相干扰HTTP 状态码判断200-399 算成功兼容重定向类返回码避免把 302 当失败liveness 周期10-20 秒不用太频繁避免无谓压力readiness 周期5-10 秒缩短故障摘除时间窗口initialDelaySeconds日志实际就绪耗时 20%-30%防止启动初期误杀startupProbe初始化时间不确定时优先使用替代拍脑袋的 initialDelay外部依赖检查readiness 可以检查liveness 不要防止外部故障引发容器反复重启exec 探针脚本只做本地毫秒级操作避免脚本耗时过长导致误判探针资源开销基于 exec 时控制进程创建频率kubelet 频繁创建进程会占用节点资源发布验收滚动更新前人工摘除一个副本观察验证 readiness 摘流量真实生效我自己在设计探针方案时还有一个深层心法探针的本质是给 K8s 一个明确的“健康信号协议”。你希望 K8s 在什么条件下重启你、在什么条件下不给你流量、在什么条件下认为你准备好接客都要通过探针明确表达出来。这套协议设计得越清晰K8s 的自动化能力发挥得就越充分人力介入的次数就越少。说到底高可用不是靠 K8s 帮你扛出来的而是靠你把应用的真实状态准确告诉 K8s让它替你做最优决策。探针就是你们之间沟通的那根信号线。把这根线接对、接稳比你多写一百行业务代码都管用。最后分享一个我自己一直在用的小习惯每次写完探针配置我都会故意在模拟环境里把业务端口停掉然后观察 K8s 的反应。该重启的重启、该摘流量的摘流量全部符合预期之后再放到生产。这个“故障演练式验证”花不了几分钟却能在真正出故障时帮你守住底线。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑