Kubernetes 探针(Probe)详解与生产环境配置实践
记录时间:2026-08-20
环境:K8s 1.28(信创 ARM64);Helm Charts:jar / tomcat / tongweb / vue 四类业务 chart(内部 chart 仓库);测试环境 argocd-apps / 生产环境 argocd-prod(ArgoCD 管理)
一、背景
最近看了一篇掘金上讲探针的文章,把 livenessProbe / readinessProbe / startupProbe 三大探针从头到尾捋了一遍,写得挺细的。光看文章不过瘾,于是顺手把测试环境和生产环境的 ArgoCD 配置都拉出来对比了一下,发现我们环境里其实已经有很完整的探针实践,但也有十几个服务的探针在测试环境是关着的。整理成一篇笔记,把文章要点和我们自己的配置串起来。
二、三大探针分别管什么
K8s 的探针本质是”服务健康状态的三层校验机制”,三种探针职责不同、失败后果也不同:
| 探针 | 管什么 | 失败后果 | 典型场景 |
|---|---|---|---|
livenessProbe(存活探针) |
容器是否活着 | 按 restartPolicy 重启容器 |
死锁、OOM、进程假死 |
readinessProbe(就绪探针) |
实例是否能用 | 从 Service Endpoints 摘除,不再收流量 | 依赖未就绪、初始化未完成 |
startupProbe(启动探针) |
慢启动保护 | 重启容器 | Java/Tomcat 启动 3 分钟以上的服务 |
一句话区分:liveness 管”生死”,readiness 管”流量”,startup 管”启动保护”。liveness 和 readiness 的失败后果不同——一个重启、一个摘流量,这俩别搞混。
startupProbe 比较特殊:它探测成功之前,liveness 和 readiness 都不会生效,所以它是专门给慢启动服务兜底的。
三、三种探测方式
| 方式 | 原理 | 适用场景 | 局限 |
|---|---|---|---|
exec |
容器内执行命令,退出码 0 为正常 | 复杂校验(依赖检查、文件存在性) | 依赖容器内装好命令(如 curl) |
httpGet |
请求 HTTP/HTTPS 端点,状态码 200-399 为正常 | Web 服务,能校验业务端口可用性 | 仅支持 HTTP(S) 协议 |
tcpSocket |
建 TCP 连接,连得上即正常 | 数据库、Redis 等非 HTTP 服务 | 只能判断端口存活,无法校验内部状态 |
选择原则:Web 服务优先 httpGet;端口监听类服务用 tcpSocket;需要复杂逻辑校验用 exec。
四、探针参数与黄金法则
所有探针共用同一套参数:
| 参数 | 含义 | 推荐值 |
|---|---|---|
initialDelaySeconds |
容器启动后延迟多久开始探测 | 略短于实际启动时间(如 Java 实际 40s 设 30-35s) |
periodSeconds |
探测间隔 | liveness 10-15s,readiness 5-8s |
timeoutSeconds |
单次探测超时 | httpGet/tcpSocket 1-3s,exec 2-5s |
successThreshold |
连续成功多少次算恢复 | 默认 1(readiness 滚动更新时也建议 1) |
failureThreshold |
连续失败多少次算故障 | liveness 3-5 次,readiness 2-3 次 |
terminationGracePeriodSeconds |
失败后的优雅终止时间 | 30-60s |
startupProbe 的启动窗口 = failureThreshold × periodSeconds,必须覆盖服务最慢一次的启动时间。
五、我们环境的探针配置
5.1 Chart 模板怎么渲染的
我们的 chart(jar / tomcat / tongweb / vue 四类)在 deployment 模板里统一渲染探针,probe.enabled 是总开关,各探针还有独立的 enabled 子开关:
# jar chart 的 deployment.yaml 模板(节选)
{{- if and .Values.probe .Values.probe.enabled }}
{{- if .Values.probe.startupProbe.enabled | default false }}
startupProbe:
{{- omit .Values.probe.startupProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.probe.livenessProbe.enabled | default false }}
livenessProbe:
{{- omit .Values.probe.livenessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- if .Values.probe.readinessProbe.enabled | default false }}
readinessProbe:
{{- omit .Values.probe.readinessProbe "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- end }}
vue chart 的模板更聪明一点:httpGet 配了但 path 为空时,会用 probePath 这个 include 自动补上路径(SPA 首页路径),values 里不用手写:
{{- if and $probe.httpGet (not $probe.httpGet.path) }}
{{- $probe = mergeOverwrite $probe (dict "httpGet" (mergeOverwrite $probe.httpGet (dict "path" (include "probePath" $)))) }}
{{- end }}
注意:模板里探针是
omit "enabled"后整体透传的,所以 values.yaml 里怎么写,最终 pod 里就是什么。
5.2 Java / Tongweb 服务标配:tcpSocket 三探针
测试和生产的大多数 Java 服务(如 szzx 的 qjksh、infostat、wmyc)配置完全一致,参数带注释的版本如下(测试环境写注释、生产环境没写,参数一个不差):
# argocd-apps/application/szzx/qjksh/values.yaml(测试,生产同参数)
probe:
enabled: true
startupProbe:
enabled: true
httpGet: null
tcpSocket:
port: http # 使用容器端口名称,自动引用 appConfig.port
initialDelaySeconds: 60 # 容器启动后 60s 开始检查
periodSeconds: 10 # 每 10s 检查一次
failureThreshold: 30 # 最多允许失败 30 次,总计 60s + (10s * 30) = 360s
# 存活探针:启动探针成功后接手,判断应用是否由于死锁等原因挂掉
livenessProbe:
enabled: true
tcpSocket:
port: http # 使用容器端口名称
initialDelaySeconds: 10 # 启动探针成功后,只需 10s 即可开始
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
# 就绪探针:启动探针成功后接手,判断应用是否可对外提供服务
readinessProbe:
enabled: true
tcpSocket:
port: http # 使用容器端口名称
initialDelaySeconds: 10 # 启动探针成功后,只需 10s 即可开始
periodSeconds: 20 # 就绪检查稍快一些,以便及时摘除故障节点
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 3
这套配置的思路:
- startupProbe 给足 360 秒窗口(60s + 10s×30):东方通/Tomcat 启动慢,最坏情况要 6 分钟,保证启动期间不被误杀。
- liveness 用
tcpSocket而非httpGet:Java 服务端口起来就算活,避免业务层偶发 5xx 触发重启(这点文章里也强调了,liveness 探测条件要”轻量无副作用”)。 - readiness 周期比 liveness 短(20s vs 30s):故障时能更快把实例从 Service 摘掉,滚动发布时新 pod 就绪判定也更及时。
port: http用的是容器端口名称,由模板自动引用appConfig.port,不用写死端口号,改端口不用动探针。
5.3 Vue 前端服务:httpGet 探针
前端服务(nginx 容器)用 httpGet,因为 nginx 是纯 HTTP 服务,直接请求页面最实在:
# argocd-apps/application/sccg/market-vue/values.yaml(节选)
probe:
enabled: true
readinessProbe:
enabled: true
httpGet:
path: '' # path 留空,模板自动补 SPA 首页路径
port: http
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 3
livenessProbe:
enabled: true
tcpSocket:
port: http
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3
timeoutSeconds: 3
startupProbe:
enabled: true
httpGet:
path: ''
port: http
failureThreshold: 30
periodSeconds: 10
timeoutSeconds: 3
注意这里 liveness 反而是 tcpSocket——nginx 挂不挂看端口就行,httpGet 留给 readiness 判断页面能不能访问,两边分工明确。生产环境的 gzsw-ui 等 vue 服务配置和测试完全一致。
5.4 测试与生产对比:kjds 口岸平台测试环境探针关闭
把两个环境的 values.yaml 全部扫了一遍(测试 82 个、生产 56 个 values.yaml 启用了至少一个探针),发现一个值得记录的差异:
kjds 的 bordertradeplatform 一组 14 个服务,测试环境 13 个服务探针被显式关闭——values.yaml 里都是 probe.enabled: false;生产环境 14 个服务全部配置了三探针(tcpSocket 版)。
# argocd-apps/application/kjds/bordertradeplatform/base-service/values.yaml(测试,探针关闭)
probe:
enabled: false
# argocd-prod/application/kjds/bordertradeplatform/base-service/values.yaml(生产,探针齐全)
probe:
enabled: true
startupProbe:
enabled: true
tcpSocket:
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 30
livenessProbe:
enabled: true
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
enabled: true
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 3
这一组里还有个特例:bordertradeplatform-web(前端服务)测试环境是开着的,但没配 startupProbe,而是我把 liveness 的 initialDelaySeconds 拉大到 240s(readiness 120s)来兜住慢启动——startup 窗口的另一种等价写法。生产环境我统一改成了标准的 startupProbe(120s + 10s×30)写法。
测试环境 13 个服务探针全关,是我当时拉长探针参数时一起关掉的,测试环境一直保持这个状态。测试环境没探针意味着:pod 挂了不重启、流量照常打到坏实例,排障时看不到探针事件,和生产的故障表现会不一样。
六、组合场景与 5 个坑
文章总结的典型组合:
| 场景 | 组合 | 关键参数 |
|---|---|---|
| Spring Boot 微服务 | startup + liveness + readiness | startup 24×10s=240s;liveness initialDelay 60s;readiness period 5s |
| MySQL | liveness + readiness | liveness tcpSocket 3306;readiness exec mysqladmin ping |
| Nginx | liveness + readiness | liveness exec pgrep nginx;readiness httpGet / |
| 依赖 MQ 的服务 | readiness + liveness | readiness 校验内部健康端点(含 MQ 连接),failureThreshold 2 |
5 个坑(对应到我们环境):
- 用 liveness 代替 readiness → 启动中的实例被塞流量,直接 5xx。我们的配置里两探针职责分明,没这个毛病。
- 慢启动服务不配 startupProbe → 启动慢的 Java 服务被 liveness 反复重启,起不来的同时疯狂消耗资源。我们全配了,startup 窗口 360s。
- 探测方式不当(tcpSocket 校验 Web 服务)→ 端口通但页面 500 也照样”健康”。我们 Java 用 tcpSocket 是刻意为之(liveness 轻量优先),但 readiness 其实可以更严格,后续可以评估给核心服务上 httpGet 健康端点。
- 参数过严(如 periodSeconds=2s)→ 探测本身占资源,还可能把抖动当故障。我们的参数都按黄金法则来。
- readiness 依赖外部不稳定服务(跨集群 DB)→ 上游一抖,实例被反复摘除,流量雪崩。这条要警惕:readiness 探针里的依赖校验别连跨集群的外部服务。
七、监控告警与排障
文章给出的探针监控指标(kube-state-metrics 提供):
| 指标 | 告警建议 |
|---|---|
kube_pod_probe_success{probe="liveness"} |
5 分钟成功率 <90% 告警 |
kube_pod_probe_success{probe="readiness"} |
5 分钟成功率 <80% 告警 |
kube_pod_probe_success{probe="startup"} |
启动后 3 分钟未成功告警 |
kube_pod_container_status_waiting_reason(ProbeFailed) |
持续 5 分钟以上告警 |
kube_pod_status_ready(0) |
持续 10 分钟以上告警 |
Grafana 可以直接导入官方 Dashboard “Kubernetes Pod Health”(ID 13105)。
排障流程(遇到探针失败时):
# 1. 看 Events,确认失败原因
kubectl describe pod <pod-name> -n <namespace>
# 关注 "Liveness probe failed: ..." / "Readiness probe failed: ..." 事件
# 2. 看日志,排查崩溃或依赖连接问题
kubectl logs --tail=100 <pod-name> -n <namespace>
# 3. 手动执行探测命令,复现问题
kubectl exec -it <pod-name> -n <namespace> -- curl -v http://127.0.0.1:8080/actuator/health
# 4. 确认是参数问题还是业务问题,再决定调整探针还是修业务
八、注意事项
probe.enabled是总开关:values.yaml 里三个探针都配了但总开关是false,一样不生效(测试 kjds base-service 就是这个状态,光看 probe 段容易误判)。httpGet.path留空:vue chart 模板会自动补 SPA 路径;其他 chart 透传,path 留空会请求/。- tcpSocket 探针的边界:端口通不代表业务健康,核心服务建议上 httpGet + 业务健康端点(如 Spring Boot Actuator),端点必须轻量、耗时 <1s。
- 探针不是越多越好:核心是”精准校验”,在探测准确性和资源消耗之间取平衡。
- 测试环境探针状态和生产不一致:kjds 测试环境 13 个服务探针全关是我拉长探针参数时一起处理的,这类差异会掩盖测试环境的问题表现——测试环境没探针意味着 pod 挂了不重启、流量照常打到坏实例,排障时看不到探针事件。
九、参考资料
- Kubernetes 探针详解:三大探针、参数、生产实践(掘金)
- K8s 官方文档 Configure Liveness, Readiness and Startup Probes:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/