news 2026/10/22 8:59:40

Kubernetes健康检查探针实践:三类探针配置与故障案例复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kubernetes健康检查探针实践:三类探针配置与故障案例复盘

如果你维护过 Kubernetes 集群,大概率遇到过这种诡异情况:某个 Pod 状态明明显示 Running 且 Ready,但业务方反馈接口一直超时;另一个 Pod 看着一切正常,却在凌晨毫无征兆地被重启了好几回。查到最后,问题往往都出在同一个地方——健康检查探针的配置。Kubernetes 的健康检查机制本身并不复杂,文档里的概念十几分钟能看完,但真正放到生产环境,探针用不好引发的连锁故障,能让你在监控屏前耗上一整天。

这篇内容不是把官方文档翻译一遍,而是我在维护集群和帮业务团队排障过程中,沉淀下来的探针配置思路、参数计算逻辑和故障复盘。无论你是在图形界面上点按钮部署,还是直接写 Deployment YAML,这些经验应该都能帮上忙。

1. 三种探针各管一段生命周期,别只盯着 liveness 一个

很多团队最早接触健康检查,抄的第一份 YAML 基本只有 livenessProbe,因为看到的效果很直接:探针失败、Pod 被杀、重启,看起来服务被"保护"住了。但真实生产里,只配 liveness 往往是不够的,甚至可能起反作用。

1.1 探针机制的分工逻辑:为什么 K8s 要把健康检查拆成三份

Kubernetes 在 Pod 级别提供三种可配置的探针:

  • livenessProbe:存活性探针。探测失败后 kubelet 会杀掉容器,按 restartPolicy 重启。它回答的问题是"进程还活着吗"。
  • readinessProbe:就绪性探针。探测失败后 Pod 会从对应的 Service Endpoints 中移除,不再接收新流量,但 Pod 不会被杀。它回答的问题是"这个 Pod 能接流量了吗"。
  • startupProbe:启动探针。用于保护启动很慢的应用,在它成功之前,另外两种探针都不会启动。它回答的问题是"应用真正起来了吗"。

我见过不少团队把"存活"和"就绪"混为一谈,总觉得探针失败就该重启。实际上,readiness 失败只是隔离流量,让 Pod 暂时"离线",等恢复之后再挂回去;只有 liveness 才涉及杀容器。两者职责完全不同。

1.2 三种探针同时配置时的生效顺序

实际执行顺序是这样的:Pod 启动后,kubelet 先看有没有配置 startupProbe,如果配置了,就先执行 startupProbe;startupProbe 一旦成功,kubelet 才开始执行 livenessProbe 和 readinessProbe。

这个顺序很容易被忽略。很多人以为 startupProbe 只是用来"延迟"检查的,其实它是三道闸门的第一道。对一个启动需要 30 秒的 Java 应用,如果不配 startupProbe,就得把 liveness 的 initialDelaySeconds 调成很大的值;配了 startupProbe,initialDelaySeconds 就可以保持很小,因为 startupProbe 会在应用真正起来之后才放行后续探针。

1.3 三种探针失败后的不同结果,决定了你的选择

方便对照,我列一个表:

探针类型探测失败后果典型用途常见误区
livenessProbe容器被杀并重启检测死锁、OOM、进程僵死用它做业务依赖检查
readinessProbe从 Endpoints 摘除,不接收新流量检测依赖服务、连接池是否就绪以为它也会重启 Pod
startupProbe容器被杀并重启保护冷启动时间长的应用用固定 initialDelay 代替

我实际遇到的典型事故是:团队把 liveness 指向一个查数据库的接口,数据库抖动,所有实例的 liveness 同时失败,整个服务在一分钟内被全部重启,流量直接雪崩。所以第三章我会专门拆一个"探针端点设计"的细节。

2. 探测方式与参数计算:从 HTTP 到 TCP,选错就会误杀

2.1 三种探测机制的原理与选型建议

