Kubernetes のヘルスチェック Liveness, Readiness, Startup Probe についてまとめてみた

はじめに

Kubernetes(以下 k8s)には、Pod へのヘルスチェックを行う機能がある。その種類・方式をつい忘れてしまうのでまとめてみた。便利な機能なので、それぞれの特徴や違いを理解してより活用していきたい。

前提として、今回使用した環境は以下になる。

  • Docker Desktop for Mac
  • Kubernetes v1.22.1

結論

まず、最初に結論から。まとめると下記のようになる。

  • ヘルスチェックの種類
    • LivenessProbe:Pod 内のコンテナが正常に動いているかを確認
    • ReadinessProbe:Pod 内のコンテナがリクエストに応答する準備ができているか確認
    • StartupProbe:Pod の初回起動が完了したかを確認
  • ヘルスチェック方式
    • ExecAction:コンテナ内で任意のコマンドを実行。コマンドのステータスが 0 で終了したら成功。
    • HTTPGetAction:Pod の特定の IP、ポート、パスに対して HTTP GET リクエストを送信。レスポンスが 200 以上 400 未満だったら成功。
    • TCPSocketAction:Pod の特定の IP に対して TCP チェックを行う。ポートが空いていれば成功。

ヘルスチェック方式

ヘルスチェック方式については、種類に限らず全て同じなので順番が前後するが先に解説する。

それぞれ YAML の例を書いているが、kubectl explain pod.spec.containers.livenessProbe --recursive で確認できる。

ExecAction

コンテナ内で任意のコマンドを実行し、その終了コードにより状態を確認する方式。終了コードが 0 だと成功、1 だと失敗となる。

spec:
  containers:
    - livenessProbe:
        exec:
          command:
            - cat
            - /tmp/healty

HTTPGetAction

Pod の特定の IP、ポート、パスに対して HTTP GET リクエストを送信することで状態を確認する方式。レスポンスのステータスコードが 200 から 399 の場合を成功、それ以外の場合を失敗となる。

spec:
  containers:
    - livenessProbe:
        httpGet:
          path: /health
          port: 8080
          schema: HTTP
          httpHeaders:
            - name: X-Custom-Header
              value: Awesome

TCPSocketAction

Pod の特定の IP に対して TCP チェックを行うことで状態を確認する方式。TCP セッションを確立できると成功になり、できないと失敗になる。

spec:
  containers:
    - livenessProbe:
        tcpSocket:
          port: 8080

LivenessProbe

LivenessProbe は、コンテナが正常に動作しているかどうかを確認する役割。

アプリケーションのバグやメモリ不足など、様々な理由でコンテナは応答不可になる。勝手にコンテナがクラッシュする場合は必要ない1が、再起動なしでは回復しづらいコンテナに設定すると良い。

ここでは TCPSocketAction での挙動を見ていく。

まずは、下記の YAML ファイルを適用して Pod を作成する。

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: liveness
  name: liveness
spec:
  containers:
    - image: nginx
      name: liveness
      livenessProbe:
        tcpSocket:
          port: 8080
  dnsPolicy: ClusterFirst
  restartPolicy: Always

Pod の状態を watch オプションで見てみると、30 秒毎2に Restart されるのが確認できる。

$ k get po -w
NAME       READY   STATUS    RESTARTS   AGE
liveness   1/1     Running   3          97s
liveness   1/1     Running   4          2m4s

今回利用している Nginx イメージは 80 番ポートを LISTEN している。そのため 8080 番ポートだと TCP コネクションが失敗する。(試しに spec.containers[0].livenessProbe.tcpSocket.port を 80 に変えてやってみると Restart しないはずだ)

イベントログを確認すると、livenessProbe によるチェックで失敗しているのが分かる。

$ k describe po liveness | grep -A10 Events
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  75s                default-scheduler  Successfully assigned default/liveness to docker-desktop
  Normal   Pulled     72s                kubelet            Successfully pulled image "nginx" in 2.6530177s
  Normal   Pulled     43s                kubelet            Successfully pulled image "nginx" in 2.5414899s
  Normal   Killing    16s (x2 over 46s)  kubelet            Container liveness failed liveness probe, will be restarted
  Normal   Pulling    15s (x3 over 75s)  kubelet            Pulling image "nginx"
  Normal   Created    13s (x3 over 72s)  kubelet            Created container liveness
  Normal   Started    13s (x3 over 72s)  kubelet            Started container liveness
  Normal   Pulled     13s                kubelet            Successfully pulled image "nginx" in 2.6919547s

ReadinessProbe

ReadinessProbe は、Pod がリクエストに応答できるか状態かを確認する役割。

