インフラ・DevOps
Kubernetesのプローブの仕組み
How Kubernetes Probes Work (ngrok.com)
要約
Kubernetesのプローブ(Startup, Readiness, Liveness)は、コンテナのヘルスチェックを行い、アプリケーションの回復力と安定性を向上させます。これらのプローブを設定することで、コンテナの起動中やクラッシュ時の不要な再起動ループやリクエスト失敗を防ぎ、サービス提供の継続性を確保できます。本記事では、インタラクティブなデモを通じて、プローブの設定方法や誤設定による失敗例、デプロイ速度への影響などを解説します。
全文翻訳
Kubernetesのプローブがどのように機能するかを、実際に見ていきましょう。プローブがアプリケーションをどのように回復力のあるものにし、回復に数時間かかる再起動ループや、ロールアウト中にリクエストが失われるといった避けられる間違いをどのように防ぐのに役立つかを示します。
この投稿のすべてのインタラクティブデモは、KubernetesをTypeScriptに部分的に移植した my kubernetes を使用しています。これは、ブラウザ上でシミュレートされたクラスタを実行するために、10万行以上のKubernetes Goコードを移植したものです。これらのデモの動作をk3sに対して検証し、Kubernetesのバグを発見することさえできました!後ほど詳しく説明します。
この投稿で学ぶこと
プローブの3つのタイプとその目的。
それらをどのように設定し、組み合わせるか。
一般的な誤設定がどのように失敗するか。
プローブがデプロイ速度にどのように影響するか。
このセクションをブックマークする
プローブのないポッド
単一のコンテナを持つポッドを実行したいとします。そのマニフェストは以下の通りです。pod-a.yaml:
pod-a.yaml
1apiVersion: "v1"
2kind: "Pod"
3metadata:
⋯
4 name: "pod-a"
5spec:
⋯
6 containers:
⋯
7 - name: "app"
⋯
8 image: "my-app:latest"
このイメージ、my-app:latest は、初期化に数秒かかってからポート8080でリッスンを開始します。コンテナにシグナルを送信してクラッシュさせ、Kubernetesに再起動させるために「再起動」をクリックすると、以下でこれを確認できます。いつでもデモを一時停止またはリセットできます。
node-1
Reset cluster
Pause cluster
0/2
Restart container
Not yet complete.
Restart container
最初のクラッシュの後、コンテナはすぐに再起動します。2回目のクラッシュの後、Kubernetesはそれを再起動する前にCrashLoopBackOffを適用します。デフォルトでは、この遅延は10秒で、クラッシュごとに倍増し、最大5分まで待機します。このデモでは3秒に短縮しました。
どちらの場合も、コンテナが起動するとすぐにKubernetesはコンテナをReadyとみなしますが、実際にはそうではありません。まだ起動作業中で、ポート8080でリッスンしていません。
次に、2秒ごとにpod-aにリクエストを送信するpod-bを追加します。投稿全体を通して、pod-bをクライアントトラフィックのソース(イングラストコントローラ、ロードバランサ、サービス間リクエストなど)と考えてください。デモでpod-aを再起動中にリクエストが送信されると、そのリクエストは失敗します。
node-1
Reset cluster
Pause cluster
Cause a request to fail
Not yet complete.
Restart container
コンテナを再起動してから起動作業が完了するまでの間、コンテナはReadyとみなされているにもかかわらず、リクエストは失敗します!これは私が望むものではありません。Kubernetesに、pod-aがトラフィックを受け入れる準備ができていることを知ってもらう必要があります。そのために、Kubernetesはプローブを提供しています。
プローブは、コンテナのヘルスを判断するために定期的に送信されるチェックです。プローブには3つの種類があります。
Startup probes: コンテナ内のアプリケーションが起動したかどうかを判断します。
Readiness probes: アプリケーションがトラフィックを受け入れる準備ができているかどうかを判断します。
Liveness probes: アプリケーションを再起動する必要があるかどうかを判断します。
上記のデモで示した問題には、Startup probesが最も適しているように思われるので、そこから始めましょう。
このセクションをブックマークする
Startup probes
以下に、pod-a.yamlにstartup probeを追加しました。pod-a.yaml:
pod-a.yaml
1apiVersion: "v1"
2kind: "Pod"
3metadata:
⋯
4 name: "pod-a"
5spec:
⋯
6 containers:
⋯
7 - name: "app"
⋯
8 image: "my-app:latest"
9 startupProbe:
⋯
10 httpGet:
⋯
11 path: "/startup"
12 port: 8080
13 periodSeconds: 1
14 failureThreshold: 5
これはhttpGetプローブで、ポート8080でポッドにGET /startupリクエストを送信します。ステータスコード200〜399は成功とみなされます。これはperiodSecondsごとに発生し、failureThreshold回連続で失敗するまでKubernetesはコンテナを終了させません。これにより、コンテナは約5秒で起動作業を完了できます。
Kubernetesは、tcpSocket、exec、grpcプローブもサポートしています。これらは、TCP接続を確立したり、コンテナ内でコマンドを実行したり、gRPCヘルスチェックプロトコルを呼び出してコンテナのヘルスを確立したりします。それらについては、Kubernetesのドキュメントで読むことができます。この投稿では、httpGetを使用します。
プローブはkubeletと呼ばれるプロセスによって送信されます。クラスタの各ノードには独自のkubeletがあり、各ノードで適切なポッドが実行され、プローブされていることを確認するのがkubeletの仕事です。
以下の「pod-a」を再起動すると、NotReadyと表示されるようになります。Kubernetesは、pod-aがまだ初期化されていないことを認識しています。最初のstartup probeが成功した後でのみReadyになります。
node-1
Reset cluster
Pause cluster
0/2
Restart container
Not yet complete.
kubelet
kubelet
Restart container
NotReadyは、startup probeを持つコンテナを持つポッドのデフォルトです。しかし、Readyでなくても、pod-bは依然としてpod-aにリクエストを送信し、コンテナの起動期間中にそれらのリクエストは失敗します。これは、pod-bが直接pod-aのIPアドレスにリクエストを送信するように設定されており、Readinessメカニズムをバイパスしているためです。
NotReadyについて少し嘘をついています
技術的には、KubernetesにはNotReady状態はありません。Ready状態があり、True、False、またはUnknownの値を取ることができます。デモでReady=TrueまたはReady=Falseと言うよりも短いため、NotReadyと呼んでいます。
これらのリクエスト失敗を修正するには、より本番環境に適したセットアップに移行する必要があります。つまり、複数のpod-aのコピーを用意し、それらの間でリクエストをロードバランシングします。ここでは、pod-aの2つのレプリカを実行するように設定されたReplicaSetと、それらの間でロードバランシングを行うServiceを作成します。replica-set-a.yaml:
replica-set-a.yaml
1apiVersion: "apps/v1"
2kind: "ReplicaSet"
3metadata:
⋯
4 name: "replica-set-a"
5spec:
⋯
6 # `template`の下で定義されたポッドを実行します。
7 replicas: 2
8 selector:
⋯
9 matchLabels:
⋯
10 # このラベルを持つポッドをこのレプリカセットの一部とみなします。
11 app: "pod-a"
12 template:
⋯
13 metadata:
⋯
14 labels:
⋯
15 app: "pod-a"
16 spec:
⋯
17 # 前と同じポッド仕様です。
18 containers:
⋯
19 - name: "app"
⋯
20 image: "my-app:latest"
21 startupProbe:
⋯
22 httpGet:
⋯
23 path: "/startup"
24 port: 8080
25 periodSeconds: 1
26 failureThreshold: 5
service-a.yaml:
service-a.yaml
1apiVersion: "v1"
2kind: "Service"
3metadata:
⋯
4 name: "service-a"
5spec:
⋯
6 selector:
⋯
7 # このラベルを持つポッド間でロードバランシングします。
8 app: "pod-a"
9 ports:
⋯
10 # ポッドのこのポートにリクエストを送信します。
11 - port: 80
⋯
12 targetPort: 8080
今後は、pod-bは、個々のポッドに直接リクエストを送信するのではなく、ServiceのためにKubernetesが作成するDNS名、この場合はservice-a.default.svc.cluster.localにリクエストを送信します。
Kubernetesは、ポッドのReady状態を使用して、Serviceのロードバランシングに含めるか除外するかを決定します。以下で「再起動」ボタンをクリックすると、上のコンテナのみがクラッシュします。上のコンテナが起動中に、リクエストは常に下のコンテナに送信されることに注意してください。コンテナがNotReadyの場合、ポッド全体がNotReadyとマークされ、それが属するどのServiceからもトラフィックを受け取らなくなります。
node-1
Reset cluster
Pause cluster
0/2
Restart top container
Not yet complete.
Loading...
kubelet
kubelet
Restart top container
それでも、コンテナを再起動したときにリクエストがインフライト(処理中)の場合、リクエストは失敗する可能性があります。これは、再起動ボタンがコンテナを突然クラッシュさせるためです。インフライトリクエストを完了させる機会を与えません。
ここで行うべきより良いことは、ポッドを削除し、ReplicaSetに新しいポッドを起動させることです。これは2つの理由で優れています。
Kubernetesはデフォルトでポッドに30秒の終了猶予期間を与えますが、この投稿では待たなくても済むように2秒に設定しました。削除されると、ポッドは終了中とみなされ、Kubernetesはそれらを属するどのServiceからも削除します。新しいリクエストは受け取らなくなります。
ReplicaSetsは、終了中のポッドをアクティブなレプリカとしてカウントしないため、削除されたポッドが終了中の状態になるとすぐに置き換えを作成します。
これらを組み合わせることで、正常な終了とstartup probeは、起動中または停止中のコンテナからリクエストを遠ざけます。次のデモでは、「削除」をクリックしても、pod-bからのリクエストは失敗しません。
node-1
Reset cluster
Pause cluster
0/2
Wait for containers to be ready
Not yet complete.
Loading...
kubelet
kubelet
Delete top pod
常に新しいリクエストを処理できるポッドが存在するため、ユーザーのトラフィックを中断することなくポッドを安全に削除できます。
この猶予期間は実際にはどのように機能しますか?
ポッドを削除すると、kubeletは最初にSIG