Kubernetes Liveness・Readiness・Startup プローブの選び方と設定|再起動ループを防ぐ設計ガイド

(更新: 2026年7月22日 ) Kubernetes DevOps コンテナ ヘルスチェック Express インフラ 可用性
結論
  • liveness = プロセスは生きているか(失敗 → コンテナ再起動)
  • readiness = トラフィックを受けられるか(失敗 → Endpoints から除外。再起動しない)
  • startup = 起動完了まで他プローブを抑止(遅い起動・マイグレーション向け)
  • 同じ URL を共用しない。liveness に DB チェックを入れると依存障害で CrashLoopBackOff になる

Kubernetes(k8s)クラスタにおいて、Pod の健全性とトラフィック制御を支える Liveness・Readiness・Startup の 3 種のプローブ(probes)の基本的な選び方、Express での実装パターン、よくある再起動ループ(CrashLoopBackOff)を防ぐ deployment.yaml の設計ガイドです。

なぜ Kubernetes プローブが必要か

Pod をデプロイしたあと、Kubernetes は「コンテナが生きている」ことと「リクエストを処理できる」ことを 別々に 判断する必要があります。

コンテナプロセスが起きていても、次のような状態は日常的です。

  • DBマイグレーション中でまだリクエストを受け付けられない
  • 依存サービスの一時障害でエラー率が上がっている
  • 接続プール枯渇で新規クエリがタイムアウトしている
  • GCやスレッドプール枯渇でハング気味だがプロセスは存在する
  • ローリングデプロイで新旧 Pod が混在している

プローブなしでは、kubelet は「プロセスが生きていれば正常」とみなし、Service は 準備できていない Pod にもトラフィックを送り続ける。結果として 502/503、デプロイ中のエラー率スパイク、健全 Pod への再起動が連鎖した事例が起きます。

Kubernetes 1.16 以降、3種類のプローブが安定化されています。2026年7月時点の本番クラスタ(EKS 1.33–1.36GKE Stable ~1.34)でもこの3種類が基本設定の軸です。

プローブ主な質問失敗時の動作
livenessProbeプロセスは生きているか?コンテナ再起動
readinessProbeトラフィックを受けられるか?Endpoints から除外
startupProbe起動が完了したか?liveness/readiness の開始を遅延

3種類のプローブ — 役割の基本的な選び方

プローブ 設計のポイント
livenessProbe デッドロック・ハング検知。チェックは軽量に。失敗 = 再起動
readinessProbe 依存や暖機状態を確認。失敗 = トラフィック遮断(再起動しない)
startupProbe 起動猶予。成功まで他プローブ無効。遅い起動のアプリ向け
同じURLを共用 アンチパターン。liveness が DB 依存で失敗 → 再起動ループ
HTTP / exec / tcp HTTP推奨(アプリ層)。exec は重い。tcp はリッスンのみ確認

livenessProbe — 生存確認

liveness は 「このコンテナはもう健全ではないか?」 を問う。失敗すると kubelet は SIGTERM → grace period → SIGKILL の順でコンテナを再起動します。

向いているケース:

  • デッドロックで HTTP 応答が永遠に返らない
  • メインスレッドがハングし、プロセスだけ残っている
  • アプリ内部の watchdog が異常を検知した

向いていないケース:

  • DB接続プールの一時枯渇(readiness で切り離し。対処ガイド参照)
  • 外部 API のレイテンシ悪化(readiness またはサーキットブレーカー)
  • 起動直後のウォームアップ中(startupProbe で切り離し)
liveness に DB チェックを入れない

PostgreSQL メンテナンスや PgBouncer の再起動で /ready が 503 になるのは正常な防御です。これを liveness に繋ぐと CrashLoopBackOff になります。枯渇の根本原因はプール設定側で直し、トラフィック制御は readiness に任せます。

readinessProbe — 準備完了確認

readiness は 「ロードバランサーに載せてよいか?」 を問う。失敗しても Pod は再起動されません。Service の Endpoints コントローラが対象 Pod の IP をリストから外し、Ingress / Service 経由のトラフィックが向かなくなります。

ローリングデプロイでは、新 Pod が readiness 成功するまで旧 Pod にトラフィックが流れ続けます。これにより ゼロダウンタイム が成立します。

readiness で確認すべき依存の例:

依存チェック方法失敗時の意味
PostgreSQLSELECT 1書き込み・読み取り不可
RedisPINGセッション・キャッシュ不可
メッセージキュー接続確認非同期処理不可
必須の外部 APIHEAD/GET(タイムアウト短く)機能の一部が使えない

オプション依存(機能フラグで無効化できるもの)は readiness 失敗にしない設計も検討する。checks オブジェクトで ok / fail / skip を返し、必須のみで 503 を返す。

startupProbe — 起動猶予

Kubernetes 1.16 で追加。起動に時間がかかるアプリで、liveness の initialDelaySeconds だけでは足りない場合に使う。

startupProbe が設定されている間、liveness と readiness は無効。startup が成功した時点から通常の liveness/readiness が動き始めます。

最大起動猶予の計算:

猶予秒数 = failureThreshold × periodSeconds

例: failureThreshold: 30, periodSeconds: 10 → 最大 300 秒(5 分)の起動猶予。

Spring Boot + Flyway マイグレーション、大きなインメモリキャッシュのウォームアップ、GPU モデルのロードなど、起動完了の定義が「ポートが開いた」では足りないケースで startup が効く。

プローブの流れ — kubelet が何をしているか

