三种探针各管什么,不要混用
readinessProbe 回答「现在能不能给我派流量」:失败时 kubelet 只把 Pod 从 Service 的 Endpoints 里摘掉,容器继续运行、不会重启。滚动更新时它决定新 Pod 何时接流量,是发布平滑的关键。
livenessProbe 回答「进程是不是已经废了」:失败达到阈值 kubelet 会杀掉容器并按重启策略重建。判断标准必须是「不重启就无法自愈」,比如死锁、事件循环卡死;依赖抖动、短时网络故障绝不能放进 liveness。
startupProbe 回答「还没起来,先别急着判死刑」:它成功之前,liveness 与 readiness 都不会被求值。慢启动应用靠它避免被 liveness 在启动途中反复杀掉,这是官方推荐替代「把 initialDelaySeconds 调得很大」的做法。
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30 # 最长容忍 150 秒启动窗口startupProbe 的预算怎么算
启动预算 = periodSeconds × failureThreshold(外加第一次探测前的 initialDelaySeconds)。想要 150 秒的启动容忍,就取 5 × 30。startupProbe 成功后,liveness 和 readiness 的计时才正式开始。
把预算覆盖「最坏情况下的冷启动」:JVM 需要加载类、连数据库、跑迁移。压测出的冷启动 P99 是个合理参考,但要给它余量,因为节点负载高时启动会变慢。预算过大只会让真正卡死的进程更晚被重启。
常见错误是把 startupProbe 和 liveness 指向同一个深度依赖接口。启动预算耗尽后 liveness 接管,依赖一抖动容器就被杀,形成「重启风暴」。让 /healthz 只检查进程自身,依赖状态交给 readiness。
startupProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30
# 检查点:所有依赖都在 healthz 之外;依赖检查只出现在 /readyz
kubectl exec -it deploy/api -- curl -s localhost:8080/healthz超时与周期:默认值的坑
timeoutSeconds 默认只有 1 秒,失败不会立刻重启,而是算作一次失败。GC 停顿或慢磁盘让处理耗时超过 1 秒时,健康检查接口本身会被误判失败。对 GC 停顿明显的 JVM 服务把它调到 3 到 5 秒。
periodSeconds 默认 10,配合默认 failureThreshold 3 意味着连续约 30 秒失败才重启。想更快发现死锁可以缩短周期,但探测本身有开销(尤其 exec 探针要在容器里 fork 进程),高频探测在节点压力大时反而成为噪声源。
exec 探针每次都会在容器内启动一个进程,命令越重开销越大。脚本里别做递归查找或网络请求,改用轻量的 HTTP 探针或 gRPC 探针更稳。successThreshold 对 liveness 固定为 1,只有 readiness 允许大于 1。
livenessProbe:
exec:
command: [cat, /tmp/healthy]
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
successThreshold: 2 # 连续两次成功才接流量定位探针失败的标准动作
先看事件,不要猜。`kubectl describe pod` 会直接打出 Liveness probe failed: Get "http://10.244.0.5:8080/healthz": context deadline exceeded 之类的原文,错误信息比推理可靠。
事件里出现 connection refused,通常是监听地址或端口不对:应用只绑了 127.0.0.1,或容器端口号写错。出现 404,说明路径写错或该路径只在特定路由组里注册。出现 context deadline exceeded,多半是 timeoutSeconds 太短。
然后在容器内手动复现探针请求,能区分「探针配错」和「应用真的有问题」。再对比 `kubectl get pod` 的 RESTARTS 列:如果 liveness 在反复重启而 readiness 只是失败,那是配置问题;如果 readiness 恢复后立刻又失败,看应用日志。
最后还要看探针端点自身的开销。很多服务把 /healthz 实现成会扫全表或发一次数据库查询的「深度健康检查」,数据库一抖动所有探针集体超时,一个外部故障被放大成全量 Pod 重启。三个端点应各自职责单一,深度诊断放到人工调用的接口上。
kubectl describe pod -l app=api | sed -n "/Events:/,$ p"
kubectl get pods -l app=api -o wide
kubectl logs <pod> --previous --tail=100 # 看崩溃前最后一批日志
kubectl exec -it <pod> -- curl -sv localhost:8080/healthz上线前的验证清单
第一项是启动预算:把 startupProbe 的 periodSeconds 乘 failureThreshold 算出来,确认它确实大于压测中冷启动的最差耗时,否则慢启动机器仍可能在就绪前被杀掉。
第二项是端点分工:确认 /healthz 不触碰外部依赖,/readyz 才检查依赖,并且三个探针指向的路径在应用里真实存在——路径写错是最常见也最容易在上线前发现的错误。
第三项是回退验证:临时把 livenessProbe 的路径改成一个必定返回 500 的地址,确认 Pod 会按预期重启,再改回正确值。这样能在上线前证明探针链路本身是通的,而不是等真出故障时才发现 kubelet 压根没在探测。
第四项是负载验证:在节点 CPU 打满的情况下观察就绪转换是否稳定。如果正常 CPU 使用率下 readiness 就开始抖动,说明 successThreshold 与 periodSeconds 的组合给出的容忍窗口太窄,需要放大或改用 startupProbe 式的渐进放宽。
# 快速验证探针链路是否真的生效
kubectl apply -f - <<YAML
apiVersion: v1
kind: Pod
metadata:
name: probe-check
spec:
containers:
- name: app
image: acme/api:1.4
livenessProbe:
httpGet: { path: /definitely-not-here, port: 8080 }
periodSeconds: 5
failureThreshold: 2
YAML
kubectl get pod probe-check -w # 观察是否按预期重启
kubectl delete pod probe-check