Kubernetes 支持三种探测动作,对应不同的服务形态:

  • exec:在容器内执行命令,退出码为 0 视为健康。适合进程内状态可以自检、又不想额外暴露端口的组件。但 exec 有个隐藏成本:命令是 kubelet 在容器里 fork 执行的,如果命令本身耗资源或可能卡住,会拖累 kubelet。
  • tcpSocket:向容器 IP 的指定端口发起 TCP 连接,三次握手成功即视为健康。适合数据库、缓存、消息队列这类"端口通基本可用"的组件。
  • httpGet:请求指定路径的 HTTP 接口,返回状态码在 200-399 之间视为健康。适合 Web 服务。

值得说一点:tcpSocket 是最"轻"的探测方式,但也是最容易误判的。比如一个缓存组件连接池耗尽,端口照样在监听,TCP 握手照常成功,readiness 依然通过,但业务请求进来就是超时。所以对关键无状态服务,能用 httpGet 就不要贪省事用 tcpSocket。

2.2 探针参数的计算逻辑,别拍脑袋填

无论哪种探针,都有五个参数需要理解:

  • initialDelaySeconds:容器启动后,等多久开始第一次探测。
  • periodSeconds:每隔多少秒探测一次。
  • timeoutSeconds:单次探测等待响应的超时时间。
  • successThreshold:连续成功多少次才视为"健康"。
  • failureThreshold:连续失败多少次才触发对应动作。

一个常见的误判是只算 periodSeconds,忘了 timeoutSeconds 也算进失败判定时间。假设 failureThreshold=3、periodSeconds=10、timeoutSeconds=1,那么从第一次探测失败开始,大约需要 33 秒才会触发重启;如果 timeoutSeconds 是 30、periodSeconds 是 10,实际判定时间会明显变长。

数值之间还有一个容易踩的雷:timeoutSeconds 最好小于 periodSeconds。如果 timeout 比 period 还大,上一轮探测还没结束,下一轮又发起了,kubelet 侧会出现探针叠加,日志里全是超时,业务看到的则是探针请求密集涌入。

2.3 到底该怎么填?我用一个场景算一遍

假设一个网关服务,正常启动要 5 秒,依赖 MySQL、Redis 和两个内部 RPC。健壮的配置大概是:

  • readinessProbe:periodSeconds=5,timeoutSeconds=1,failureThreshold=3。也就是说依赖任何一个抖动,最多 18 秒左右就会把实例摘掉。
  • livenessProbe:periodSeconds=10,timeoutSeconds=1,failureThreshold=3。
  • startupProbe:periodSeconds=2,timeoutSeconds=2,failureThreshold=15,最多给它 30 秒完成启动。

有人会问:failureThreshold 调大一点是不是更稳?对,也不对。调大了确实能减少瞬时抖动导致的误杀,但也会延长故障发现时间。一个已经死锁的实例,本来 30 秒内能被清理,failureThreshold=10 可能要等 2-3 分钟才被处理,这期间流量一直打在一个坏实例上。我的习惯是:liveness 的 failureThreshold 保持 3-5,readiness 的 failureThreshold 可以略大,因为它只是摘流量,不涉及杀进程。

3. 三个真实落地场景的探针配置拆解

3.1 常规 Web 服务:liveness 和 readiness 路径必须分开

大多数 HTTP 服务的正确姿势是提供两个健康检查端点:/livez 和 /readyz。一个只管进程活着,一个真正检查依赖。

readinessProbe: httpGet: path: /readyz port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 1 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /livez port: 8080 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3

/livez 的实现要足够简单:内存、goroutine 正常就返回 200;不要查数据库,不要查远程调用。/readyz 可以去查依赖,但要控制超时,比如 1 秒内必须返回结果,否则直接判失败。

原因:如果把依赖检查放进 liveness,数据库一抖动,所有实例的 liveness 同时失败,K8s 会把整个服务快速重启,这是最典型的自残式故障。

3.2 缓存和存储组件:tcpSocket 更适合

