Kubernetes Liveness・Readiness・Startup プローブの選び方と設定|再起動ループを防ぐ設計ガイド
- 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.36、GKE 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 で切り離し)
PostgreSQL メンテナンスや PgBouncer の再起動で /ready が 503 になるのは正常な防御です。これを liveness に繋ぐと CrashLoopBackOff になります。枯渇の根本原因はプール設定側で直し、トラフィック制御は readiness に任せます。
readinessProbe — 準備完了確認
readiness は 「ロードバランサーに載せてよいか?」 を問う。失敗しても Pod は再起動されません。Service の Endpoints コントローラが対象 Pod の IP をリストから外し、Ingress / Service 経由のトラフィックが向かなくなります。
ローリングデプロイでは、新 Pod が readiness 成功するまで旧 Pod にトラフィックが流れ続けます。これにより ゼロダウンタイム が成立します。
readiness で確認すべき依存の例:
| 依存 | チェック方法 | 失敗時の意味 |
|---|---|---|
| PostgreSQL | SELECT 1 | 書き込み・読み取り不可 |
| Redis | PING | セッション・キャッシュ不可 |
| メッセージキュー | 接続確認 | 非同期処理不可 |
| 必須の外部 API | HEAD/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 に注意)
コンテナ起動。startupProbe があれば成功まで他プローブを抑止
startup 成功後、readiness / liveness が periodSeconds ごとに実行
readiness 成功で Service Endpoints に追加されトラフィック受付開始
readiness 失敗で Endpoints から除外(プロセスは生存)
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,
});
}
});
ポイント:
/healthは同期・依存なし — デッドロックしていなければ 200/readyだけ依存を見る — DB / Redis / 必須 API- 起動フラグ
ready— マイグレーション完了まで 503 - タイムアウトは短く — 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.periodSeconds | 5 前後 | 切り離しを早め、デプロイを滑らかに |
preStop sleep | 数秒 | LB から外れる猶予(graceful shutdown) |
readiness 失敗 → Endpoints 除外 → SIGTERM → 進行中リクエスト完了、の順が理想です。terminationGracePeriodSeconds と preStop を合わせて、プローブだけの局所最適化にしないこと。
よくある失敗パターン
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 以上にでき、「一時的な成功」を無視して安定してから載せたい場合に使う。
チェックリスト
/health と /ready を別エンドポイントに分離したか
liveness が DB・外部 API に依存していないか
遅い起動があるなら startupProbe を入れたか
failureThreshold × periodSeconds が起動最悪ケースを覆うか
readiness 失敗時に Endpoints から外れることだけを期待し、再起動を期待していないか
preStop / terminationGracePeriodSeconds と整合しているか
ステージングで依存を止めて CrashLoop にならないことを確認したか
まとめ
Kubernetes のプローブ設計は「生きているか」と「載せられるか」を分けることが核心です。
- liveness — 軽量な
/health。失敗 = 再起動 - readiness — 依存込みの
/ready。失敗 = トラフィック遮断 - startup — 起動が遅いアプリの猶予。他プローブの誤爆を防ぐ
- 依存障害は readiness で切り離し、プールやサーキットブレーカーで根本対処
- 2026年7月時点の EKS 1.33–1.36 / GKE Stable ~1.34 でも、この分離が CrashLoopBackOff を防ぐ最短ルート
まずは /health と /ready を分け、deployment でパスを対応付ける——これだけで再起動ループの大半は防げます。