kubelet は各プローブについて、設定された間隔(periodSeconds)でチェックを実行します。

Pod 起動

  ├─ startupProbe あり?
  │     ├─ YES → startup 成功まで liveness/readiness はスキップ
  │     └─ NO  → 直ちに liveness/readiness 開始

  ├─ readiness 失敗 → Endpoints から除外(再起動しない)
  └─ liveness 失敗 → コンテナ再起動(CrashLoop に注意)
1

コンテナ起動。startupProbe があれば成功まで他プローブを抑止

2

startup 成功後、readiness / liveness が periodSeconds ごとに実行

3

readiness 成功で Service Endpoints に追加されトラフィック受付開始

4

readiness 失敗で Endpoints から除外(プロセスは生存)

5

liveness 失敗が failureThreshold 連続で再起動

Express 実装 — /health/ready を分ける

最小構成では 生存確認用 /health準備確認用 /ready を分離します。

// health.js — Express 5 例
import express from 'express';

const app = express();
let ready = false;

// 起動完了後に true にする(マイグレーション・ウォームアップ後)
export function markReady() {
  ready = true;
}

// liveness: プロセスが応答できることだけ確認(依存を見ない)
app.get('/health', (_req, res) => {
  res.status(200).json({ status: 'ok' });
});

// readiness: 必須依存が揃っているか
app.get('/ready', async (_req, res) => {
  if (!ready) {
    return res.status(503).json({ status: 'starting' });
  }

  try {
    await db.query('SELECT 1');
    await redis.ping();
    res.status(200).json({ status: 'ready' });
  } catch (err) {
    res.status(503).json({
      status: 'not_ready',
      reason: err.message,
    });
  }
});

ポイント:

  1. /health は同期・依存なし — デッドロックしていなければ 200
  2. /ready だけ依存を見る — DB / Redis / 必須 API
  3. 起動フラグ ready — マイグレーション完了まで 503
  4. タイムアウトは短く — readiness 内の DB クエリは 1〜2 秒上限

本番では 構造化ログ(JSON / Loki) で probe 失敗理由を残すと、CrashLoop と一時的な readiness 落ちの切り分けが速くなります。

deployment.yaml 完全例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.4.2
          ports:
            - containerPort: 3000
          startupProbe:
            httpGet:
              path: /health
              port: 3000
            failureThreshold: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 0
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 0
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
            successThreshold: 1
          resources:
            requests:
              cpu: "100m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 5"]
フィールド目安理由
startupProbe.failureThreshold × periodSeconds起動最悪ケース以上遅い起動で liveness に殺されない
livenessProbe.path/health(軽量)依存障害で再起動しない
readinessProbe.path/ready(依存あり)不完全な Pod に流量を載せない
readinessProbe.periodSeconds5 前後切り離しを早め、デプロイを滑らかに
preStop sleep数秒LB から外れる猶予(graceful shutdown)
graceful shutdown と合わせて設計する

readiness 失敗 → Endpoints 除外 → SIGTERM → 進行中リクエスト完了、の順が理想です。terminationGracePeriodSecondspreStop を合わせて、プローブだけの局所最適化にしないこと。

よくある失敗パターン

1. liveness = readiness の同一エンドポイント

DB 障害やプール枯渇で readiness が落ちるべき場面で、liveness も同時に失敗し 全レプリカが再起動 する。依存障害が長引くほど CrashLoop が深くなる。

2. initialDelaySeconds だけで起動を待つ

固定遅延は「いつもちょうど足りる」ことがない。遅い環境では短すぎ、速い環境では無駄に長い。startupProbe で「実際に起動完了するまで」待つ方が安全。

3. exec プローブで重いスクリプト

# 避けたい例
livenessProbe:
  exec:
    command: ["sh", "-c", "curl -f http://localhost:3000/health && ps aux | grep node"]

exec はシェル起動コストと権限問題がある。可能な限り HTTP プローブ に寄せる。

4. timeoutSeconds が長すぎる

timeoutSeconds: 30 の readiness は、kubelet の並列チェックを詰まらせ、他 Pod の判定も遅らせる。依存チェックはアプリ側で短いタイムアウトをかけ、プローブ側は 1〜3 秒程度に抑える。

5. successThreshold の誤解

liveness の successThreshold は常に 1(変更不可)。readiness だけ 2 以上にでき、「一時的な成功」を無視して安定してから載せたい場合に使う。

チェックリスト

1

/health と /ready を別エンドポイントに分離したか

2

liveness が DB・外部 API に依存していないか

3

遅い起動があるなら startupProbe を入れたか

4

failureThreshold × periodSeconds が起動最悪ケースを覆うか

5

readiness 失敗時に Endpoints から外れることだけを期待し、再起動を期待していないか

6

preStop / terminationGracePeriodSeconds と整合しているか

7

ステージングで依存を止めて CrashLoop にならないことを確認したか

まとめ

Kubernetes のプローブ設計は「生きているか」と「載せられるか」を分けることが核心です。

  1. liveness — 軽量な /health。失敗 = 再起動
  2. readiness — 依存込みの /ready。失敗 = トラフィック遮断
  3. startup — 起動が遅いアプリの猶予。他プローブの誤爆を防ぐ
  4. 依存障害は readiness で切り離し、プールやサーキットブレーカーで根本対処
  5. 2026年7月時点の EKS 1.33–1.36 / GKE Stable ~1.34 でも、この分離が CrashLoopBackOff を防ぐ最短ルート

まずは /health/ready を分け、deployment でパスを対応付ける——これだけで再起動ループの大半は防げます。