对 Redis、MySQL 这类组件,通常配置 tcpSocket 就够了。

readinessProbe: tcpSocket: port: 6379 periodSeconds: 10 timeoutSeconds: 3

注意事项:tcpSocket 只能证明端口通了。如果你的缓存组件状态需要在 readiness 里体现(比如主从切换或集群握手状态),建议在业务里提供一个命令行检查工具,用 exec 方式探测。但 exec 命令不能太重,控制在 100ms 内完成,避免每次探活都在容器里做一次完整的状态汇总。

3.3 冷启动很慢的应用:startupProbe 兜底

一个典型的 Java 微服务,依赖配置中心、注册中心,冷启动经常需要 60-90 秒。如果按传统方式把 initialDelaySeconds 调成 120,又会导致真正故障时发现太慢。这个时候 startupProbe 最有用。

startupProbe: httpGet: path: /healthz port: 8080 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 30

即最多等待 30 次探测,每次最长 2 秒超时,相当于给应用约 210 秒的启动窗口。startupProbe 成功之前,liveness 和 readiness 不会执行,所以 initialDelaySeconds 可以放心保持较小值。

我在一个线上团队接手过一个服务,启动要 50 秒,但原来的 YAML 里 initialDelaySeconds=60,periodSeconds=5。表面看刚好能过,但一旦启动过程中 CPU 被抢占,第 60 秒还没监听端口,马上被杀重启,陷入"启动失败-被杀-再启动"的死循环。改用 startupProbe 之后,这个问题一次都没再出现。

3.4 健康检查端点的实现细节

无论用哪种探针,端点本身的实现质量决定整套机制的可靠性。我的建议是:

  • 端点要轻。不要在健康检查里打印大量调用栈、采集完整指标。
  • 端点响应要快。探针 timeoutSeconds 通常只有 1-3 秒,不能做重计算。
  • 端点要稳定。不要把一些偶发抖动也判定成失败,可利用状态码区分:200 表示健康,503 表示依赖未就绪,让 readiness 和 liveness 能够按不同的状态码处理。

这些建议看起来是常识,但我在生产环境里见过太多人把健康检查端点写成一个"迷你压测接口",每次探测都要查全量配置、预热缓存,结果探针请求本身把 CPU 打满了。

4. 一次滚动更新卡死的完整排查链路与级联故障复盘

4.1 案例一:Pod 反复 CrashLoopBackOff

那次事故发生在一次普通版本升级之后。新版本发布上去,Pod 没有像预期那样 Running,而是进入了 CrashLoopBackOff。

我按顺序做了这几步排查:

  1. kubectl get pods看到新版本 Pod 状态 CrashLoopBackOff,重启次数一直在涨。
  2. kubectl describe pod <name>看到 Events 里有一行关键信息:Liveness probe failed: HTTP probe failed with statuscode: 500。
  3. kubectl logs <容器名>没发现异常堆栈,应用进程本身是起来的。
  4. 进入容器手动curl /livez,返回 500。
  5. 查路由配置,发现新版本把 /livez 放进了需要鉴权的路由组,探针请求不带 token 返回 500。

修复方式就是:把健康检查路由放在独立分组,不加鉴权;或者给探针配置 httpHeaders,让它带上内部 token。

复盘下来,这条经验最通用:健康检查端点不要和服务业务路由混在一起。有时候开发为了省事,把 healthz 塞进了 web 框架的全局拦截器里,一旦做权限改造,探针就跟着挂。

4.2 案例二:滚动更新卡死,新 Pod 一直不 Ready

另一个 case 是滚动更新一直不完成,新 Pod 起不来,旧 Pod 也不退出。kubectl describe pod看到的是 Readiness probe failed,而不是 Liveness probe failed,所以 Pod 不会被杀,只是永远不 Ready。

进入容器检查后惊讶地发现:服务监听的是 8081,而探针配的是 8080。原因很简单,测试环境容器端口恰好和探针配置一致,部署到集群时端口映射变了,但探针配置还在用老端口。

