欢迎光临

Kubernetes 健康检查探针完全指南:Liveness、Readiness 与 Startup 的正确配置与生产避坑实践

在 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 关键参数解读

参数 默认值 含义
1
initialDelaySeconds
0 容器启动后等待多少秒才开始探测
1
periodSeconds
10 探测间隔
1
timeoutSeconds
1 单次探测超时时间
1
failureThreshold
3 连续失败多少次才判定为失败
1
successThreshold
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 的
    1
    timeoutSeconds

    设为 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 应用在面对故障时将具备真正的自愈能力,而不是在重启循环中反复挣扎。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » Kubernetes 健康检查探针完全指南:Liveness、Readiness 与 Startup 的正确配置与生产避坑实践
分享到: 更多 (0)