8月19日、ngrokが「How Kubernetes probes work」と題した記事を公開した。Kubernetesのプローブ(Probe)の仕組みと、設定ミスによって引き起こされる典型的な障害パターンを解説した記事だ。
この記事の読みどころは、単なるプローブ入門にとどまらない点にある。著者はKubernetesのGoコードをTypeScriptに移植してブラウザ上でクラスタ動作をシミュレートする「Webernetes」というプロジェクトを実装する過程で、実際のKubernetesのバグを発見したと述べている。100,000行超のGoコードを別言語に移植するという作業でしか気づけない類の発見だ。記事全体が、公式ドキュメントの要約ではなく実装レベルの深い観察から生まれた知見として読める。
Kubernetesは現在、コンテナオーケストレーションのデファクトスタンダードとして多くの本番環境を支えている。しかしその普及とともに、「とりあえず設定してある」だけで中身を把握していないケースが増え、CrashLoopBackOffに悩むエンジニアは後を絶たない。プローブはその典型的な原因のひとつだ。
プローブなしのPodが「Ready」なのにリクエストを落とす理由
プローブを設定しないPodは、コンテナが起動した直後からReadyと判定される。しかし実際には、アプリケーションがポート8080でListenを始めるまで数秒の初期化時間がかかる。この間にリクエストが来ると、当然失敗する。Kubernetesが「Ready」と言っても、実態は準備できていない。
この問題を解決するのがプローブだ。Kubernetesの公式ドキュメントが定義する3種類のプローブには、それぞれ異なる役割がある。
- Startup probe:コンテナ内のアプリが起動完了したかを判定する
- Readiness probe:トラフィックを受け入れられる状態かをライフタイム全体にわたって継続監視する
- Liveness probe:コンテナを再起動すべき状態かを判定する
CrashLoopBackOffに落ちるまでの構造
Startup probeは以下のように設定する:
startupProbe:
httpGet:
path: "/startup"
port: 8080
periodSeconds: 1
failureThreshold: 5
この設定では、1秒ごとにGET /startupを送り、5回連続で失敗するとコンテナをkillする。つまりアプリに与えられる起動猶予は約5秒だ。
failureThresholdを小さくしすぎると、アプリが起動を完了する前にKubernetesがコンテナをkillしてしまう。killされたコンテナは再起動され、また起動途中でkillされる——これがCrashLoopBackOffだ。公式ドキュメントによれば、デフォルトの再起動待機時間は10秒から始まり、クラッシュのたびに倍増して最大5分まで伸びる。本番環境でこのループに陥ると、復旧に数時間かかることもある。
記事は自分のコンテナの最悪ケースの起動時間を実測した上でfailureThresholdとperiodSecondsを決めることを強く推奨している。感覚値で設定するのが最も危険なパターンだ。
Readiness probeの「想定外」な挙動
Startup probeが成功した後は、Readiness probeがコンテナのライフタイム全体を通じて監視する。failureThresholdに達するとコンテナはNotReadyとなり、Serviceのロードバランシング対象から外れる(コンテナ自体は再起動しない)。
readinessProbe:
httpGet:
path: "/ready"
port: 8080
periodSeconds: 3
failureThreshold: 1
successThreshold: 1
ここで著者がWebernetes実装中に発見した興味深い挙動がある。Kubernetesの公式ドキュメントには次の記述がある:
While a container is not Ready, the readiness probe may be executed at times other than the configured
periodSecondsinterval. This is to make the Pod ready faster.
著者の調査によれば、NotReady状態のPodに対してアノテーションの更新やステータス変更などのイベントが発生すると、periodSecondsで設定したインターバルとは無関係に帯域外プローブ(out-of-band probe)が実行されることがわかった。つまり外部からの操作が意図せずプローブのタイミングに影響を与えうる。これは実装の詳細であり、依存すべきではないとしているが、トラブルシューティング時には知っておく価値のある挙動だ。
また、failureThresholdを1にすると一時的なエラーでもすぐにNotReadyになってしまう。記事ではデフォルト値(successThreshold: 1、failureThreshold: 3)が実用的に優れた設定であり、明確な理由がなければ変更しないことを推奨している。
なぜStartup probeとReadiness probeを両方使うのか
「Readiness probeだけでStartupの検出もできるのでは」と思うエンジニアは多い。記事はこの疑問に正面から答えている。両方使う理由は主に3点だ。
第一に、Startup probeが成功するまで、ReadinessとLivenessの監視が始まらない。起動中の誤検知を防ぐ仕組みとして機能する。第二に、起動中は高頻度(例: 1秒ごと)、定常状態は低頻度(例: 5秒ごと)と、それぞれ異なるperiodSecondsを使い分けられる。第三に、Startup probeの失敗はコンテナのkillと再起動につながる一方、Readiness probeの失敗は再起動しないため、一時的な過負荷状態からの回復に有効だ。
Liveness probeが担う「最後の砦」
Liveness probeはReadiness probeと似た設定だが、failureThresholdに達するとコンテナをkillして再起動する(restartPolicyのデフォルトはAlways)。メインスレッドのデッドロックや重要なバックグラウンドスレッドの異常終了など、コンテナ自身では回復できない状態を検出して強制再起動するための仕組みだ。Readiness probeがトラフィックを遮断して時間を稼ぐ役割だとすれば、Liveness probeは回復不能と判断した時点でリセットをかける役割を担う。
3つのプローブはそれぞれ独立した役割を持ち、組み合わせて使うことで初めて意図した動作が保証される。「設定してある」から安心するのではなく、各プローブが何を判定し、失敗時に何が起きるかを把握した上で値を決める——それがCrashLoopBackOffを未然に防ぐ唯一の方法だ。
詳細はHow Kubernetes probes workを参照していただきたい。