这类问题用一条命令就能区分:kubectl exec进容器,再curl localhost:端口,立刻能判断是监听问题还是探针配置问题。

4.3 案例三:数据库抖动引发全站级联雪崩

前面提过这种事故,我展开讲一下完整链路。某个晚上数据库出现了一次 5 秒的抖动,正常情况下这只是一次小告警。但因为 A 服务的 /livez 内部查询数据库,探针超时,liveness 连续失败,A 服务的所有 Pod 被快速重启。A 不可用之后,依赖 A 的下游服务开始报错,B 服务的 /readyz 又检查了 A 服务,B 的 Pod 也被摘流或重启。

最终结果是数据库一次 5 秒的抖动,演变成 20 多个服务同时雪崩。

正确的做法是:

  • liveness 永远不检查依赖。
  • readiness 可以检查依赖,但要给依赖检查设置很短的超时(比如 300ms),并考虑退避策略。

4.4 排查健康检查问题的实用命令

  • kubectl get pod <name> -o wide:看状态、重启次数、IP。
  • kubectl describe pod <name>:重点看 Events 里 probe failure 的报错。
  • kubectl logs <name>:对照容器日志和探针时间点。
  • kubectl get events --sort-by=.lastTimestamp:看全集群事件。

建议是:把探针请求日志在应用侧打印一下(或统计到 metrics),这样对比 kubelet 事件的时间线,定位是"探针没发出来"还是"端点响应失败",能省下大量瞎猜时间。

5. readiness 失效之后,优雅停机也在保护你的流量

5.1 从 readiness 失败到摘流量之间存在时间窗口

很多人以为探针一失败,Pod 马上就从 Service Endpoints 摘掉了。实际不是:探针是周期性执行的,失败后 kubelet 更新 Pod 状态,控制平面再更新 Endpoints,这中间有几秒到十几秒的延迟。在这段时间里,如果 Pod 还继续收到新请求,就会出现部分请求失败。

所以当我们要下线一个 Pod 时,应该先让它"失去 readiness",给控制平面一点时间把流量摘空,再真正退出。这就要靠优雅停机机制。

5.2 preStop 钩子和 terminationGracePeriodSeconds 的配合

lifecycle: preStop: exec: command: ["/bin/sh","-c","sleep 15"] terminationGracePeriodSeconds: 30

流程是:kubelet 收到删除 Pod 请求后,把 Pod 标记为 Terminating,发送 SIGTERM 信号给主进程,同时执行 preStop 钩子。preStop 执行完之后,容器才会进入后续终止流程。如果 preStop 执行时间超过了 terminationGracePeriodSeconds,kubelet 会直接发 SIGKILL 强杀。

所以配置要保证 terminationGracePeriodSeconds 大于 preStop 的 sleepTime。我见过有人把 preStop sleep 设为 30,但宽限期还是默认 30,结果 sleep 还没跑完就被强杀,等于白配。

5.3 应用侧也要响应 SIGTERM

Kubernetes 之外的问题:如果应用不捕获 SIGTERM,优雅停机无从谈起。常见做法是在应用里注册一个 shutdown hook,收到 SIGTERM 后按顺序做这几件事:从注册中心摘除自己、关闭 HTTP 连接池、等待存量请求结束、刷新缓冲数据、退出进程。

很多老服务只处理了 SIGINT(Ctrl+C),没处理 SIGTERM,到了 K8s 里一删 Pod 就直接被杀,连接池里的请求全部断掉。这类问题排起来很隐蔽,因为 Pod 状态和探针都正常,只有业务反馈"发布时总有报错"。

6. 复盘完这几个故障,我重新设计了探针规范

