K8s健康检查探针详解:Liveness/Readiness/Startup

发布时间:2026/10/20 22:22:14
K8s健康检查探针详解:Liveness/Readiness/Startup 线上一次P0事故让我记忆深刻Pod状态明明是Running容器进程也没退出但用户注册请求全部超时。排查到最后发现业务进程早就卡死在一个死锁里CPU占用为0对外不响应任何请求。Kubernetes对此无动于衷因为默认情况下它就只看进程是否活着进程在它就认为一切正常。那次之后我才真正意识到k8s健康检查探针Liveness/Readiness/Startup Probe不是一个可有可无的配置项而是保证线上服务可用性的最后一道防线。这篇文章我把我实际配置探针、排查探针问题的经验全部梳理一遍从原理到参数计算再到完整的YAML示例和排障方法争取让你看完就能直接上手少走我踩过的那些坑。适合谁看刚接触k8s、被Pod“假死”坑过的新手运维已经在用探针但配置总是不稳定的开发者以及想系统理清三种探针区别的人。1. 探针要解决的问题容器活着不代表服务可用1.1 从“假死”现象说起为什么进程在服务却挂了很多刚接触容器的人有一个误区只要容器没有退出就说明应用是健康的。这在实际生产环境里完全不成立。拿我们那个事故来说Java进程还在JVM还在线程池却全部打满所有新请求都阻塞在队列里。这种状态在操作系统层面完全正常进程没有崩溃没有OOM但业务层面已经彻底不可用。再比如Go写的服务发生死锁某个协程占着锁不放其他协程全部阻塞这时候进程既不退出也不消耗CPU从docker ps看就是完美的Running状态。k8s默认的容器存活判断依据是什么是容器的主进程PID 1是否退出。只要主进程没退出kubelet就认为容器是健康的不会对它做任何干预。这就像什么呢你租了个房子房东只看你有没有退房至于你躺在床上生病了、无法工作了他完全不管。可你的房租合约规定的是你要正常工作不是你人还在。探针就是解决这个问题的机制让k8s定期去问应用你还好吗还能干活吗通过应用自己的回答来判断健康状态而不是仅仅依赖进程是否存在。1.2 三种探针的分工活没活、能不能接流量、起没起来k8s提供了三种探针很多初学者会把它们混为一谈实际上它们负责的事情完全不同。**LivenessProbe存活探针**回答的问题是容器该不该被杀掉重来。如果探测失败kubelet会按照restartPolicy的规则杀掉容器并重新创建。它保护的是Pod本身防止假死容器一直占着资源却不干活。**ReadinessProbe就绪探针**回答的问题是这个Pod现在能不能接收外部流量。探测失败时kubelet会把该Pod的IP从对应的Service的Endpoints列表中移除流量不再打到它上面但Pod本身不会被杀掉。它保护的是流量入口防止请求打到不健康的实例上。**StartupProbe启动探针**是后来加入的机制专门解决启动慢的应用和存活探针误杀之间的矛盾。它只在容器启动阶段运行探测成功一次之后存活探针和就绪探针才开始接管。如果启动探针一直失败容器会被杀掉重启。三者的关系可以这样理解启动探针管起来没存活探针管还能不能用就绪探针管能不能接客。1.3 选型思路哪些场景必须配哪种探针我见过不少团队只配一个liveness或者只配readiness结果换了个环境就翻车。实际选型里我的经验是这样的无状态Web服务readiness和liveness都应该配。readiness保证滚动发布时新Pod先确认自己能干活再接流量liveness保证运行中假死时能被重启。启动特别慢的应用比如JVM应用启动要两三分钟或者启动时要加载大量模型文件必须配startupProbe不然liveness在启动阶段就有可能误判把还在初始化中的容器杀掉陷入无限重启的循环。批处理任务Job/CronJob一般不需要readinessliveness可以配帮助任务卡死时自动重跑。依赖外部资源的应用比如连数据库、连Redisreadiness里可以顺带检查依赖是否可用这样依赖挂了Pod会从Service中摘除而不是收到一堆连接超时的请求。表格对比一下三种探针的定位探针类型探测失败后果成功后的行为适用场景startupProbe容器被杀按restartPolicy重启探测成功后不再执行启动慢的应用、Java大内存应用、加载大模型livenessProbe容器被杀按restartPolicy重启持续周期性执行防止进程假死、死锁、线程池耗尽readinessProbePod从Service Endpoints移除不接收流量持续周期性执行恢复后重新加入滚动发布、依赖不可用时的优雅摘除配置三种探针时还有个小细节经常被忽略readiness和liveness可以同时存在但不要用同一个探测逻辑。比如readiness检查依赖数据库liveness如果也检查数据库一旦数据库抖动liveness会把所有Pod杀掉重启整个服务直接雪崩。liveness应该只检查应用自身进程级别的健康状况readiness可以扩大范围去检查依赖。1.4 为什么不能用进程检查替代探针有朋友问过我既然探针这么麻烦能不能在容器里跑个脚本监控进程发现异常就自己退出理论上可以但有三个致命问题一是进程退出这个方案默认应用能感知自己是否健康而大部分假死场景里应用自己是毫无感知的等它发现异常线上已经挂了很久。二是容器内自检脚本本身也可能出问题比如脚本依赖的某个命令不存在、脚本自身被kill反而引入更多不确定性。三是k8s的探针是平台级的机制它的结果会被kubelet、kube-proxy、Service调度联动使用比如readiness失败自动摘流量容器内自检脚本完全没有这个能力。所以探针不是多此一举而是把健康判断从应用内部提升到了编排系统层面的基础设施能力。2. 三种探测方式与六个核心参数配置前必须吃透2.1 探测方式HTTP、TCP、Exec各自怎么选k8s的探针支持三种探测方式它们的本质区别在于以什么标准判断健康。HTTP探测httpGetkubelet向容器内的某个HTTP接口发起GET请求如果返回码在200到399之间算探测成功否则算失败。这是最常用也最推荐的方式因为它直接验证应用的应用层逻辑能确认HTTP服务真的能处理请求。我一般会建议开发人员在项目里单独暴露一个/healthz或/healthy接口专门给探针用这个接口不要走业务逻辑太深避免探针本身给系统带来额外压力。TCP探测tcpSocketkubelet尝试与容器内指定端口建立TCP连接能连上算成功连不上算失败。它的粒度比HTTP粗只要端口监听正常就算健康但应用层是否正常它管不了。适合那些不是HTTP协议的服务比如gRPC、数据库、消息队列或者服务还没做健康检查接口时的临时方案。Exec探测execkubelet在容器内执行一条命令命令退出码为0算成功非0算失败。这个最灵活比如可以执行cat /tmp/healthy检查某个标识文件是否存在或者执行一个自定义脚本来验证内部状态。但要注意exec每次探测都会在容器里起一个进程如果探测命令本身写得很重比如查数据库、遍历日志会对容器资源造成额外消耗。我的选型原则很简单能走HTTP优先走HTTP因为应用层验证最靠谱没有HTTP服务的用TCPexec只用来做特殊检查而且检查命令一定要轻量。三种方式的配置骨架对比如下# HTTP探测 livenessProbe: httpGet: path: /healthz port: 8080 httpHeaders: - name: X-Custom-Header value: health-check # TCP探测 readinessProbe: tcpSocket: port: 3306 # Exec探测 livenessProbe: exec: command: - cat - /tmp/healthy2.2 六个核心参数每一个都决定探针的手感光有探测方式还不够探针的灵敏度由六个参数控制。这六个参数任何一个设置不当都可能带来灾难。initialDelaySeconds容器启动后等多长时间才开始第一次探测。这个参数必须大于等于应用的启动时间否则探针一上来就失败直接把刚启动的容器杀掉。periodSeconds探测的周期默认10秒。周期越短发现问题的速度越快但对容器的压力也越大。timeoutSeconds单次探测的超时时间默认1秒。如果应用响应慢比如接口里查询了数据库1秒很容易超时需要适当调大。successThreshold连续成功几次才认为探针从失败转为成功默认1次。对readiness来说如果设为1一旦应用恢复正常流量立刻恢复这可能导致流量瞬间涌入通常我会设为1保持快速恢复但对liveness一般保持默认1即可。failureThreshold连续失败几次才认为探针最终失败默认3次。这个参数配合periodSeconds计算判定失败的总时间。terminationGracePeriodSeconds容器被判定失败后kubelet发送SIGTERM到SIGKILL之间的宽限期默认30秒。如果应用优雅退出需要较长时间比如要排空连接、刷盘这个参数要相应调大。2.3 参数计算的思路用最坏情况倒推配置很多文章只列参数不教你怎么定值这里我给出一个我实际使用的计算框架。第一步先测量应用的冷启动时间。怎么测在测试环境把Pod启动从容器创建开始计时到健康检查接口可以正常返回为止。多测几次取最大值乘以1.5的系数作为initialDelaySeconds的参考值。比如应用启动平均需要80秒峰值100秒那么initialDelaySeconds设150秒是比较稳妥的。第二步确认单次健康检查接口的响应时间。平时P95响应时间如果是200毫秒timeoutSeconds至少要给1秒如果接口偶尔会到2秒就给3秒。timeout设置过短是探针误报最常见的坑。第三步用公式估算从故障发生到探针判定失败的总时长判定失败耗时 ≈ periodSeconds × (failureThreshold - 1) timeoutSeconds举个例子periodSeconds: 10failureThreshold: 3timeoutSeconds: 2那么探针大概需要10 × 2 2 22秒才能确认失败。如果你想在30秒内发现故障就得收敛参数。第四步注意重启循环的问题。如果initialDelaySeconds periodSeconds × failureThreshold远小于应用启动时间容器会陷入没起来就被杀重启再被杀的循环。启动慢的应用一定配合startupProbe否则liveness在启动阶段的误杀会让你怀疑人生。3. 实操过程从零到完整配置一个健康检查探针3.1 拿着一个真实场景Web服务 MySQL依赖的探针配置假设我有一个比较典型的业务Pod它包含两个容器一个Web服务容器端口8080提供REST API一个MySQL客户端容器或者mysqld容器。Web服务启动时要初始化数据库连接池大概需要40秒。这个场景里Web服务暴露了一个/healthz接口它的逻辑设计如下如果Web服务进程自身能响应HTTP请求返回200如果数据库连接池获取不到连接返回500其他异常情况返回503。注意这里/healthz检查了数据库依赖那么readiness用它liveness就不能再用它了。我会给liveness单独准备一个/live接口只检查进程内部状态不涉及外部依赖防止数据库抖动导致所有Pod被重启。3.2 完整YAMLstartupProbe livenessProbe readinessProbe 组合直接上配置这个配置是针对Java应用的典型写法启动慢所以加了startupProbeapiVersion: apps/v1 kind: Deployment metadata: name: web-server namespace: production spec: replicas: 3 selector: matchLabels: app: web-server template: metadata: labels: app: web-server spec: containers: - name: web image: registry.example.com/web-server:v2.3.1 ports: - containerPort: 8080 name: http env: - name: DB_HOST value: mysql:3306 startupProbe: httpGet: path: /live port: 8080 initialDelaySeconds: 0 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 30 livenessProbe: httpGet: path: /live port: 8080 initialDelaySeconds: 50 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 50 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3逐个解释一下参数的选择理由。startupProbeinitialDelaySeconds: 0意味着容器一启动就开始探测periodSeconds: 5每5秒探一次failureThreshold: 30意味着允许连续失败30次。这样只要150秒内应用能起来startupProbe就放行。目标启动时间是40秒留了很大的余量不会误判。startupProbe不需要timeoutSeconds给太长2秒够用。livenessProbeinitialDelaySeconds: 50因为startupProbe成功后应用大概率已经起来liveness才开始工作50秒是给足缓冲。periodSeconds: 10每10秒检查一次timeoutSeconds: 3因为/live接口很简单3秒超时足够failureThreshold: 3连续3次失败重启也就是说从故障到重启大约需要10 × 2 3 23秒。readinessProbeperiodSeconds: 5readiness的检查频率比其他两个高因为流量摘除越快用户体验损失越小。timeoutSeconds: 3因为/healthz内部要检查连接池比/live慢一些。注意readiness也设了failureThreshold: 3连续失败5次实际是periodSeconds × (failureThreshold-1) 10秒才摘流量避免偶发抖动导致流量频繁切换。3.3 exec探针配置检查应用内部状态的实战写法有些应用没有健康检查接口也没法暴露HTTP端口这时候exec是一个兜底方案。我处理过一个离线任务型容器它的健康标志是某个标记文件被写入磁盘于是这样配置livenessProbe: exec: command: - sh - -c - test -f /var/run/app/healthy.marker initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3这个探测逻辑很轻量test -f只是检查文件是否存在几乎不消耗资源。命令只检查退出码0成功非0失败。exec探针有几个坑要提醒第一command里的命令必须在容器镜像里存在。比如镜像基于scratch构建的极简容器里面连sh都没有你写sh -c就会直接探测失败。第二探测命令不要写交互式的、有状态依赖的。比如你写一个脚本去读环境变量、去查日志文件里的关键字一旦脚本执行时间超过timeoutSeconds就会被判定为失败哪怕应用本身是好的。第三timeoutSeconds对exec同样生效。有些容器文件系统慢比如高负载的NFS挂载目录test -f也可能跑几秒timeout设置太短就会误报。3.4 HTTP探测的特殊情况端口、路径与HeaderHTTP探测里最容易踩坑的是端口和路径写错。端口默认填containerPort对应的端口如果容器里进程监听的端口和你声明的不一致探针会一直失败。我遇到过一次容器里Java进程监听的是8081但YAML里写的port: 8080结果readiness永远不通过Service里一个Endpoints都不挂。路径方面如果健康检查接口要求认证比如需要token探针会返回401按规则这是探测失败。解决办法是用httpHeaders加上认证信息livenessProbe: httpGet: path: /healthz port: 8080 httpHeaders: - name: Authorization value: Bearer eyJhbGciOi...不过更推荐的做法是让健康检查接口在应用内单独走一套无认证的逻辑探针只负责进程活着这个基本判断不要混入业务鉴权逻辑。3.5 配置完成后如何观察探针状态YAML写好并apply之后不要直接看Pod Running就觉得完事了一定要确认探针确实在工作。先看Pod的详细状态kubectl describe pod web-server-7d9f5b6c4-abcde在Events区域能看到类似这样的输出Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Started 2m kubelet Started container web Normal Readiness Changed 90s kubelet Readiness probe succeeded如果探针失败会看到类似Liveness probe failed: HTTP probe failed with statuscode: 503的事件。再看Pod的READY列kubectl get pods -l appweb-server输出里READY显示1/1说明就绪探针已通过显示0/1说明还没通过这时候去查Endpointskubectl get endpoints web-server如果readiness没通过Endpoints列表里不会有这个Pod的IP通过了才会出现。想手动验证探针路径是否真的可达可以用kubectl exec进入容器直接curl本机接口kubectl exec -it web-server-7d9f5b6c4-abcde -- curl -s http://localhost:8080/healthz这一步能帮你区分到底是探针配置错了还是应用本身的健康接口有问题。3.6 一个经常被忽略的细节探针接口自身的性能健康检查接口一定要设计得轻量。我见过有些应用的/healthz接口里做了大量查询操作比如查所有表的大小、遍历缓存、检查磁盘容量结果单次探测耗时好几秒探针周期是10秒timeout是2秒那这个探针就永远不可能通过Pod被一遍遍杀掉。健康检查接口正确的设计是O(1)复杂度最好只做几件事确认当前goroutine/线程没卡死、确认关键内部组件比如HTTP Router能响应、最多确认一下连接池的计数器而不是实际建连。数据库依赖的检查放到readiness里而且也要做超时控制比如context.WithTimeout限制在500毫秒内。4. 常见问题与排查技巧实录4.1 问题速查表现象、原因、解决方案下面是这些年我遇到过的探针相关高频问题整理成一张速查表现象可能原因解决方案Pod一直CrashLoopBackOffEvents里全是Liveness probe failedinitialDelaySeconds太小liveness在应用启动完成前就开始探测调大initialDelaySeconds或增加startupProbe容器能起来但Service访问不通Pod READY为0/1readiness的端口或路径写错健康接口依赖的外部服务未就绪检查readiness配置健康接口去掉外部依赖或等依赖就绪再启动接口偶发超时但Pod被频繁重启timeoutSeconds太小单次探测超时调大timeoutSeconds比如从1秒调到3秒新版本发布后Pod一直不Ready回滚后恢复正常新版本健康检查接口行为变化比如加了鉴权、改了返回码发布前检查新版本健康接口返回码在200-399之间Pod内进程卡死但liveness没触发failureThreshold和periodSeconds组合太长故障发现延迟缩短periodSeconds或降低failureThresholdexec探针一直失败但命令在容器里手动执行成功exec探测的命令路径没写对镜像里没有shtimeout太短用二进制绝对路径确认镜像包含所需命令4.2 启动慢被liveness误杀的经典案例这是新手最容易踩的坑。有个团队部署一个Spring Boot应用启动需要90秒他们只配了livenesslivenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3结果是Pod永远在重启。算一下时间线容器启动后等30秒开始探测每10秒探一次连续3次失败就杀掉那么最早在第30 10×2 2 52秒就会把容器杀掉。应用要90秒才能起来52秒时还处于启动中liveness一探一个失败。这就等于每个容器活不过1分钟就被判死刑然后是无限重启循环。解决方案就是我前面推荐的加一个startupProbe让它在应用启动阶段接管判断容忍较长的启动时间。后来这个团队的startupProbe配置改成了这样startupProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 4040 × 5 200秒的启动宽限期彻底解决了误杀问题。4.3 readiness和liveness用同一个接口导致雪崩再讲一个更隐蔽的坑。某团队给所有微服务统一配置了探针readiness和liveness都指向同一个/healthz接口这个接口会检查Redis连接。有一天Redis发生故障所有Pod的/healthz都返回503。readiness率先把Pod从Service中摘除这本来是对的但紧接着liveness也开始失败kubelet开始大规模重启Pod。重启后的Pod连Redis还是失败的继续被liveness杀掉。结果整个集群的Pod陷入重启风暴服务彻底不可用。这里的关键教训是liveness的探测路径一定要避开外部依赖。liveness只回答这个进程还活着吗严格来说只要进程能返回HTTP响应不管数据库有没有连上都应该算存活。外部依赖的检查只应该放在readiness里。这样即使Redis挂掉Pod们只是被摘除流量、等待恢复而不是被一遍遍杀掉。4.4 排查探针问题的标准步骤如果你遇到探针相关的故障我建议按这个顺序排查不要上来就改配置。这套流程是我在多次排障中总结出来的效率很高。第一步看Pod事件。执行kubectl describe pod pod-name在Events里找Liveness probe failed、Readiness probe failed、Startup probe failed这些关键字。事件里会带上探针失败的具体原因比如HTTP probe failed with statuscode: 500。第二步看探针目标接口的实际返回。用kubectl exec进入容器手动curl探针路径观察返回码和响应内容。如果手动能通但探针失败可能是探测配置和你实际访问的地址不一致端口、路径、Header。第三步看容器日志。kubectl logs pod-name有时候应用在启动阶段会打印异常堆栈能直接定位为什么健康接口迟迟不通过。第四步检查资源配置。探针接口本身需要CPU和内存如果容器被resources.limits限制得太狠健康检查接口可能因为资源不足而响应超时。这种情况在高峰期尤其明显平时正常一压测就探针失败。第五步确认网络策略。如果集群里启用了NetworkPolicykubelet发起的探针请求可能被策略挡掉导致探针一直失败。可以先临时放宽NetworkPolicy验证一下。4.5 几个我踩过坑后总结的避坑技巧第一探针路径要独立于业务接口。不要复用那种返回完整业务数据的接口也不要复用需要鉴权的接口。单独建一个/healthz、/live、/ready三兄弟逻辑越简单越好。第二发布上线前先灰度探针。新版本改了健康检查逻辑先在测试环境验证探针通过再灰度到生产。我在一个版本里不小心把健康检查接口返回码写成了201因为框架自动处理探针真心认为这是非健康状态差点把发布卡住。第三不要忽略Pod的restartPolicy。默认是Always如果业务上希望失败后不要自动重启比如某些任务型PodrestartPolicy要改成Never或OnFailure否则liveness一触发就会循环重启很难收到告警。第四探针配置要纳入版本管理。探针参数不是一次配好就完事的应用启动时间会随着版本变化而变化。每次发版前比较一下新版本的启动耗时如果明显变长要同步调整startupProbe。我建议把探针参数写成一个单独的ConfigMap或者在Helm values里显式声明方便审计和调整。第五监控探针的失败事件。探针失败早期往往是零星的不一定会导致Pod重启但可能预示着隐患。我建议把ProbeFailed类型的k8s事件接入告警能在问题扩大前提前介入。5. 一点经验之谈探针配置没有银弹每个服务的启动时间、接口耗时、外部依赖都不一样参数必须个性化。但核心原则就那么几条启动慢用startupProbeliveness只看进程自身readiness承担依赖检查timeout尽量比接口P99响应时间大用公式计算出故障发现时间再决定period和failureThreshold的取值。我最后再分享一个我自己很常用的技巧在应用的健康检查接口里额外加上一个/healthz/verbose路径返回JSON格式的详细状态包括依赖列表和各自耗时。平时探针只走简洁的/healthz排查问题时访问/healthz/verbose能直接看到是哪个依赖拖慢了响应。这个设计帮我省去了很多次进容器手敲命令的麻烦也推荐你试试。先把你现有的探针配置翻出来对照上面的参数和坑位过一遍改完观察一天事件和重启次数很快就知道问题在哪了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询