ユースケースとしては、この Probe が成功している Pod にのみトラフィックを送信したい場合などに適している。Service のバックエンドとして待ち構える Pod がリクエストに応答しない場合は、Service のロードバランシングからは切り離される挙動になる。

ここでは、HTTPGetAction で検証していく。

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: readiness
  name: readiness
spec:
  containers:
    - image: nginx
      name: readiness
      readinessProbe:
        httpGet:
          port: 80
          path: /
        failureThreshold: 3
        periodSeconds: 10
  dnsPolicy: ClusterFirst
  restartPolicy: Always

kubectl describe コマンドで設定できているか確認してみる。

$ k describe pod readiness | grep  Readiness
    Readiness:      http-get http://:80/ delay=0s timeout=1s period=10s #success=1 #failure=3

ReadinessProbe も設定できているのが分かる。 ここで確認用の Pod を作成して、Service に接続できるか確認してみる。

# 確認用の Pod を作成
$ k run test --image busybox --command sleep "3000"

# 確認用の Pod から Service に疎通
$ k exec -it test -- wget -O- readiness-svc
Connecting to readiness-svc (10.109.255.33:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
-                    100% |********************************|   615  0:00:00 ETA
written to stdout

Service 経由で readiness Pod で配信している HTML ファイルをダウンロードできているのが分かる。

Nginx のデフォルトのエントリーポイントは /usr/share/nginx/html/index.html 。なのでこれを削除すれば ReadinessProbe が失敗するか試してみる(後で戻したいのでファイル名を書き換えるに留めた)。

$ k exec -it readiness -- mv /usr/share/nginx/html/index.html /usr/share/nginx/html/tmp.html
# Pod のイベントに ReadinessProbe 失敗のイベントログが表示されている
$ k describe po readiness | grep -A5 Events
Events:
  Type     Reason     Age                From     Message
  ----     ------     ----               ----     -------
  Warning  Unhealthy  10s (x6 over 60s)  kubelet  Readiness probe failed: HTTP probe failed with statuscode: 403

# Service 経由でアクセスしても失敗する
$ k exec -it test -- wget -O- readiness-svc
Connecting to readiness-svc (10.109.255.33:80)
wget: can't connect to remote host (10.109.255.33): Connection refused
command terminated with exit code 1

# Pod の一覧を表示しても READY が 0 となっている
$ k get po
NAME        READY   STATUS    RESTARTS   AGE
readiness   0/1     Running   0          5m1s
test        1/1     Running   11         5m37s

ReadinessProbe が上手く動作しているのが確認できた。

この場合の挙動としては、LivenessProbe とは違って、コンテナの再起動はされないが、Service からのロードバランシングは停止される。

変更したファイル名をもとに戻してあげればトラフィックが再開するはずだ。

StartupProbe

StartupProbe は、Pod の初回起動が完了したかを確認する役割。

名前からもだいたい予想はつくかもしれないが、起動の遅いコンテナに対して設定してあげると良い。

ExecAction で(例として適しているかは微妙だが)検証していく。

Pod は、下記のような YAML を適用して作成する。

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: startup
  name: startup
spec:
  containers:
    - image: nginx
      name: startup
      startupProbe:
        exec:
          command:
            - cat
            - /etc/nginx/nginx.conf
        initialDelaySeconds: 1
        periodSeconds: 2
        timeoutSeconds: 1
        successThreshold: 1
        failureThreshold: 1
      resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always

確認すると、StartupProbe が成功して、Pod の起動ができていることが分かる。

$ k describe po startup | grep Startup
    Startup:        exec [cat /etc/nginx/nginx.conf] delay=1s timeout=1s period=2s #success=1 #failure=1

$ k get po
NAME      READY   STATUS    RESTARTS   AGE
startup   1/1     Running   0          65s

続いて下記のように YAML を修正して、StartupProbe が失敗するか見ていく。

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: startup
  name: startup
spec:
  containers:
    - image: nginx
      name: startup
      startupProbe:
        exec:
          command:
            - cat
            - /etc/nginx/nginx.conf-do-not-exist
        initialDelaySeconds: 1
        periodSeconds: 2
        timeoutSeconds: 1
        successThreshold: 1
        failureThreshold: 1
      resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
$ k replace --force -f <FILE_PATH>

$ k get po
NAME      READY   STATUS    RESTARTS   AGE
startup   0/1     Running   0          7s

$ k describe po startup | grep -A15 Events
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  68s                default-scheduler  Successfully assigned default/startup to docker-desktop
  Normal   Pulled     64s                kubelet            Successfully pulled image "nginx" in 3.0760395s
  Normal   Pulled     58s                kubelet            Successfully pulled image "nginx" in 3.1046343s
  Normal   Pulled     52s                kubelet            Successfully pulled image "nginx" in 2.8248999s
  Warning  Unhealthy  50s (x3 over 62s)  kubelet            Startup probe failed: cat: /etc/nginx/nginx.conf-do-not-exist: No such file or directory
  Normal   Killing    49s (x3 over 61s)  kubelet            Container startup failed startup probe, will be restarted
  Warning  BackOff    47s (x3 over 49s)  kubelet            Back-off restarting failed container
  Normal   Pulling    36s (x4 over 67s)  kubelet            Pulling image "nginx"
  Normal   Pulled     29s                kubelet            Successfully pulled image "nginx" in 7.3990341s
  Normal   Created    29s (x4 over 64s)  kubelet            Created container startup
  Normal   Started    28s (x4 over 64s)  kubelet            Started container startup

$ k get po -w
NAME      READY   STATUS             RESTARTS   AGE
startup   0/1     CrashLoopBackOff   4          90

イベントログを見ると /etc/nginx/nginx.conf-do-not-exist に対して「そんなファイルはないよ」というエラーとなり、コンテナの起動に失敗しているのが分かる。

この時 failureThreshold * periodSeconds で計算される時間は起動を待ち続け、失敗すると spec.restartPolicy に従ってアクションする。(今回はファイルの確認のみなので 2 秒とした)

まとめ

今回は k8s のヘルスチェックについてまとめてみた。

それぞれの特性を活かして、上手く活用できるようにしたい。

また initialDelaySecondsperiodSeconds などのヘルスチェック時の細かい設定値については割愛したが、運用時には考慮が必要なので引き続き深ぼっていきたい。

参考

Liveness Probe、Readiness ProbeおよびStartup Probeを使用する
このページでは、Liveness Probe、Readiness ProbeおよびStartup Probeの使用方法について説明します。 kubeletは、Liveness Probeを使用して、コンテナをいつ再起動するかを認識します。 例えば、アプリケーション自体は起動しているが、処理を継続することができないデッドロック状態を検知することができます。 このような状態のコンテナを再起動することで、バグがある場合でもアプリケーションの可用性を高めることができます。 kubeletは、Readiness Probeを使用して、コンテナがトラフィックを受け入れられる状態であるかを認識します。 Podが準備ができていると見なされるのは、Pod内の全てのコンテナの準備が整ったときです。 一例として、このシグナルはServiceのバックエンドとして使用されるPodを制御するときに使用されます。 Podの準備ができていない場合、そのPodはServiceのロードバランシングから切り離されます。 kubeletは、Startup Probeを使用して、コンテナアプリケーションの起動が完了したかを認識します。 Startup Probeを使用している場合、Startup Probeが成功するまでは、Liveness Probeと Readiness Probeによるチェックを無効にし、これらがアプリケーションの起動に干渉しないようにします。 例えば、これを起動が遅いコンテナの起動チェックとして使用することで、起動する前にkubeletによって 強制終了されることを防ぐことができます。 始める前に Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます: Killercoda Play with Kubernetes バージョンを確認するには次のコマンドを実行してください: kubectl version. コマンド実行によるLiveness Probeを定義する 長期間実行されているアプリケーションの多くは、再起動されるまで回復できないような異常な状態になることがあります。 Kubernetesはこのような状況を検知し、回復するためのLiveness Probeを提供します。 この演習では、registry.k8s.io/busyboxイメージのコンテナを起動するPodを作成します。 Podの構成ファイルは次の通りです。 pods/probe/exec-liveness.yaml apiVersion: v1 kind: Pod metadata: labels: test: liveness name: liveness-exec spec: containers: - name: liveness image: registry.k8s.io/busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600 livenessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 5 periodSeconds: 5 この構成ファイルでは、Podは一つのContainerを起動します。 periodSecondsフィールドは、kubeletがLiveness Probeを5秒おきに行うように指定しています。 initialDelaySecondsフィールドは、kubeletが最初のProbeを実行する前に5秒間待機するように指示しています。 Probeの動作としては、kubeletはcat /tmp/healthyを対象のコンテナ内で実行します。 このコマンドが成功し、リターンコード0が返ると、kubeletはコンテナが問題なく動いていると判断します。 リターンコードとして0以外の値が返ると、kubeletはコンテナを終了し、再起動を行います。
Liveness Probe、Readiness ProbeおよびStartup Probeを使用する favicon kubernetes.io
Liveness Probe、Readiness ProbeおよびStartup Probeを使用する
Podのライフサイクル
このページではPodのライフサイクルについて説明します。Podは定義されたライフサイクルに従い Pendingフェーズから始まり、少なくとも1つのプライマリーコンテナが正常に開始した場合はRunningを経由し、次に失敗により終了したコンテナの有無に応じて、SucceededまたはFailedフェーズを経由します。 Podの実行中、kubeletはコンテナを再起動して、ある種の障害を処理できます。Pod内で、Kubernetesはさまざまなコンテナのステータスを追跡して、回復させるためのアクションを決定します。 Kubernetes APIでは、Podには仕様と実際のステータスの両方があります。Podオブジェクトのステータスは、PodのConditionのセットで構成されます。カスタムのReadiness情報をPodのConditionデータに挿入することもできます。 Podはその生存期間に1回だけスケジューリングされます。PodがNodeにスケジュール(割り当て)されると、Podは停止または終了するまでそのNode上で実行されます。 Podのライフタイム 個々のアプリケーションコンテナと同様に、Podは(永続的ではなく)比較的短期間の存在と捉えられます。Podが作成されると、一意のID(UID)が割り当てられ、(再起動ポリシーに従って)終了または削除されるまでNodeで実行されるようにスケジュールされます。 ノードが停止した場合、そのNodeにスケジュールされたPodは、タイムアウト時間の経過後に削除されます。 Pod自体は、自己修復しません。Podがnodeにスケジュールされ、その後に失敗した場合、Podは削除されます。同様に、リソースの不足またはNodeのメンテナンスによりPodはNodeから立ち退きます。Kubernetesは、比較的使い捨てのPodインスタンスの管理作業を処理する、controllerと呼ばれる上位レベルの抽象化を使用します。 特定のPod(UIDで定義)は新しいNodeに"再スケジュール"されません。代わりに、必要に応じて同じ名前で、新しいUIDを持つ同一のPodに置き換えることができます。 volumeなど、Podと同じ存続期間を持つものがあると言われる場合、それは(そのUIDを持つ)Podが存在する限り存在することを意味します。そのPodが何らかの理由で削除された場合、たとえ同じ代替物が作成されたとしても、関連するもの(例えばボリューム)も同様に破壊されて再作成されます。 Podの図 file puller(ファイル取得コンテナ)とWebサーバーを含むマルチコンテナのPod。コンテナ間の共有ストレージとして永続ボリュームを使用しています。 Podのフェーズ Podのstatus項目はPodStatusオブジェクトで、それはphaseのフィールドがあります。 Podのフェーズは、そのPodがライフサイクルのどの状態にあるかを、簡単かつ高レベルにまとめたものです。このフェーズはコンテナやPodの状態を包括的にまとめることを目的としたものではなく、また包括的なステートマシンでもありません。 Podの各フェーズの値と意味は厳重に守られています。ここに記載されているもの以外にphaseの値は存在しないと思ってください。 これらがphaseの取りうる値です。 値 概要 Pending PodがKubernetesクラスターによって承認されましたが、1つ以上のコンテナがセットアップされて稼働する準備ができていません。これには、スケジュールされるまでの時間と、ネットワーク経由でイメージをダウンロードするための時間などが含まれます。 Running PodがNodeにバインドされ、すべてのコンテナが作成されました。少なくとも1つのコンテナがまだ実行されているか、開始または再起動中です。 Succeeded Pod内のすべてのコンテナが正常に終了し、再起動されません。 Failed Pod内のすべてのコンテナが終了し、少なくとも1つのコンテナが異常終了しました。つまり、コンテナはゼロ以外のステータスで終了したか、システムによって終了されました。 Unknown 何らかの理由によりPodの状態を取得できませんでした。このフェーズは通常はPodのホストとの通信エラーにより発生します。 備考: Podの削除中に、kubectlコマンドにはTerminatingが出力されることがあります。このTerminatingステータスは、Podのフェーズではありません。Podには、正常に終了するための期間を与えられており、デフォルトは30秒です。--forceフラグを使用して、Podを強制的に削除することができます。 Nodeが停止するか、クラスターの残りの部分から切断された場合、Kubernetesは失われたNode上のすべてのPodのPhaseをFailedに設定するためのポリシーを適用します。 コンテナのステータス Pod全体のフェーズと同様に、KubernetesはPod内の各コンテナの状態を追跡します。container lifecycle hooksを使用して、コンテナのライフサイクルの特定のポイントで実行するイベントをトリガーできます。 PodがschedulerによってNodeに割り当てられると、kubeletはcontainer runtimeを使用してコンテナの作成を開始します。コンテナの状態はWaiting、RunningまたはTerminatedの3ついずれかです。 Podのコンテナの状態を確認するにはkubectl describe pod [POD_NAME]のコマンドを使用します。Pod内のコンテナごとにStateの項目として表示されます。 各状態の意味は次のとおりです。 Waiting コンテナがRunningまたはTerminatedのいずれの状態でもない場合コンテナはWaitingの状態になります。Waiting状態のコンテナは引き続きコンテナイメージレジストリからイメージを取得したりSecretを適用したりするなど必要な操作を実行します。Waiting状態のコンテナを持つPodに対してkubectlコマンドを使用すると、そのコンテナがWaitingの状態である理由の要約が表示されます。 Running Running状態はコンテナが問題なく実行されていることを示します。postStartフックが構成されていた場合、それはすでに実行が完了しています。Running状態のコンテナを持つPodに対してkubectlコマンドを使用すると、そのコンテナがRunning状態になった時刻が表示されます。 Terminated Terminated状態のコンテナは実行されて、完了したときまたは何らかの理由で失敗したことを示します。Terminated状態のコンテナを持つPodに対してkubectlコマンドを使用すると、いずれにせよ理由と終了コード、コンテナの開始時刻と終了時刻が表示されます。 コンテナがTerminatedに入る前にpreStopフックがあれば実行されます。 コンテナの再起動ポリシー Podのspecには、Always、OnFailure、またはNeverのいずれかの値を持つrestartPolicyフィールドがあります。デフォルト値はAlwaysです。 restartPolicyは、Pod内のすべてのコンテナに適用されます。restartPolicyは、同じNode上のkubeletによるコンテナの再起動のみを参照します。Pod内のコンテナが終了した後、kubeletは5分を上限とする指数バックオフ遅延(10秒、20秒、40秒...)でコンテナを再起動します。コンテナが10分間実行されると、kubeletはコンテナの再起動バックオフタイマーをリセットします。 PodのCondition PodにはPodStatusがあります。それにはPodが成功したかどうかの情報を持つPodConditionの配列が含まれています。kubeletは、下記のPodConditionを管理します: PodScheduled: PodがNodeにスケジュールされました。 PodHasNetwork: (アルファ版機能; 明示的に有効にしなければならない) Podサンドボックスが正常に作成され、ネットワークの設定が完了しました。 ContainersReady: Pod内のすべてのコンテナが準備できた状態です。 Initialized: すべてのInitコンテナが正常に終了しました。 Ready: Podはリクエストを処理でき、一致するすべてのサービスの負荷分散プールに追加されます。 フィールド名 内容 type このPodの状態の名前です。 status その状態が適用可能かどうか示します。可能な値は"True"、"False"、"Unknown"のうちのいずれかです。 lastProbeTime Pod Conditionが最後に確認されたときのタイムスタンプが表示されます。 lastTransitionTime 最後にPodのステータスの遷移があった際のタイムスタンプが表示されます。 reason 最後の状態遷移の理由を示す、機械可読のアッパーキャメルケースのテキストです。 message ステータスの遷移に関する詳細を示す人間向けのメッセージです。 PodのReadiness FEATURE STATE: Kubernetes v1.
Podのライフサイクル favicon kubernetes.io
Podのライフサイクル
Kubernetes道場 10日目 - LivenessProbe / ReadinessProbeについて - Toku's Blog
この記事は Kubernetes道場 Advent Calendar 2018 10日目の記事です。 今回はLivenessProbeとReadinessProbeについて。 Kubernetesのヘルスチェックについて Kubernetesにはコンテナへのヘルスチェック(Probe) ...
Kubernetes道場 10日目 - LivenessProbe / ReadinessProbeについて - Toku's Blog favicon cstoku.dev
Kubernetes道場 10日目 - LivenessProbe / ReadinessProbeについて - Toku's Blog
【Kubernetes】LivenessProbeとReadinessProbeを試してみる
はじめに Kubernetesにおける**Liveness ProbeとReadiness Pr
【Kubernetes】LivenessProbeとReadinessProbeを試してみる favicon amateur-engineer-blog.com
【Kubernetes】LivenessProbeとReadinessProbeを試してみる
KubernetesのLivenessProbe, ReadinessProbe, StartupProbeのベストプラクティス
KubernetesのLivenessProbe, ReadinessProbe, StartupProbeのベストプラクティス favicon zenn.dev
KubernetesのLivenessProbe, ReadinessProbe, StartupProbeのベストプラクティス

Footnotes

  1. kubelet が自動で spec.restartPolicy に基づいたアクションを実行する。ref. kubelet | Kubernetes

  2. spec.containers.livenessProbe.periodSeconds に特に設定していなければデフォルトは 30 秒。