探针(Probes)决定了:
- Pod 是否 接收流量(readiness)
- 容器是否会被 自动重启(liveness)
- 启动慢的服务是否会被 过早判死(startup)
很多“发布一上来就抖/经常重启”的问题,根因常在探针语义用错。
先看故障长什么样
探针问题很少以“探针配置错了”这个名字出现。它通常伪装成下面几类事故:
- 发布后新 Pod 的 EndpointSlice 条目长期不是
ready=true,Service 没有可用后端。 - 应用启动慢,被 liveness 提前杀掉,形成重启风暴。
- 下游依赖短暂抖动,liveness 跟着失败,把本来可恢复的问题放大成集群级重启。
- readiness 过宽,Pod 还没准备好就接流量;readiness 过严,Pod 明明可用却长期被摘除。
排查时先问一个问题:这个检查到底是在回答“能不能接流量”,还是“该不该重启”?如果这两个问题由同一个接口回答,后面大概率会出事。
最常见的误判
- 把 liveness 当流量开关,结果依赖抖动时把容器杀掉。
- 用很深的业务链路做 readiness,导致外部系统抖动时本服务也被摘除。
- 用
initialDelaySeconds硬猜启动时间,而不是用 startupProbe 保护慢启动。 - 只看 Pod
Running,不看 readiness condition 和 endpoints。
心智模型(最重要的三句话)
- Readiness:现在适合接流量吗?
- Liveness:进程是不是卡死/不可恢复,需要重启吗?
- Startup:启动期给我额外时间,先别用 liveness 折腾我。
最常见的坑:用 liveness 充当“流量开关”
如果你把 liveness 当成“没 ready 就死给你看”,你会得到:
- 还在 warmup 或迁移的 Pod 被不断重启
- 发布速度变慢,错误率变高
- 甚至形成“重启风暴”
正确做法:
- 用 readiness 把 EndpointSlice 中的端点标记为未就绪,使常规 Service 流量不再选择它
- 用 startupProbe 保护启动期
- 用 liveness 只处理“重启能解决的卡死/异常”
一个靠谱的默认配置(HTTP 服务)
startupProbe:
httpGet:
path: /startupz
port: 8080
failureThreshold: 30
periodSeconds: 2
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
这套配置的核心是:启动期先过 startup,再谈 liveness;流量交给 readiness 控制。
/readyz、/livez 应该检查什么
/readyz:能否正确服务请求(“现在”)
readiness 应该在以下场景失败:
- 依赖还没连上(DB、缓存、上游服务)
- 迁移未完成(但你又不希望接流量)
- 预热未完成(关键缓存/配置未加载)
readiness 的本质是“业务可用性”,不是“进程是否活着”。
/livez:进程是否卡死/不可恢复(“重启能救”)
liveness 适合检查:
- 死锁/事件循环卡死
- 内部状态不可恢复(严重异常)
不推荐:把 DB 可用性作为 liveness 的硬条件。DB 抖一下你就把所有 Pod 重启一遍,往往会把事故扩大。
探针类型:HTTP / TCP / gRPC / exec(用得越简单越好)
Kubernetes 支持:
httpGet:最推荐(语义清晰、可观测)tcpSocket:只检查端口是否能连(语义弱)grpc:调用标准 gRPC Health Checking Protocolexec:在容器里跑命令(灵活但可能昂贵)
优先 HTTP 探针
Web 服务尽量暴露小而快的检查接口:
/livez:进程是否健康/readyz:现在能否接流量
readiness 检查如果偶尔要跑 5-10 秒,通常是把过深的依赖检查塞进了探针。
TCP 探针什么时候用
它只能证明“端口能建立连接”,不能证明“请求会成功”。简单代理或没有 HTTP 接口的旧系统可以用,但要接受它的语义更弱。
gRPC 探针什么时候用
gRPC 探针从 Kubernetes 1.27 起进入 Stable,适合已经实现标准 gRPC Health Checking Protocol 的服务:
readinessProbe:
grpc:
port: 50051
service: readiness
periodSeconds: 5
timeoutSeconds: 2
port 必须写数字,不能使用命名端口。基础模式使用明文连接;如果服务只开放 TLS,先核对当前集群版本与对应 feature gate,不要直接照搬 HTTP 探针配置。
exec 探针什么时候用
exec 可以检查文件、socket、进程状态,但它:
- 在容器内执行;
- 可能较慢;
- 每个 Pod、每几秒执行一次,规模变大后会带来额外负载。
如果一定要用,命令必须轻量、快速。
startupProbe 比 initialDelaySeconds 更靠谱
传统做法用 initialDelaySeconds 延迟探针启动,但缺点明显:
- 延迟是固定值,启动快也要等
- 启动慢超过延迟就被误判
startupProbe 更智能:
- 在 startupProbe 成功之前,liveness 和 readiness 都不会开始
- 启动快就快通过,启动慢就给时间
实用换算:
最长允许启动时间 ≈
failureThreshold * periodSeconds
例如希望给 60 秒启动窗口,可以:
periodSeconds: 2failureThreshold: 30
发布友好视角(Mermaid)
flowchart LR A[Pod 启动] --> B{startupProbe 通过?} B -- 否 --> A B -- 是 --> C{readiness 通过?} C -- 否 --> D[不接流量] C -- 是 --> E[接收流量] E --> F{liveness 通过?} F -- 否 --> A F -- 是 --> EReadiness 在发布中到底影响什么
Deployment 滚动更新时:
- 新 Pod 启动;
- readiness 决定 EndpointSlice 端点什么时候变为
ready=true; - 旧 Pod 按策略终止。
如果 readiness 太宽:配置还没加载、缓存还没预热、迁移还没完成,Pod 就开始接请求,于是“rollout succeeded”但错误率上升。如果 readiness 太严:DB 抖 1 秒就把所有新 Pod 摘掉,发布卡住,甚至形成级联故障。
一个更稳的边界是:readiness 表示“现在能正确处理请求”,允许短时依赖抖动;liveness 只判断“重启是否有帮助”。
与优雅下线配合(否则探针也救不了)
探针只决定“是否在 endpoints 里”,但如果 Pod 退出太粗暴,还是会有错误。
建议:
- 合理设置
terminationGracePeriodSeconds - 收到 SIGTERM 立即让 readiness 失败(快速摘流量)
- 等待 in-flight 请求完成后再退出
- 必要时加
preStop做 drain/flush
调参避免抖动(timeout/threshold/period)
常见问题:
timeoutSeconds太小(真实 p99 可能 > 1s)periodSeconds太密(探针负担大,反而影响业务)failureThreshold太小(一次抖动就摘流量/重启)
实践经验:
- readiness:
period 5s、timeout 1-2s、failureThreshold 3 - liveness:
period 10s、timeout 1-2s、failureThreshold 3 - startup:按启动最长时间换算
Liveness 别绑定外部依赖
这一点值得反复强调:如果 DB 一抖,liveness 就失败,你会把一次依赖故障放大成全集群重启风暴。
更合理的分工是:
- readiness 在依赖不可用时摘流量;
- liveness 只在进程真的卡死或不可恢复时失败。
排查探针失败(必备命令)
先看 endpoints,再看 Pod 状态:
kubectl get endpointslice -n <ns> -l kubernetes.io/service-name=<svc> -o wide
kubectl describe pod -n <ns> <pod>
kubectl logs -n <ns> <pod> -c <container> --previous
kubectl get events -n <ns> --sort-by=.lastTimestamp
在容器内部验证:
kubectl exec -n <ns> -it <pod> -- sh
curl -sS -i http://127.0.0.1:8080/readyz
HTTP、TCP 和 gRPC 探针由 kubelet 直接访问 Pod IP,不经过 Service。Pod 内 curl 127.0.0.1 成功只能证明处理器可用;如果探针仍失败,继续检查应用是否只监听 loopback、端口与路径是否一致,以及基于 Host 的路由是否接受 kubelet 请求。
如果镜像是 distroless 没有工具,用 ephemeral container(kubectl debug)来跑 curl 更安全。
可观测性建议:让 readiness 失败有“原因”
即使探针只看状态码,你也应该在服务内部记录:
- 哪个依赖导致 readiness 失败
- 失败次数与持续时间
- 失败发生在发布/扩容/依赖抖动的哪个阶段
如果 /readyz 是组合检查(缓存、DB、队列),建议返回带 reason code 的 JSON。探针虽然只看状态码,但运维和监控能立刻看到具体原因。
这样你才能把“探针失败”从现象变成可行动的根因。
上线前自检
- 启动慢的服务启用 startupProbe
- readiness 控制接流量,liveness 控制重启
- liveness 不依赖外部依赖(避免重启风暴)
- timeouts/thresholds/period 贴近真实延迟分布
- 下线流程与 readiness 配合(优雅摘流量)
探针配置的三个判断
Q: readiness 和 liveness 要用同一个接口吗? A: 通常不建议。readiness 关注依赖与可接流量,liveness 只需要快速判断是否“卡死”。
Q: 启动很慢怎么办?
A: 用 startupProbe,或提高 failureThreshold/period,别用很长的 initialDelaySeconds。
Q: readiness 失败会重启吗? A: 不会。只有 liveness 失败才会触发重启。