Kubernetes 中的 Liveness、Readiness 和 Startup Probe

1. 概览

当在 Kubernetes 上运行应用程序时,集群无法自动知道你的 pod 是否“活着”或“已准备好接收流量”——它必须通过 probe 来询问。本文解释了 liveness、readiness 和 startup probe 之间的区别,并附带 Spring Boot Actuator 和 Deployment YAML 的配置示例。读完本文,你将能够为真实的服务(如 OrderService)正确配置 probe,而不会让 pod 陷入无谓的重启循环。

2. 为什么需要 Probe

默认情况下,Kubernetes 只知道容器内的进程是否还在运行(容器状态 Running)。它不知道内部应用程序是否陷入死锁、丧失数据库连接,或者尚未完成启动。

如果没有 probe,后果是:

  • Pod 被挂起(死锁),但仍然接收流量 → 请求大量超时。
  • Pod 刚启动,尚未完成数据库连接,却已被 Service 添加到端点列表 → 返回 500 错误。

三种 probe 解决三个不同的问题:

Probe 回答问题 失败时
Liveness 容器还活着吗? Kubernetes 重启容器
Readiness 容器已准备好接收流量吗? Kubernetes 从 Service 端点移除 pod(不重启)
Startup 容器已完成启动吗? Kubernetes 等待,暂时禁用 liveness/readiness

3. Liveness Probe

Liveness probe 回答:“这个进程是否处于无法自我恢复的状态?”。如果 probe 失败超过 failureThreshold 次,kubelet 杀死并重启容器。

3.1. 添加 Spring Boot Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info

management.endpoint.health.probes.enabled=true 这一行启用了两个子端点:/actuator/health/liveness 和 /actuator/health/readiness,符合 Kubernetes 的预期标准。

3.2. 在 Deployment 中声明

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
            failureThreshold: 3
  • initialDelaySeconds: 20:容器启动后等待 20 秒才开始 probe。
  • periodSeconds: 10:每 10 秒调用一次 probe。
  • failureThreshold: 3:连续失败 3 次才认为已死亡 → 重启。

注意: Liveness probe 应该只检查“应用程序是否仍在运行”,不应该检查外部依赖项(数据库、Redis 等)。如果数据库宕机导致 liveness 失败,Kubernetes 会持续重启 pod 但无法解决任何问题——这是一个非常常见的错误。

4. Readiness Probe

Readiness probe 回答:“pod 已准备好处理请求吗?”。失败时,pod 从 Service 的端点中移除——流量不再被发送到它,但容器不会被重启。

4.1. 自定义 Readiness Indicator

假设 OrderService 需要连接到 PaymentGateway 后才能准备就绪:

@Component
public class PaymentGatewayReadinessIndicator implements HealthIndicator {

    private final PaymentGatewayClient paymentGatewayClient;

    public PaymentGatewayReadinessIndicator(PaymentGatewayClient paymentGatewayClient) {
        this.paymentGatewayClient = paymentGatewayClient;
    }

    @Override
    public Health health() {
        if (paymentGatewayClient.isConnected()) {
            return Health.up().withDetail("gateway", "connected").build();
        }
        return Health.down().withDetail("gateway", "unreachable").build();
    }
}

要将此 indicator 包含在 readiness 组中(而不影响 liveness),请声明 group:

management.endpoint.health.group.readiness.include=readinessState,paymentGateway
  • health() 当 PaymentGatewayClient 未连接时返回 Health.down() — readiness probe 会失败,pod 在网关准备就绪之前不会接收流量。
  • Spring 自动将 bean 名称 paymentGatewayReadinessIndicator 映射为健康响应中的键 paymentGateway。

4.2. Indicator 的测试

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <scope>test</scope>
</dependency>
class PaymentGatewayReadinessIndicatorTest {

    @Test
    void shouldReportDownWhenGatewayNotConnected() {
        PaymentGatewayClient client = mock(PaymentGatewayClient.class);
        when(client.isConnected()).thenReturn(false);

        PaymentGatewayReadinessIndicator indicator = new PaymentGatewayReadinessIndicator(client);
        Health health = indicator.health();

        assertThat(health.getStatus()).isEqualTo(Status.DOWN);
        assertThat(health.getDetails()).containsEntry("gateway", "unreachable");
    }
}
  • mock(PaymentGatewayClient.class) 模拟外部依赖项,无需真实连接。
  • assertThat(health.getStatus()) 确认当网关未连接时状态为 DOWN — 这是 readiness 的预期行为。

