【K8s运维实战】Kubernetes 容器健康检查三探针实战:从原理到生产配置避坑指南

📅 2026/7/1 19:32:21
【K8s运维实战】Kubernetes 容器健康检查三探针实战:从原理到生产配置避坑指南
先说个真实故事凌晨 2 点某金融公司监控报警——支付服务异常中断。几分钟内多个微服务进入CrashLoopBackOff状态业务全面瘫痪。事后复盘发现根因就一句话存活探针配得太敏感了。高流量期间服务响应稍微慢了 1 秒存活探针超时→kubelet 重启容器→重启过程中更多请求失败→雪崩。这不是段子。CrashLoopBackOff占了 Kubernetes 支持工单的 40%而其中相当部分是探针配置问题。我干运维这十几年半夜被叫起来处理这类事故的次数两只手数不过来。所以今天把 Kubernetes 三种探针的原理、配置方法和避坑指南彻底讲清楚。三种探针各司其职Kubernetes 的探针Probe是 kubelet 对容器定期执行的诊断操作。三种探针各有各的活千万别搞混用途。Liveness Probe存活探针—— “活着吗”决定要不要重启容器。比如应用死锁了——进程还在跑但卡住不动了。存活探针发现了kubelet 就把容器杀了重建。关键理解存活探针失败 “这进程彻底废了得重启”。不是“暂时有点忙”。Readiness Probe就绪探针—— “能接客吗”决定要不要把流量切过来。应用启动需要加载配置、连数据库、暖缓存这段时间虽然进程在跑但还没准备好服务。就绪探针失败时Pod 的 IP 会从 Service 的 EndpointSlice 中移除流量不会打过来。关键理解就绪探针失败 ≠ 重启只是暂时“靠边站”。Startup Probe启动探针—— “启动完了吗”解决“慢启动被误杀”的问题。有些 Java/Spring Boot 应用启动要一两分钟。如果只配了存活探针kubelet 在initialDelaySeconds之后就开始检查此时应用还没起来→探针失败→容器被重启→永远起不来。启动探针的作用是只要启动探针还没成功存活探针和就绪探针就不干活。三种探针的执行顺序startupProbe启动时→ 成功后 → livenessProbe readinessProbe持续运行。搞清楚三种探针各自管什么之后接下来看怎么“检查”——Kubernetes 为所有探针统一提供了四种检查手段按需选用即可。探针的四种实现方式不管哪种探针都支持四种检查方式方式适用场景关键配置exec容器内有检查脚本或命令command数组返回 0 表示成功httpGet提供 HTTP/HTTPS 服务的应用path、port、scheme、httpHeaderstcpSocket只关心端口是否监听的场景portgrpcgRPC 服务Kubernetes v1.24 引入 Betav1.27 正式 GAport、service生产环境最常用的是httpGet——让应用自己暴露/healthz或/ready端点由应用决定自己是否健康。生产级配置实战公共参数说明所有探针共享以下参数参数默认值说明initialDelaySeconds0容器启动后多久开始探测注意存在 startupProbe 时行为有变见下方警告periodSeconds10每隔多少秒探测一次timeoutSeconds1探测超时时间successThreshold1从失败变成功所需连续成功次数。对于 livenessProbe 和 startupProbe此值必须为 1API 强制约束readinessProbe 可设为 1比如在依赖不稳定时避免状态频繁抖动failureThreshold3判定为失败所需的连续失败次数⚠️关键行为差异initialDelaySeconds 的隐藏逻辑如果 Pod 中定义了startupProbe则livenessProbe和readinessProbe的探测动作会等到startupProbe成功之后才开始执行但initialDelaySeconds本身仍从容器启动时刻开始计时。这意味着实际首次探测时间 startupProbe耗时 initialDelaySeconds。如果没有定义startupProbe则initialDelaySeconds从容器启动时刻开始计时首次探测就在这个时间点发生。两者行为不同配置时务必区分。我见过有人同时配了启动探针又把存活探针的initialDelaySeconds设成 120 秒以为双保险实际上那 120 秒是从容器启动开始计时的而探测动作要等启动探针成功后才触发。最终首次探测远晚于他的预期——完全不是他想要的效果。完整配置示例下面是一个生产级 Deployment 配置涵盖三种探针apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: app image: my-app:1.0.0 ports: - containerPort: 8080 # ---------- 启动探针 ---------- startupProbe: httpGet: path: /healthz/startup # 专用于启动检查的端点 port: 8080 initialDelaySeconds: 10 # 可选项主要用于规避极端情况如容器瞬间启动但端口尚未监听 periodSeconds: 5 # 核心启动窗口靠下面这个 failureThreshold 控制 failureThreshold: 30 # 30 * 5 150 秒启动窗口 timeoutSeconds: 5 # ---------- 存活探针 ---------- livenessProbe: httpGet: path: /healthz/liveness # 轻量级端点只检查进程是否卡死 port: 8080 periodSeconds: 30 # 别太频繁 timeoutSeconds: 5 failureThreshold: 3 # 连续 3 次失败才重启90 秒 # ---------- 就绪探针 ---------- readinessProbe: httpGet: path: /healthz/readiness # 检查依赖是否就绪 port: 8080 periodSeconds: 10 # 可以频繁一点 timeoutSeconds: 5 failureThreshold: 3参数调优的核心公式启动探针的容忍时间 failureThreshold × periodSeconds。上面的例子里30 × 5 150秒给应用 150 秒完成启动。如果你的应用启动需要 60 秒设failureThreshold: 20、periodSeconds: 5就是 100 秒窗口留点余量。我的经验公式启动探针容忍时间 应用正常启动时间 × 1.5存活探针容忍时间 应用正常响应 P95 延迟 × 2 × failureThreshold验证方法1. 查看 Pod 状态kubectl get pods正常状态应该是RunningRESTARTS列应该为 0 或稳定不变。2. 查看探针事件附“怎么看”解读kubectl describe pod pod-name输出中会包含探针相关的 Events。如果探针失败会看到类似Warning Unhealthy 10s kubelet Liveness probe failed: HTTP probe failed with status code: 500怎么看事件里会明确标明是Liveness、Readiness还是Startup。如果你看到Readiness频繁失败但Liveness正常说明应用依赖有问题但容器没被重启——这是预期行为说明探针在正确工作不要慌去修依赖就行。反之如果Liveness失败容器会被重启你得赶紧看日志。3. 查看容器日志kubectl logs pod-name --previous # 查看上一次崩溃的日志4. 实时监控探针状态kubectl get pod pod-name -o yaml | grep -A 10 -E livenessProbe|readinessProbe|startupProbe我踩过的五个坑坑一存活探针检查外部依赖❌错误做法存活探针去检查数据库、Redis、消息队列是否可达。livenessProbe: httpGet: path: /healthz # 这个端点会检查 DB、Redis、MQ...后果数据库维护期间短暂不可用→所有 Pod 存活探针同时失败→kubelet 重启所有容器→雪崩。✅正确做法存活探针只检查进程自身是否卡死如死锁检测就绪探针才检查外部依赖。livenessProbe: httpGet: path: /healthz/liveness # 只检查本进程状态 readinessProbe: httpGet: path: /healthz/readiness # 检查 DB、缓存等依赖官方文档明确警告“Liveness probes must be configured carefully to ensure that they truly indicate unrecoverable application failure”。外部依赖暂时不可用是可恢复的不该触发重启。官方文档还特别指出存活探针实现不当会导致级联故障在高负载下容器被重启、客户端请求失败、剩余 Pod 负载加重——“Incorrect implementation of liveness probes can lead to cascading failures. This results in restarting of container under high load; failed client requests as your application became less scalable; and increased workload on remaining pods due to some failed pods.” 这段话我每次给团队培训都会念一遍不是教条是血泪教训。坑二慢启动应用没有配启动探针❌错误做法只配存活探针initialDelaySeconds设了个固定值比如 30 秒。livenessProbe: initialDelaySeconds: 30 # 假设 30 秒一定能起来后果某次发布后启动变慢到 45 秒→存活探针在第 30 秒开始检查→失败→kubelet 在第 60 秒重启→永远起不来。✅正确做法用启动探针不要依赖initialDelaySeconds。startupProbe: httpGet: path: /healthz/startup port: 8080 failureThreshold: 30 periodSeconds: 5 # 最多等 150 秒坑三存活探针和就绪探针用同一个端点❌错误做法livenessProbe: httpGet: path: /healthz readinessProbe: httpGet: path: /healthz # 同一个端点后果应用启动过程中/healthz 返回 503→存活探针失败→容器被重启。应用永远启动不起来。✅正确做法区分端点语义——/healthz/liveness只返回 200进程活着就返回 200/healthz/readiness依赖就绪才返回 200否则返回 503/healthz/startup启动完成才返回 200坑四隐藏款terminationGracePeriodSeconds 太长导致重启“不果断”探针失败了kubelet 不会直接SIGKILL而是先发SIGTERM等待terminationGracePeriodSeconds默认30 秒再强杀。如果你的应用捕获了SIGTERM但清理逻辑很慢比如在等数据库连接池关闭、在等 HTTP 长连接排干这 30 秒会被计入“从探针失败到容器真正重启”的时间。在高频探测场景下这段时间内新请求依然可能被路由过来尤其是就绪探针还没来得及更新造成间歇性 5xx。我的做法如果你的应用对SIGTERM没有复杂的状态清理需求可以酌情调小这个值spec: terminationGracePeriodSeconds: 10 # 默认 30按需调短注意调短的前提是你确认业务能接受这么短的优雅终止时间。有状态应用如正在处理事务的 worker请谨慎。坑五隐藏款探针超时被 CPU 节流Throttling坑了这个坑我排查了整整一个下午。现象是/healthz逻辑极简单只读一个内存标志位timeoutSeconds已经设到了 5 秒但还是偶发超时。查了一圈发现根因是CPU Limit 设得太小。当容器的 CPU Limit 较低且流量上来时CFS 会对容器进行节流Throttling。此时你的/healthz请求虽然逻辑简单但可能因为线程被调度出去实际响应时间远超timeoutSeconds。解决方法先用kubectl top pod看实时 CPU 使用率检查监控指标里的container_cpu_cfs_throttled_seconds_total如果这个值在持续增长说明 Limit 太紧了适当调大 CPU Limit或者把/healthz/liveness端点做得更轻量比如不经过业务线程池直接在 Netty/框架层返回这个不算探针配置错误但它会导致“探针配置看起来完全正确但就是偶尔重启”的玄学现象。希望你不用像我一样花一下午才找到它。总结五个核心要点收个尾三探针各司其职liveness 重启决策readiness 流量决策startup 保护慢启动。别混用。存活探针只查进程自身外部依赖交给就绪探针——这是防止雪崩的关键。慢启动应用必须配启动探针别依赖initialDelaySeconds硬编码。探针失败了不是秒杀terminationGracePeriodSeconds会影响重启速度按需调优。探针超时不一定是你配置的事检查 CPU Limit 是否过小导致节流。最后送大家一句话探针配得好半夜睡得好探针配得烂凌晨三点见。如果觉得有用欢迎分享给更多正在被探针折磨的同事。