最后分享一些我现在的做法,不算标准答案,但都是被生产环境教育的:

  • 探针路径在所有服务中统一:/livez、/readyz,不允许各自起名。
  • liveness 只允许做进程存活检查,readiness 才能检查依赖,依赖检查超时必须小于 500ms。
  • 启动阶段一律用 startupProbe,initialDelaySeconds 只保留 5-10 秒。
  • 探针的 successThreshold、failureThreshold 进入发布规范,改动要走评审。
  • 端点耗时和成功率纳入监控,探针平均耗时超过 200ms 就告警排查。

这些规范看起来严格,但都是从事故里反推出来的。我原来也觉得健康检查"能用就行",直到连续经历了几次探针引发的故障,才明白它决定了 Kubernetes 对服务状态的判断边界。你把它当普通接口配,它就会在关键时刻用一场事故教你重新认识它。

多说一句,健康检查只是"判断健康"的第一步,真正的稳定性还要靠资源限制、弹性伸缩、容灾设计配合。但把探针这件事做对了,至少能让 K8s 在异常发生时,做出一个你不会后悔的决定。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 8:58:31

OpenOffice4与JODConverter集成SpringBoot实现文档转PDF全指南

这两年做过几个需要在线文档预览的后端项目&#xff0c;核心需求都差不多&#xff1a;用户上传docx、xlsx、ppt&#xff0c;服务端得在几秒内转出PDF&#xff0c;方便在线预览。最常用的开源方案就是OpenOffice4&#xff08;或LibreOffice&#xff09;加JODConverter&#xff0…

作者头像 李华
网站建设 2026/10/22 8:58:31

Sigrity OptimizePI实战:让去耦电容从经验估算变为PDN目标阻抗优化方案

简介&#xff1a;面向高速数字与电源完整性设计工程师的 Sigrity OptimizePI 操作教程包&#xff0c;聚焦电力输送系统&#xff08;PDS&#xff09;中的去耦电容布局与优化。OptimizePI 特有的任务式流程支持预布线与后布局阶段的 PDS 设计优化&#xff0c;基于专有算法综合评估…

作者头像 李华
网站建设 2026/10/22 8:58:24

基于Java的球鞋交易平台小程序:从数据库设计到答辩全攻略

做计算机毕设的同学&#xff0c;十个里面少说有三四个会碰电商类项目&#xff0c;而球鞋商城又是这类题目里最典型的方向之一。表面上看就是"用户逛鞋、下单&#xff0c;管理员管商品、发货"&#xff0c;但真动手做的时候&#xff0c;登录鉴权、商品模型、购物车、订…

作者头像 李华
网站建设 2026/10/22 8:55:52

YOLOv8口罩检测毕设实战:从数据集到答辩全流程解析

口罩检测这个题&#xff0c;这两年在毕业设计里出镜率非常高。几乎每隔几天就有人来问我选题靠不靠谱、框架用哪个、答辩怎么突出亮点。我看了不少人的开题报告&#xff0c;发现一个普遍现象&#xff1a;百分之九十的人打算做的所谓“深度学习行人口罩佩戴检测”&#xff0c;最…

作者头像 李华
网站建设 2026/10/22 8:55:33

CentOS7 利用 pyshark 解析 QUIC 报文实战指南

Centos7利用pyshark解析QUIC报文 最近在做网络协议分析&#xff0c;遇到一个真让人头疼的场景&#xff1a;C端应用大量走到UDP 443端口&#xff0c;但 tcpdump 抓下来全是密文&#xff0c;仔细一看原来都是QUIC流量。CentOS 7上想快速把QUIC报文里的版本号、包类型、DCID这些…

作者头像 李华
网站建设 2026/10/22 8:55:12

展会官网数据采集与清洗:邮箱解码、电话校验与多页爬取实战

1. 项目背景&#xff1a;一个展会官网到底埋了多少坑1.1 这个爬虫到底要爬什么做展会类网站的数据采集&#xff0c;尤其是跨境B2B展会&#xff0c;最让人头疼的其实不是数据量大&#xff0c;而是数据本身不规整。前阵子我接了一个某国际塑料行业展会官网的数据采集项目&#xf…

作者头像 李华