在 Kubernetes 中,Pod 的生命周期管理并不只是”启动容器、运行服务”这么简单。集群需要一种机制来实时判断应用是否正常运行、是否准备好接收流量、以及启动过程是否还在进行中——这正是 健康检查探针(Probes) 的职责。然而,探针配置不当是生产环境中最常见的问题来源之一:一次错误的 Liveness 配置可能导致 Pod 被反复杀死,一次遗漏的 Readiness 配置可能让流量打到未就绪的实例上。本文将从底层原理到生产实践,系统梳理三种探针的正确使用方式。
一、三种探针的职责与生命周期
Kubernetes 提供三种探针,它们在 Pod 生命周期中扮演不同角色,绝不能混用:
| 探针类型 | 失败后果 | 核心职责 | 何时使用 |
|---|---|---|---|
| Startup Probe | 容器被杀死并重启 | 判断应用是否已完成初始化 | 启动时间较长的应用 |
| Liveness Probe | 容器被杀死并重启 | 判断应用是否处于健康运行状态 | 应用可能死锁或无响应时 |
| Readiness Probe | 从 Service Endpoints 中移除 | 判断应用是否准备好接收流量 | 所有对外提供服务的容器 |
1.1 Startup Probe:给慢启动应用一个缓冲期
Startup Probe 是 Kubernetes 1.18 引入的功能,1.20 进入稳定版。它解决的核心问题是:如何区分”应用还在初始化”和”应用已经挂了”。
在 Startup Probe 成功之前,Kubernetes 不会执行 Liveness 和 Readiness 探针。这意味着你可以为 Startup Probe 设置一个很长的
1 | failureThreshold × periodSeconds |
窗口,同时为 Liveness Probe 设置一个很短的超时时间——两者不冲突。
1
2
3
4
5
6
7 startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30 # 允许失败30次
periodSeconds: 10 # 每10秒检测一次
# 最长等待 30 × 10 = 300秒(5分钟)完成初始化
1.2 Liveness Probe:重启还是不重启,这是个问题
Liveness Probe 失败时,kubelet 会杀死容器并根据 restartPolicy 重启。这是最危险的探针——配置错误的 Liveness Probe 比没有 Liveness Probe 更可怕。
关键原则:Liveness Probe 应该只检测那些”只有重启才能恢复”的故障,比如死锁、线程池耗尽、内存损坏。如果故障可以通过外部恢复(比如数据库连接断开后自动重连),就不应该触发 Liveness Probe 失败。
1.3 Readiness Probe:流量控制的第一道防线
Readiness Probe 失败时,Pod 会从 Service 的 Endpoints 列表中移除,但容器不会被杀死也不会重启。这是最安全的探针,也是最应该被广泛使用的探针。
典型场景:应用启动后需要从远程加载配置、预热缓存、建立数据库连接池——在这些准备工作完成之前,不应该有流量进入。
二、探针的检测机制与参数详解
每种探针都支持三种检测方式,它们的适用场景各不相同:
2.1 HTTP GET
最常用的检测方式。kubelet 向指定路径发送 HTTP GET 请求,响应状态码在 200-399 之间即为成功。
1
2
3
4
5
6
7
8
9
10
11
12 livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: probe-token
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
注意:
1 | httpHeaders |
不是必须的,但在某些需要鉴权的场景下很有用。
2.2 TCP Socket
适用于非 HTTP 协议的服务,如数据库、Redis、gRPC 等。只要 TCP 连接能建立就算成功。
1
2
3
4
5 livenessProbe:
tcpSocket:
port: 3306
periodSeconds: 10
failureThreshold: 3
2.3 Exec
在容器内执行命令,退出码为 0 即为成功。适用于需要复杂逻辑判断的场景。
1
2
3
4
5
6
7
8 livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -h localhost -U postgres
periodSeconds: 5
failureThreshold: 3
2.4 gRPC 健康检查(Kubernetes 1.24+)
Kubernetes 1.24 原生支持 gRPC 健康检查协议,无需额外 sidecar:
1
2
3
4 livenessProbe:
grpc:
port: 50051
periodSeconds: 10
2.5 关键参数解读
| 参数 | 默认值 | 含义 | ||
|---|---|---|---|---|
|
0 | 容器启动后等待多少秒才开始探测 | ||
|
10 | 探测间隔 | ||
|
1 | 单次探测超时时间 | ||
|
3 | 连续失败多少次才判定为失败 | ||
|
1 | 连续成功多少次才判定为成功 |
时间计算公式:从容器启动到被判定为失败的窗口 =
1 | initialDelaySeconds + failureThreshold × periodSeconds |
。这个公式在设置 Startup Probe 时尤其重要。
三、生产环境中的五大避坑实践
3.1 避坑一:永远不要让 Liveness Probe 检查外部依赖
这是最常见也是最致命的错误。很多人把数据库连通性检查放在 Liveness Probe 中:
1
2
3
4
5 # 错误做法:数据库不可用时整个 Pod 被杀死
livenessProbe:
httpGet:
path: /healthz # 内部检查了数据库连接
port: 8080
当数据库短暂不可用时,所有 Pod 的 Liveness Probe 同时失败,集群同时重启所有实例,导致雪崩效应。更糟糕的是,重启后的 Pod 依然连不上数据库,又会被杀死——进入无限重启循环。
正确做法:外部依赖的连通性应该放在 Readiness Probe 中检查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # 正确做法:数据库不可用时仅从 Endpoints 移除
readinessProbe:
httpGet:
path: /ready # 检查数据库连接和缓存预热状态
port: 8080
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz # 只检查进程本身是否存活(死锁检测等)
port: 8080
periodSeconds: 15
failureThreshold: 3
3.2 避坑二:用 Startup Probe 替代 initialDelaySeconds
传统做法是用
1 | initialDelaySeconds |
来等待应用启动,但这是一个固定等待时间——太短会导致启动慢的实例被误杀,太长则浪费启动快的时间。
1
2
3
4
5
6
7
8 # 旧做法:固定等待30秒,不管应用实际启动多快
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
推荐做法:使用 Startup Probe 实现自适应等待:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # 新做法:最快0秒通过,最慢等300秒
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
Startup Probe 通过后立即开始 Liveness 检测,不浪费一秒钟。同时 Liveness 的
1 | failureThreshold |
可以设得很严格,因为不用担心和启动时间冲突。
3.3 避坑三:Readiness Probe 与滚动更新的配合
在 Deployment 滚动更新时,Readiness Probe 决定了新 Pod 何时被加入 Endpoints 以及旧 Pod 何时被移除。如果新 Pod 没有 Readiness Probe,它会在容器一启动就被加入 Endpoints,导致用户请求到未就绪的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 # 完整的滚动更新安全配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 不允许有任何Pod不可用
maxSurge: 1 # 最多多创建1个Pod
template:
spec:
containers:
- name: app
image: my-app:v2
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
注意
1 | preStop |
钩子的作用:当 Pod 被终止时,kubelet 先执行 preStop(这里 sleep 10 秒),同时 kube-apiserver 已经将 Pod 标记为 Terminating。但 Kubernetes 的 Endpoints 控制器和 kube-proxy 的 iptables/ipvs 更新是异步的,10 秒的缓冲可以确保 iptables 规则更新完毕后再真正停止进程,避免流量打到已关闭的容器上。
3.4 避坑四:探针端点本身必须有超时保护
如果你的健康检查端点本身有阻塞风险(比如查询数据库),即使
1 | timeoutSeconds |
设了 5 秒,如果应用线程池满了,探针请求可能排队等不到处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 # 健康检查端点的正确实现(Go 示例)
func healthzHandler(w http.ResponseWriter, r *http.Request) {
// 设置独立的短超时上下文
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
// 只做轻量级检查,不查数据库
select {
case <-ctx.Done():
w.WriteHeader(http.StatusServiceUnavailable)
default:
// 检查内存使用率等轻量指标
if isDeadlocked() {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
}
func readyHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
// Readiness 可以检查外部依赖
if !db.PingContext(ctx) {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
核心原则:Liveness 端点只做本地轻量检查,Readiness 端点可以检查外部依赖但必须有超时。
3.5 避坑五:注意 Java 应用的探针陷阱
Java 应用是探针问题的重灾区,原因有两个:
- JVM 堆内存达上限时的 Full GC 停顿:Full GC 可能暂停应用数秒到数十秒,导致探针超时失败。如果 Liveness Probe 的
1timeoutSeconds
设为 1 秒,一次 Full GC 就能杀死整个 Pod。
- 类加载和 Spring Bean 初始化耗时:Spring Boot 应用冷启动可能需要 30-60 秒,远超默认的探针窗口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 # Java 应用的推荐探针配置
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 60 # 给足启动时间
periodSeconds: 5 # 最长等 60 x 5 = 300秒
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 15 # 不要太频繁
timeoutSeconds: 10 # 给 GC 停顿留余量
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
Spring Boot Actuator 从 2.0 开始支持将 Liveness 和 Readiness 拆分为独立端点,配合 Kubernetes 的探针机制使用非常方便。启用方式:
1
2
3
4
5
6
7
8
9
10
11
12 # application.yml
management:
endpoint:
health:
probes:
enabled: true
show-details: always
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
四、不同类型应用的探针配置模板
不同类型的应用对探针的需求差异很大,下面给出几种典型场景的推荐配置。
4.1 无状态 Web 服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 12
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
4.2 数据库(如 PostgreSQL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 startupProbe:
exec:
command: ["pg_isready", "-h", "localhost", "-U", "postgres"]
failureThreshold: 30
periodSeconds: 5
livenessProbe:
exec:
command: ["pg_isready", "-h", "localhost", "-U", "postgres"]
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- /bin/sh
- -c
- |
pg_isready -h localhost -U postgres &&
psql -h localhost -U postgres -c "SELECT 1" > /dev/null
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
4.3 任务处理器(不对外服务)
1
2
3
4
5
6
7
8
9
10
11
12
13 # 任务处理器不需要 Readiness Probe
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 20
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
# 不设置 readinessProbe - 该 Pod 不需要接收 Service 流量
五、探针故障的应急排查手册
当 Pod 频繁重启或不在 Endpoints 中出现时,按以下步骤排查:
5.1 查看 Pod 事件
1 kubectl describe pod my-app-7d4f8b6c5-x2k9j | grep -A5 "Events|Last State"
重点关注
1 | Last State |
中的
1 | Reason |
和
1 | Exit Code |
。如果 Reason 是
1 | ProbeFailed |
且 Exit Code 为 137(SIGKILL),说明是探针失败导致的强制终止。
5.2 查看探针日志
kubelet 的探针日志不在容器内部,需要查看节点上的 kubelet 日志:
1
2 # 在 Pod 所在节点上
journalctl -u kubelet | grep "probe" | grep "my-app"
你会看到类似这样的输出:
1
2 kubelet: Liveness probe failed: HTTP probe failed with statuscode: 503
kubelet: Readiness probe failed: Get "http://10.244.1.5:8080/ready": context deadline exceeded
5.3 手动执行探针命令
1
2
3
4
5 # 对于 Exec 探针
kubectl exec my-app-7d4f8b6c5-x2k9j -- /bin/sh -c "pg_isready -h localhost"
# 对于 HTTP 探针
kubectl exec my-app-7d4f8b6c5-x2k9j -- wget -qO- http://localhost:8080/healthz
5.4 临时调大探针窗口
在紧急情况下,可以临时放大
1 | failureThreshold |
或
1 | periodSeconds |
来避免 Pod 被反复杀死,同时排查根因:
1 kubectl patch deployment my-app -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","livenessProbe":{"failureThreshold":10,"periodSeconds":30}}]}}}}'
六、进阶:自定义指标驱动的探针
在某些场景下,简单的 HTTP 200 或 TCP 连通性检查不足以反映应用的真实健康状态。比如:
- 消息队列消费者的消费延迟超过阈值时应该暂时停止接收新消息
- 连接池使用率超过 90% 时应该标记为 Not Ready
- GC 停顿时间超过阈值时应该触发 Liveness 失败
这些场景需要将应用内部指标与探针端点结合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 # Python Flask 示例:基于连接池状态的 Readiness 探针
@app.route('/ready')
def readiness():
pool_usage = db_connection_pool.usage_ratio()
lag = message_queue.consumer_lag()
if pool_usage > 0.9:
return 'Connection pool exhausted', 503
if lag > MAX_ACCEPTABLE_LAG:
return 'Consumer lag too high', 503
return 'OK', 200
@app.route('/healthz')
def liveness():
# Liveness 只检查进程是否死锁
if thread_pool.is_deadlocked():
return 'Deadlock detected', 500
return 'OK', 200
这种模式让你可以用 Readiness Probe 实现优雅降级:当应用暂时无法处理更多请求时,自动从负载均衡中摘除,等压力降低后再自动恢复,全程不需要重启。
总结
探针虽小,却是 Kubernetes 中最关键的流量控制和容错机制之一。正确的探针配置能让集群在故障时优雅降级,而错误的配置则会把小问题放大成全局灾难。核心原则可以总结为三条:
- Liveness 要保守:只检测不重启就无法恢复的故障,永远不检查外部依赖
- Readiness 要积极:所有对外服务都应该配置,宁可误报 Not Ready 也不要让未就绪的实例接收流量
- Startup 要大方:给慢启动应用足够的缓冲时间,同时让 Liveness 保持严格
牢记这三条原则,你的 Kubernetes 应用在面对故障时将具备真正的自愈能力,而不是在重启循环中反复挣扎。
汤不热吧