ヘルスチェックの設計(liveness/readiness/startup)
プローブを一つにまとめると、再起動ループや連鎖障害で本番が落ちます。生存・受付可否・起動完了を3種に分け、依存先をどこまで見るかを正しく設計すれば、無駄な再起動と巻き込み事故を防げます。
- 1.liveness は『プロセスが直っていないか=再起動すべきか』、readiness は『今トラフィックを受けてよいか=LBに載せるか』、startup は『起動が完了したか=他の2つを始めてよいか』を判定する。役割が違うので混ぜてはいけない。
- 2.liveness に依存先(DBや下流API)のチェックを入れるのは設計ミス。依存先の一時障害でプロセスが再起動ループに陥り、復旧をかえって遅らせ連鎖障害を増幅する。依存先は readiness 側で、それも慎重に扱う。
- 3.失敗閾値・周期・タイムアウト・起動猶予の4パラメータが再起動とルーティングの挙動を決める。startup を分けることで、起動の遅いアプリを liveness が誤って殺す事故を防げる。
3種のプローブは「問い」が違う
ヘルスチェック(プローブ)を「アプリが生きているか確かめる仕組み」と一言でまとめると、設計を誤ります。Kubernetes が3種類を持つのは、それぞれが別の問いに答え、別のアクションを引き起こす からです。問いとアクションの対応を取り違えると、健全なPodを殺したり、壊れたPodにトラフィックを流し続けたりします。
| プローブ | 問い | 失敗時のアクション | 誰のための情報か |
|---|---|---|---|
| liveness | プロセスは生存しているか(デッドロック等で詰まっていないか) | コンテナを再起動する | kubelet(自Pod) |
| readiness | 今この瞬間トラフィックを受けてよいか | Endpoints から外す(再起動はしない) | Service/LB(振り分け側) |
| startup | 起動シーケンスが完了したか | (完了まで)liveness と readiness を抑止する | kubelet(自Pod) |
決定的な違いは 「失敗したら何が起きるか」 です。liveness が失敗するとコンテナは殺されて再起動します。readiness が失敗してもコンテナは生き続け、ただ振り分け先から外れるだけです。この非対称性が、後述する設計判断のすべての根拠になります。「再起動で直る問題か、待てば直る問題か」を取り違えると、対処が逆効果になるのです。
liveness:プロセスが「自力では直らない」状態の検出
liveness プローブが答えるべき問いは、「再起動以外に回復手段がない状態に陥っていないか」 です。典型例はデッドロック、無限ループ、メモリ枯渇でイベントループが進まない、といった プロセス内部の不可逆な詰まり です。これらは外から SIGKILL→再起動するしか直しようがありません。
逆に言えば、再起動で直らないものを liveness に入れてはいけません。最も多い失敗が、liveness のエンドポイントでDB接続や下流APIを叩いてしまうことです。下流が一時的に落ちている間、liveness は失敗し続け、kubelet は健全なアプリプロセスを 延々と再起動します。再起動してもDBは復旧していないので、また失敗する——これが再起動ループ(CrashLoopBackOff)の温床です。
DBが瞬間的に詰まったとします。全レプリカの liveness が同時に失敗し、kubelet は全Podを一斉に再起動します。再起動直後は接続プールが空・JITウォームアップ前・キャッシュが冷えた状態で、復旧したDBに殺到して再び過負荷——という メタステーブル障害 を自分で作り出します。依存先の一時障害という「待てば直る」問題に、再起動という「劇薬」を処方した結果です。liveness は自プロセスの内部状態だけを見るのが鉄則です。
実装上、liveness のチェックは軽量・自己完結であるべきです。HTTPなら「リクエストを受けてイベントループが回り、200を返せること」を確認する程度——つまり「ハンドラが応答できる」こと自体がイベントループ生存の証明になります。下流呼び出しもロック取得も含めません。
readiness:受付可否は刻一刻と変わる
readiness が答えるのは、「今、この瞬間、新規リクエストを処理できる状態か」 です。失敗してもPodは殺されず、Service の Endpoints から一時的に外れるだけで、状態が戻れば自動的に復帰します。これは liveness のような不可逆な判定ではなく、可逆なゲート です。
readiness を落とすべき正当なケースは、起動直後でキャッシュ温め中、設定リロード中、ローカルのワーカープールが飽和して新規を捌けない、そしてグレースフルシャットダウンの開始時です。終了時に readiness を落としてルーティングから外す流れは グレースフルシャットダウン の起点になります。
ここで最大の設計判断が 「readiness に依存先を含めるか」 です。これは liveness と違って一概に禁止ではありませんが、含めると連鎖障害のリスクがある ため慎重さが要ります。
全レプリカが同じDBを readiness でチェックしているとします。DBが瞬間的に遅延すると、全Podが同時に readiness 失敗→全員 Endpoints から除外され、Service の振り分け先がゼロになります。結果、DBの一時的な揺らぎがサービス全体の完全停止に増幅されます。本来なら、各Podは「受けられるリクエストだけでも受ける」べきだったのに、共有依存先を全員で見たせいで同時に自滅したのです。共有依存先は readiness から外し、依存先障害は各リクエストの中で サーキットブレーカ やタイムアウトで個別に扱うのが定石です。
判断の指針はこうです。そのPod固有のローカルな受付可否(プール飽和、ウォームアップ)は readiness に入れてよい。一方、全レプリカで共有する外部依存(共通DB、共通キャッシュ)は readiness に入れない。前者は外せば負荷が他Podに逃げて意味があるが、後者は全員同時に外れて逃げ場がなくなるからです。
startup:起動の遅さで liveness に殺されない
startup プローブは比較的新しい仕組みで、答える問いは 「起動シーケンスが完了したか」 です。失敗してもアクションは「待つ」だけ。重要なのは、startup が成功するまで liveness と readiness を一切作動させない 点です。
これが解決するのは典型的な事故です。起動に40秒かかるアプリ(巨大なヒープ確保、マイグレーション、JITウォームアップ等)に対し、liveness を initialDelaySeconds だけで調整しようとすると、安全側に倒して初期遅延を長くすればするほど、本当に詰まったときの検出も遅れるというジレンマに陥ります。短くすれば、まだ起動中のPodを liveness が「死んでいる」と誤判定して殺し、永遠に起動が終わらない再起動ループになります。
startup は「起動中は猶予を長く(例:周期10秒×失敗閾値30回=最大300秒まで待つ)」、liveness は「起動後は周期を短く(例:10秒)して詰まりを素早く検出」という二段構えを可能にします。startup が一度成功すれば、以降は liveness が短い周期で番をする。これにより『起動は気長に待つが、定常時の詰まりは即座に殺す』という、本来両立しにくい二つの要求を同時に満たせます。
4つのパラメータが挙動を決める
各プローブの実際の挙動は、次の4パラメータの組み合わせで決まります。これらを理解せずにデフォルト値で運用すると、誤検知(フラッピング)と検出遅延のどちらかに偏ります。
periodSeconds … チェック周期(何秒ごとに叩くか)
timeoutSeconds … 1回のチェックの応答待ち上限
failureThreshold … 連続何回失敗したらアクション発動か
successThreshold … 連続何回成功したら復帰とみなすか(readinessで重要)
最悪検出時間 ≈ periodSeconds × failureThreshold + timeoutSeconds
failureThreshold を 1 にすると、たった1回のタイムアウト(GCの瞬間的ストップ・ザ・ワールド等)でPodが殺されます。逆に大きすぎると、本当の詰まりの検出が遅れる。一過性のノイズは閾値で吸収し、持続的な異常だけをアクションに繋げるのが設計の勘所です。timeoutSeconds の既定はしばしば短すぎ(1秒など)、負荷時のGCで簡単に超過するため、定常レイテンシのテール(パーセンタイル)を踏まえて余裕を持たせます。
liveness のチェック自体は軽量でも、プロセスが高負荷で詰まっていると応答が遅れます。ここで timeoutSeconds を短くしすぎると、負荷が高いだけで死んでいないPodを殺し、残ったPodへ負荷が集中して次々と殺される——負荷スパイクを再起動の波に変換してしまいます。liveness のタイムアウトと失敗閾値は、定常負荷のテールレイテンシより十分に大きく取るのが安全です。
設計判断のまとめ
3種のプローブを正しく使い分ける核心は、「失敗時のアクションから逆算する」 ことです。
そのチェックが失敗したとき、再起動すべきか?
→ YES(再起動で直る・自プロセス内部の詰まり) : liveness
→ NO、でもトラフィックは止めたい(待てば直る) : readiness
→ 起動が終わるまで上2つを待たせたいだけ : startup
(1) liveness に下流依存(DB・外部API)を含めていないか——含めると依存先障害で再起動ループになり連鎖障害を増幅する。(2) readiness に全レプリカ共有の依存先を入れていないか——全員同時に外れてサービス全停止を招く。(3) 起動の遅いアプリで startup を使わず liveness の initialDelay だけで凌いでいないか。(4) timeoutSeconds/failureThreshold がGCやテールレイテンシを吸収できる値か。(5) 「再起動で直る問題か/待てば直る問題か」でプローブを選び分けられているか。
まとめ
- liveness=再起動すべきか、readiness=受付可否、startup=起動完了か。問いとアクションが異なるので、決して一つのエンドポイントに混ぜない。
- liveness は自プロセス内部の不可逆な詰まりだけを軽量に見る。依存先を入れると再起動ループでメタステーブル障害を自作する。
- readiness はローカルな受付可否(プール飽和・ウォームアップ・終了開始)に使う。全レプリカ共有の依存先を入れると全員同時に外れて全停止する。依存先障害はサーキットブレーカ等で個別に扱う。
- startup を分ければ「起動は気長に、定常時の詰まりは即座に殺す」を両立できる。
periodSeconds/timeoutSeconds/failureThresholdは一過性ノイズを吸収しつつ持続的異常を検出する観点で、テールレイテンシを踏まえて調整する。
DevOps/インフラ Article
ヘルスチェックの設計(liveness/readiness/startup)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ヘルスチェック
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
liveness に依存先(DBや下流API)のチェックを入れるのは設計ミス。依存先の一時障害でプロセスが再起動ループに陥り、復旧をかえって遅らせ連鎖障害を増幅する。依存先は readiness 側で、それも慎重に扱う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「ヘルスチェック / Kubernetes」に近いか確認する。
- 強みである「liveness は『プロセスが直っていないか=再起動すべきか』、readiness は『今トラフィックを受けてよいか=LBに載せるか』、startup は『起動が完了したか=他の2つを始めてよいか』を判定する。役割が違うので混ぜてはいけない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。