探针(Probes)决定了:
- Pod 是否 接收流量(readiness)
- 容器是否会被 自动重启(liveness)
- 启动慢的服务是否会被 过早判死(startup)
很多“发布一上来就抖/经常重启”的问题,本质上不是业务逻辑崩,而是探针语义用错了。
先看故障长什么样
探针问题很少以“探针配置错了”这个名字出现。它通常伪装成下面几类事故:
- 发布后新 Pod 一直不进 endpoints,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 把 Pod 从 Service endpoints 中移除(不接流量)
- 用 startupProbe 保护启动期
- 用 liveness 只处理“重启能解决的卡死/异常”
一个靠谱的默认配置(HTTP 服务)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 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 / exec(用得越简单越好)
Kubernetes 支持:
httpGet:最推荐(语义清晰、可观测)tcpSocket:只检查端口是否能连(语义弱)exec:在容器里跑命令(灵活但可能昂贵)
建议:
- 优先 HTTP endpoints
- TCP 只作为无 HTTP 的兜底
- exec 慎用(集群大了会变成“定时在所有 Pod 内执行脚本”,成本和风险都更高)
startupProbe 比 initialDelaySeconds 更靠谱
传统做法用 initialDelaySeconds 延迟探针启动,但缺点明显:
- 延迟是固定值,启动快也要等
- 启动慢超过延迟就被误判
startupProbe 更智能:
- 在 startupProbe 成功之前,liveness 不会生效
- 启动快就快通过,启动慢就给时间
实用换算:
最长允许启动时间 ≈
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 -- 是 --> E与优雅下线配合(否则探针也救不了)
探针只决定“是否在 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:按启动最长时间换算
排查探针失败(必备命令)
kubectl describe pod <pod>
kubectl logs <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
如果镜像是 distroless 没有工具,用 ephemeral container(kubectl debug)来跑 curl 更安全。
可观测性建议:让 readiness 失败有“原因”
即使探针只看状态码,你也应该在服务内部记录:
- 哪个依赖导致 readiness 失败
- 失败次数与持续时间
- 失败发生在发布/扩容/依赖抖动的哪个阶段
这样你才能把“探针失败”从现象变成可行动的根因。
上线前自检
- 启动慢的服务启用 startupProbe
- readiness 控制接流量,liveness 控制重启
- liveness 不依赖外部依赖(避免重启风暴)
- timeouts/thresholds/period 贴近真实延迟分布
- 下线流程与 readiness 配合(优雅摘流量)
参考链接
常被忽略的问题
Q: readiness 和 liveness 要用同一个接口吗? A: 通常不建议。readiness 关注依赖与可接流量,liveness 只需要快速判断是否“卡死”。
Q: 启动很慢怎么办?
A: 用 startupProbe,或提高 failureThreshold/period,别用很长的 initialDelaySeconds。
Q: readiness 失败会重启吗? A: 不会。只有 liveness 失败才会触发重启。