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がpodをエンドポイントリストに追加 → 500エラーが発生。

3種類のprobeがこれら3つの問題をそれぞれ解決します:

Probe 検査内容 失敗時の動作
Liveness コンテナは生きているか? Kubernetesがコンテナを再起動
Readiness コンテナはトラフィックを受け付ける準備ができているか? KubernetesがpodをServiceエンドポイントから削除(再起動しない)
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の行は2つのエンドポイントを有効にします:/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 probeが失敗すると、Kubernetesはpodを無限に再起動し続け、実質的な解決にはなりません。これは非常に一般的なミスです。

4. Readiness Probe

Readiness probeは「podはリクエスト処理の準備ができているか?」という質問に答えます。失敗すると、podはServiceのエンドポイントから削除され、トラフィックは送信されなくなります。ただし、コンテナは再起動されません。

4.1. カスタムreadinessインジケーター

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();
    }
}

このインジケーターをreadinessグループに含める(livenessに影響を与えないようにする)場合は、グループを設定します:

management.endpoint.health.group.readiness.include=readinessState,paymentGateway
  • health()メソッドがPaymentGatewayClientが接続していない場合にHealth.down()を返す → readiness probeが失敗し、gatewayが準備完了になるまでpodはトラフィックを受け付けません。
  • Springは自動的にbean名paymentGatewayReadinessIndicatorをhealth responseのpaymentGatewayキーに変換します。

4.2. インジケーターのテスト

<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())で、gatewayが接続していない場合はステータスが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より短くすることで、依存関係の状態変化に素早く対応します。

5. Startup Probe

大規模キャッシュの読み込みやデータベースマイグレーションの実行など、アプリケーション起動が遅い場合、livenessで固定のinitialDelaySecondsを使用すると、十分に大きな値を推測する必要があります。これは間違いやすく、その後の再起動のたびに無駄な待機時間が発生します。

Startup probeはこの問題を解決します:startup probeが初回成功するまで、KubernetesはlivenessとreadinessのprobeをDISABLEし、アプリケーションの正常な起動中に不正な終了を防ぎます。

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が1回目に成功した後、Kubernetesはより厳しいlivenessProbe(3回 × 10秒 = 30秒)のモニタリングに切り替え、実行時の問題に迅速に反応します。

注意: アプリケーション起動が高速(数秒以内のシンプルなREST API など)な場合、startupProbeを設定する必要はありません。追加するとYAMLが冗長になるだけで、明確な利点がありません。

6. 3つのProbeを統合したDeployment

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が初回成功したら、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 が無限にリスタート(クラッシュループ)します。アプリケーション自体は正常に動作していても、です。解決策:/actuator/health/liveness(内部チェックのみ)と/actuator/health/readiness(依存関係も含める)を明確に分離してください。
  • 起動が遅いアプリケーションでinitialDelaySecondsが短すぎる: livenessが起動中に失敗し、無限のリスタートループが発生します。解決策:startupProbeを使用して、むやみにinitialDelaySecondsを増やさないでください。
  • periodSeconds × failureThresholdが大きすぎる: 実際の障害がありながら、検出までに数分かかり、SLAに悪影響を与えます。
  • 重要でないダウンストリームサービスをreadiness probeで呼び出す: 例えば、ロギングサービスへの呼び出しなど。そのサービスが停止すると、OrderService全体が不適切に準備未完了と見なされ、メイン機能は正常に動作していても、です。

8. 結論

3つのprobeは異なる目的に対応します:startupProbeは遅い起動から保護し、livenessProbeはデッドロック検出してKubernetesによる自動再起動を実現し、readinessProbeは実際の依存関係状態に基づくトラフィック調整を行います。最も重要な原則:livenessprobeを外部システムに依存させない。そうしないと、ダウンストリームの障害がpodの無限再起動に変わってしまいます。この3つのprobeを正しく設定することで、デプロイ時または障害発生時にクラスターが自動復旧し、ダウンタイムを大幅に削減できます。

kubernetesspring-bootdevopshealth-checkmicroservices