4.3. 在 Deployment 中声明 Readiness

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3
  successThreshold: 1
  • successThreshold: 1:只需 1 次 probe 成功即认为再次就绪(与 liveness 不同,通常无需设置此字段,因为默认值已是 1)。
  • periodSeconds: 5 比 liveness 更短,因为 readiness 需要快速响应依赖项状态变化。

5. Startup Probe

对于启动缓慢的应用程序(加载大型缓存、运行数据库迁移),如果只使用带有固定 initialDelaySeconds 的 liveness,你必须猜测一个足够大的数字——容易出错,且浪费随后每次重启的等待时间。

Startup probe 解决了这个问题:在 startup probe 通过之前,Kubernetes 暂时禁用 liveness 和 readiness probe,避免在应用程序合法启动时被无谓地杀死。

startupProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  failureThreshold: 30
  periodSeconds: 10
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10
  failureThreshold: 3
  • startupProbe 使用 failureThreshold: 30 和 periodSeconds: 10 → 允许应用程序最多启动 300 秒(30 × 10 秒)才认为失败。
  • startupProbe 首次通过后,Kubernetes 切换到使用阈值较低的 livenessProbe(3 次 × 10 秒 = 30 秒)进行监控——在稳定运行期间快速响应真实故障。

注意: 如果你的应用程序启动快速(几秒以内,如没有加载大型缓存的简单 REST API),无需声明 startupProbe — 添加它只会使 YAML 更长,而没有明显的好处。

6. 在单个 Deployment 中组合三个 Probe

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            failureThreshold: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 5
            failureThreshold: 3

创建新 pod 时的工作流程:

  1. 容器启动,Kubernetes 立即开始调用 startupProbe(没有 initialDelaySeconds,因为 startup 自己处理等待)。
  2. 此时 livenessProbe 和 readinessProbe 尚未运行。
  3. 当 startupProbe 首次返回 HTTP 200 时,Kubernetes 完全停止 startup probe,开始并行运行 liveness 和 readiness。
  4. Readiness 失败 → pod 从 Service 中移除,流量不再流向它,但容器仍然存活。
  5. Liveness 失败 → 容器被杀死,Kubernetes 创建新容器(同一个 pod),流程从第 1 步重新开始。

使用 kubectl describe pod 检查 probe 的实际状态:

kubectl describe pod order-service-7d9f8c6b5-xk2pq
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True

Events:
  Type     Reason     Age   From     Message
  Warning  Unhealthy  2m    kubelet  Readiness probe failed: HTTP probe failed with statuscode: 503

Readiness probe failed 这一行表示 readiness 正在失败 — 检查应用程序日志或直接调用 /actuator/health/readiness 以查看具体哪个部分 DOWN。

7. 常见错误

  • 对 liveness 和 readiness 使用同一个端点:当外部依赖项(数据库、缓存)宕机时,两个 probe 同时失败 → pod 持续重启(crash loop),尽管主应用程序仍然健康。应该清楚地分离 /actuator/health/liveness(仅检查内部)和 /actuator/health/readiness(检查依赖项)。

  • 对启动缓慢的应用程序设置 initialDelaySeconds 过小:liveness probe 在应用程序加载时立即失败,导致无限重启循环。解决方案:使用 startupProbe 而不是随意增加 initialDelaySeconds。

  • periodSeconds × failureThreshold 过大:pod 出现真实错误但需要很多分钟才能被发现,影响 SLA。

  • Readiness probe 调用不重要的下游服务:例如调用日志服务,如果该服务宕机,整个 OrderService 会被错误地认为 not-ready,尽管主要功能仍在正常工作。

8. 结论

三种 probe 服务三个不同的目的:startupProbe 在启动缓慢阶段保护应用程序,livenessProbe 检测死锁以便 Kubernetes 自动重启,readinessProbe 基于依赖项的实际状态来调度流量。最重要的原则是:不要让 liveness probe 依赖于外部系统——这样会将下游故障转变为你自己服务的 crash loop。正确配置这三个 probe 可以帮助集群平稳地自我修复,大大减少部署或基础设施故障期间的停机时间。

kubernetesspring-bootdevopshealth-checkmicroservices