Pod のライフサイクルと再起動の仕組み

v1.37 時点(2026年10月23日 確認)

Kubernetes 基本仕様第9回 / 全45回 ・ 第2部 ワークロード
目次

この記事では、Pod が作られてから終わるまでの移り変わりである Pod のライフサイクル の仕組みを、図で説明します。Pod 全体の段階を表すフェーズと、コンテナごとの状態の違いがわかります。restartPolicy によって、kubelet が止まったコンテナを再起動するかどうかが決まる仕組みもわかります。後半では kind のクラスタでわざと失敗するコンテナを動かし、CrashLoopBackOff になって再起動までの待ち時間が延びていく様子を観察する手順を示します。

Pod とコンテナの関係と、Pod が 1 つのノードに留まることを扱った第8回の内容を前提にしています。まだの方は先にこちらをどうぞ。

基本仕様 ・ 第8回 Pod を動かして理解するKubernetes で作って管理できる最小の単位である Pod の仕組みを図で説明します。Pod とコンテナの関係、Pod の中のコンテナが共有するもの、Pod が 1 つのノードに置かれて移らないことを押さえ、kind のクラスタで Pod を直接作って削除し、もう戻らないことを確かめる手順を紹介します。

以下の図は、Pod のフェーズが移り変わる主な流れを表しています。

Pod のフェーズの主な移り変わり(Pending、Running、Succeeded、Failed)

Pod は Pending から始まり、Pod のコンテナが作られて、少なくとも 1 つが無事に起動すると Running に進みます。そのあと、コンテナが再起動されずに終わると、失敗して終了したコンテナがあるかどうかで、Succeeded か Failed に進みます。再起動するかどうかを決める restartPolicy は後で説明します。コンテナが止まっても kubelet がコンテナを再起動しているあいだは、Pod は Running のままです。公式ドキュメントは、フェーズは Pod がライフサイクルのどこにいるかを大まかにまとめたもので、すべての移り変わりを表す状態遷移の仕組み(state machine)ではない、としています。図も主な流れだけを描いています。

参考:Pod Lifecycle | Kubernetes

Pod のライフサイクルとは

Pod のライフサイクル は Pod がノードに割り当てられて動き始め、終わるまでの決まった移り変わり です。第8回で見たとおり、Pod がスケジュールされる(動かすノードが決まる)のは、その一生で一度だけです。公式ドキュメントによると、ノードに割り当てられた Pod は、止まるか終了させられるまで、そのノードで動きます。選ばれたノードで Pod を始められなかったとき(たとえば、Pod が始まる前にノードが故障したとき)は、その Pod は始まらないままです。

参考:Pod Lifecycle | Kubernetes

Kubernetes は Pod 全体の段階と、Pod の中のコンテナごとの状態を、分けて記録しています。公式ドキュメントによると、Pod が動いているあいだ、kubelet はある種の障害に対処するためにコンテナを再起動できます。Kubernetes は Pod の中のコンテナの状態を追いかけ、Pod を正常な状態に戻すために何をするかを決めます。kubelet はこれを、望ましい状態(Pod の spec)と、動いているコンテナの実際の状態を定期的に突き合わせる繰り返しの処理で行います。第6回で見たコントローラーの調整ループと同じく、望ましい状態に近づけようとする仕組みです。

参考:Pod Lifecycle | Kubernetes、Kubelet Sync Loop | Kubernetes

この回で扱う 3 つのものを、次の表にまとめます。

項目書かれる場所表すもの
フェーズstatus.phasePod 全体が、ライフサイクルのどの段階にいるか
コンテナの状態status.containerStatuses(コンテナごとの状態の一覧。第5回)コンテナごとに Waiting、Running、Terminated のどれか
restartPolicyspec.restartPolicy終了したコンテナを kubelet が再起動するかどうか

参考:Pod Lifecycle | Kubernetes、Pod | Kubernetes API Reference

フェーズとコンテナの状態は status に、restartPolicy は spec に書かれます。第5回で見たとおり、spec は利用者が書く望ましい状態で、status は Kubernetes が書き込む実際の状態です。restartPolicy は書かなければ既定値の Always になります。

参考:Pod | Kubernetes API Reference

なぜ Pod のライフサイクルが必要なのか

コンテナで動かすプログラムには、止まらずに動き続けるものと、仕事を終えたら止まるものがあります。たとえば nginx のような Web サーバーは動き続けるのが正常で、止まったら再起動してほしいプログラムです。一方、データを一度だけ移し替えるような処理は、終わって止まるのが正常です。止まるたびに再起動されると、同じ処理が何度も繰り返されてしまいます。公式ドキュメントの restartPolicy の例も Web サーバーには Always、一度きりの処理には Never を使っています。止まったコンテナをどう扱うかを Pod ごとに決めるのが restartPolicy です。

参考:Pod Lifecycle | Kubernetes

Pod が思ったとおりに動かないとき、原因を探す手がかりになるのがフェーズとコンテナの状態です。コンテナが動いていないとき、イメージを取得している途中なのか、起動しては失敗を繰り返しているのかで、取るべき対応は変わります。公式ドキュメントによると、Waiting の状態のコンテナを kubectl で調べると、その状態にある理由(Reason)も表示されます。

参考:Pod Lifecycle | Kubernetes

再起動にも歯止めが要ります。設定の誤りのように、何度再起動しても直らない失敗があるからです。公式ドキュメントによると、kubelet は失敗を繰り返すコンテナの再起動の前に待ち時間を入れ、その待ち時間を少しずつ延ばします。すばやい再起動の繰り返しで、システムに負荷がかかりすぎないようにするためです。この待ち時間が働いている状態が、この回の後半で観察する CrashLoopBackOff です。

参考:Pod Lifecycle | Kubernetes

Pod のライフサイクルの仕組み

Pod のフェーズ

第5回で、Pod の status.phase が 5 つの値をとることを見ました。それぞれの意味を、改めて次の表にまとめます。

phase の値意味
Pendingクラスタに受け付けられたが、動く準備ができていないコンテナがある。ノードが決まるまでの時間と、イメージをダウンロードする時間を含む
Runningノードに割り当てられ、すべてのコンテナが作られた。少なくとも 1 つのコンテナが動いているか、起動・再起動の途中
Succeededすべてのコンテナが成功して終了し、再起動されない
Failedすべてのコンテナが終了し、少なくとも 1 つが失敗して終了した。失敗とは、0 以外の終了コードで終わったかシステムに終了させられ、そのうえ自動で再起動される設定になっていないこと
Unknown何らかの理由で Pod の状態を取得できない。多くはノードとの通信の問題

参考:Pod Lifecycle | Kubernetes、Pod | Kubernetes API Reference

冒頭の図に Unknown を描いていないのは、第5回で見たとおり、v1.37 時点の API リファレンスで非推奨とされているからです。終了コードはプログラムが終わるときに返す数で、0 が成功を表します(「restartPolicy で決まる再起動」で説明します)。公式ドキュメントの Jobs のページは、コンテナが失敗する理由の例として、0 以外の終了コードのほかに、メモリの上限を超えて止められることを挙げています。

参考:Pod Lifecycle | Kubernetes、Pod | Kubernetes API Reference、Jobs | Kubernetes

公式ドキュメントは、フェーズの値の数と意味は厳しく決められていて、ここに書かれたこと以外を、あるフェーズの Pod について仮定してはいけない、としています。たとえば Running は、少なくとも 1 つのコンテナが動いているか、起動・再起動の途中であることしか表しません。Pod がリクエストに応えられるかどうかは、第5回で見た Pod の conditions の Ready(リクエストに応えられ、Service の転送先に加えてよい状態)で表されます。Running だからといって、Ready だとは限らないと考えられます。

参考:Pod Lifecycle | Kubernetes

Running の意味に「再起動の途中」が含まれていることに注意します。Pod のフェーズは、コンテナが動いているかどうかを細かく表すものではありません。Pod の中のコンテナが止まって再起動を待っているときも、Pod は Running のことがあります。コンテナの様子は次に説明するコンテナの状態で確かめます。

参考:Pod Lifecycle | Kubernetes

kubectl get pods の STATUS 列にも、フェーズと同じ名前の値が表示されることがあります。ただし STATUS 列はフェーズそのものではありません。公式ドキュメントによると、起動に失敗し続けている Pod では、kubectl のコマンドの STATUS 欄に CrashLoopBackOff が表示されることがあります。削除中の Pod では Terminating が表示されることがあります。公式ドキュメントは、利用者が直感的に読み取るための kubectl の表示(Status)と、Kubernetes のデータの一部として決められている Pod の phase を混同しないよう注意しています。公式ドキュメントの例では、表示は次のとおりです。

NAMESPACE               NAME               READY   STATUS             RESTARTS   AGE
alessandras-namespace   alessandras-pod    0/1     CrashLoopBackOff   200        2d9h

参考:Pod Lifecycle | Kubernetes

NAMESPACE 列は Pod が属する Namespace(第3回)です。READY 列の 0/1 は Pod の 1 つのコンテナのうち、準備ができているものが 0 個であることを表すと考えられます。RESTARTS 列の 200 は再起動された回数を表すと考えられます。公式ドキュメントで説明されているのは kubectl describe pod の Restart Count のほうで、コンテナが再起動された回数を表し、restartPolicy が Always のコンテナで失敗の繰り返しに気づくのに役立つとされています。RESTARTS 列はこの Restart Count と同じ値を示すと考えられます。

参考:Debug Running Pods | Kubernetes、Pod Lifecycle | Kubernetes

フェーズは、Pod が削除されるときやノードが失われたときにも変わります。公式ドキュメントによると、Kubernetes 1.27 からは、kubelet は削除された Pod を、API から消す前に終わりのフェーズ(コンテナの終了コードに応じて Failed か Succeeded)に移します(例外は下の補足)。ノードが故障したり、クラスタのほかの部分とつながらなくなったりしたときは、そのノードのすべての Pod のフェーズを Failed にする決まりが適用されます。

参考:Pod Lifecycle | Kubernetes

コンテナの状態

公式ドキュメントによると、kube-scheduler が Pod をノードに割り当てると、そのノードの kubelet がコンテナランタイム(第4回)を使って Pod のコンテナを作り始めます。Kubernetes は Pod 全体のフェーズとは別に、Pod の中のコンテナごとの状態を記録しています。コンテナの状態は Waiting、Running、Terminated の 3 つです。それぞれを次の表にまとめます。

状態意味kubectl で一緒に見える情報
WaitingRunning でも Terminated でもない状態。イメージの取得など、起動を終えるのに必要な処理の途中その状態にある理由(Reason)
Running問題なく実行しているRunning になった時刻
Terminated実行を始めたあと、最後まで動いて終わったか、何らかの理由で失敗した理由、終了コード、実行を始めた時刻と終えた時刻

参考:Pod Lifecycle | Kubernetes

公式ドキュメントはコンテナの状態を確かめる方法として kubectl describe pod <Pod の名前> を挙げています。表示には、Pod の中のコンテナごとの状態が含まれます。

参考:Pod Lifecycle | Kubernetes

以下の図は、1 つのコンテナの状態が kubelet の再起動によって移り変わる様子を表しています。

kubelet の再起動によるコンテナの状態の移り変わり(Waiting、Running、Terminated)

コンテナは Waiting から Running に進み、終了すると Terminated になります。restartPolicy が再起動を求めていれば、kubelet はコンテナを再起動します。再起動を待っているあいだ、コンテナは Waiting になり、理由には CrashLoopBackOff などが入ります。再起動しなければ、コンテナは Terminated のままです。

参考:Pod Lifecycle | Kubernetes、Debug Init Containers | Kubernetes

図の輪は、同じコンテナが何度も回っているように見えますが、中身は少し違います。コンテナの状態はマニフェストの spec.containers に書いたコンテナの名前(後半の例では app)ごとに、status.containerStatuses の 1 つの項目として記録されます。図の輪はその項目の値の移り変わりです。後の「restartPolicy で決まる再起動」で見るとおり、再起動のたびに、その名前のコンテナは新しく作り直されます。kubectl describe pod の表示では、今のコンテナの状態が State: に、1 つ前のコンテナの終わり方が Last State: に出ると考えられます。次に引用する公式ドキュメントの例でも、Last State: に終了したときの情報が出ています。

参考:Pod | Kubernetes API Reference、Determine the Reason for Pod Failure | Kubernetes、Pod Lifecycle | Kubernetes

公式ドキュメントには失敗を繰り返すコンテナを kubectl describe pod で表示した例があります。Pod の起動時に先に動く init コンテナ(第10回)の例ですが、状態を表す項目はふつうのコンテナと同じです。2 つ目の init コンテナの部分は次のとおりです。

  <init-container-2>:
    Container ID:    ...
    ...
    State:           Waiting
      Reason:        CrashLoopBackOff
    Last State:      Terminated
      Reason:        Error
      Exit Code:     1
      Started:       ...
      Finished:      ...
    Ready:           False
    Restart Count:   3
    ...

参考:Debug Init Containers | Kubernetes

見るところを次の表にまとめます。

見るところ表しているもの
State:今の状態。例では Waiting で、理由(Reason:)は CrashLoopBackOff
Last State:1 つ前の状態。例では Terminated で、理由は Error、終了コード(Exit Code:)は 1
Started:、Finished:1 つ前の実行を始めた時刻と、終えた時刻
Ready:コンテナの準備ができているか。例では False。コンテナが再起動を待っていて動いていないためと考えられる
Restart Count:コンテナが再起動された回数。例では 3 回

参考:Debug Init Containers | Kubernetes、Debug Running Pods | Kubernetes、Pod Lifecycle | Kubernetes

公式ドキュメントの Determine the Reason for Pod Failure のページには、status.containerStatuses の各項目の lastState.terminated から、終了の情報を取り出す例があります。同じページの出力例では、lastState.terminated に exitCode などが入っています。describe の Last State: はこの lastState を表示したものと考えられます。後半の「手元で試す」では、ここから終了コードを取り出します。

参考:Determine the Reason for Pod Failure | Kubernetes

Waiting の理由は CrashLoopBackOff だけではありません。たとえば ImagePullBackOff は、イメージの名前の誤りなどで Kubernetes がイメージを取得できず、コンテナを始められないことを表します。公式ドキュメントによると、名前の BackOff の部分は待ち時間を延ばしながらイメージの取得を試し続けることを表します。v1.37 時点では、待ち時間は Kubernetes に組み込まれた上限の 300 秒(5 分)で頭打ちになります。

参考:Images | Kubernetes

restartPolicy で決まる再起動

コンテナの中で動くプログラムは、終わるときに終了の結果を表す数を返します。この数を終了コード(exit code、exit status)と呼びます。0 は成功、0 以外は失敗(エラー)を表します。kubelet は Pod の spec.restartPolicy とこの終了コードから、終了したコンテナを再起動するかどうかを決めます。restartPolicy の値は次の 3 つで、既定値は Always です。

値kubelet がすること
Alwaysどんな理由で終了しても、コンテナを再起動する
OnFailure0 以外の終了コードで終わったときだけ、コンテナを再起動する
Never終了したコンテナを自動では再起動しない

参考:Pod Lifecycle | Kubernetes

終了コードごとに再起動されるかどうかを、次の表にまとめます。公式ドキュメントの比較の表のうち、ふつうのコンテナの部分です。

終了コードAlwaysOnFailureNever
0(成功)再起動する再起動しない再起動しない
0 以外(失敗)再起動する再起動する再起動しない

参考:Pod Lifecycle | Kubernetes

再起動されないコンテナが終わると、Pod のフェーズも終わりに向かいます。たとえば Never の Pod で、コンテナが 1 で終了すると、Pod は Failed のままになります。公式ドキュメントの restartPolicy: Never のマニフェストの例に、そう説明するコメントが書かれています。OnFailure か Never の Pod で、すべてのコンテナが 0 で終了すれば、Pod は Succeeded になります。Succeeded の定義(すべてのコンテナが成功して終了し、再起動されない)と、上の比較の表から、そう言えます。Always の Pod では、コンテナが終わるたびに再起動されるので、コンテナが終了するだけでは、Pod は Running から先へ進みません。

参考:Pod Lifecycle | Kubernetes

restartPolicy の値はワークロードのリソースによって制限されます。第1回で見たとおり、Deployment の Pod では Always しか指定できません。動き続けるアプリケーションのためのリソースだからです。一方、終わりまで動いて止まる処理のための Job では、Never か OnFailure しか指定できません。Job は第16回で扱います。

参考:Pod Lifecycle | Kubernetes、Deployments | Kubernetes、Jobs | Kubernetes

ここでいう再起動は、第8回で見たとおり、Pod の中でコンテナを動かし直すものです。公式ドキュメントによると、restartPolicy に従った kubelet の再起動は、同じ Pod の中で、同じノードで動く、置き換えのコンテナを作るもの です。Pod は作り直されないので、Pod の名前も UID も変わりません。コンテナは新しく作り直されるので、前のコンテナで動いていたプログラムの続きから動くわけではないと考えられます。

参考:Pod Lifecycle | Kubernetes

前のコンテナが書き出したログは、すぐには消えません。一般に、プログラムが文字を書き出す標準の出力先を標準出力、エラーの知らせを書き出す出力先を標準エラー出力と呼びます。公式ドキュメントによると、コンテナで動くアプリケーションのログの残し方は、標準出力と標準エラー出力に書き出す方法がいちばん簡単で、広く使われています。Kubernetes は動いている Pod の各コンテナのログを取り込み、ログは kubectl logs で取り出せます。既定では、コンテナが再起動すると、kubelet は終了したコンテナを 1 つ、そのログと一緒に残します。前のコンテナのログは kubectl logs --previous で取り出せます。

参考:Logging Architecture | Kubernetes、kubectl logs | Kubernetes

CrashLoopBackOff と再起動までの待ち時間

公式ドキュメントは、コンテナの失敗を Kubernetes がどう扱うかを、次の順で説明しています。

段階起きること
最初の失敗Pod の restartPolicy に従って、すぐに再起動を試みる
失敗の繰り返し2 回目からの再起動の前に、倍々に延びる(指数関数的な)待ち時間を入れる
CrashLoopBackOff失敗と再起動を繰り返しているコンテナに、待ち時間の仕組みが働いている状態
待ち時間のリセットコンテナが一定の時間(たとえば 10 分)問題なく動くと、待ち時間を元に戻し、次の失敗を最初の失敗として扱う

参考:Pod Lifecycle | Kubernetes

待ち時間の具体的な値も同じページに書かれています。v1.37 時点の公式ドキュメントによると、Pod のコンテナが終了すると、kubelet は 10 秒、20 秒、40 秒……と倍に延びる待ち時間を置いて再起動し、待ち時間は 300 秒(5 分)で頭打ちになります。コンテナが 10 分のあいだ問題なく動くと、kubelet はそのコンテナの待ち時間を元に戻します。同じページには「最初の失敗ではすぐに再起動を試みる」という説明もあります。最初の再起動の前にも 10 秒待つのかどうかは、公式ドキュメントの 2 つの記述からは読み取れません。

参考:Pod Lifecycle | Kubernetes

CrashLoopBackOff は crash(異常終了)、loop(繰り返し)、back off を組み合わせた名前です。back off は一般に「引き下がる」という意味の英語で、ここでは再起動の間隔を空けていくことを指していると考えられます。公式ドキュメントによると、CrashLoopBackOff は Pod を一覧したり詳しく表示したりしたときに、kubectl の出力に現れる値です。Pod の中のコンテナが正しく起動できず、失敗を繰り返していることを表します。公式ドキュメントはこれを condition または event と呼んでいますが、第5回で見た Pod の conditions の種類の 1 つではありません。conditions の種類として挙がっているのは PodScheduled や Ready などです。

参考:Pod Lifecycle | Kubernetes

CrashLoopBackOff の原因として、公式ドキュメントは次の 5 つを挙げています。

  • コンテナを終了させてしまう、アプリケーションのエラー
  • 環境変数の誤りや設定ファイルの欠けなどの、設定の誤り
  • 起動に必要なメモリや CPU が足りないこと
  • アプリケーションが決まった時間のうちに動き始めず、ヘルスチェックに失敗すること
  • liveness Probe や startup Probe が失敗を返すこと(第34回)

参考:Pod Lifecycle | Kubernetes

ヘルスチェックは、アプリケーションが正常に動いているかを確かめることです。Kubernetes では、Pod の中のコンテナが正常かどうかを見張り続けるために、Probe を定義できます。Probe は第34回で扱います。

参考:Pod Lifecycle | Kubernetes

再起動を繰り返しても、原因は取り除かれません。原因を調べる方法として、公式ドキュメントはまず kubectl logs <Pod の名前> でコンテナのログを見ることを挙げ、これが多くの場合いちばん直接的な方法だとしています。次に kubectl describe pod <Pod の名前> で Pod のイベントを見て、設定や資源の問題の手がかりを探します。起動しない Pod の原因の調べ方は第41回でまとめて扱います。

参考:Pod Lifecycle | Kubernetes

待ち時間の上限はノードごとに変えられます。v1.37 時点では、KubeletCrashLoopBackOffMax の機能(v1.35 からベータで、既定で有効)により、上限を "1s" から "300s" のあいだに設定できます。設定する場所はノードごとに kubelet に渡す設定(KubeletConfiguration)の crashLoopBackOff.maxContainerRestartPeriod です。kubelet の設定の API リファレンスによると、このフィールドは再起動の待ち時間が延びていく上限で、書かなければ 300 秒になります。待ち時間は 10 秒から始まって倍に延び、設定した上限で頭打ちになります。上限を 10 秒より短くしたときは、最初の待ち時間も上限の値になります。この回の手順では kubelet の設定を変えないので、待ち時間は既定の 10 秒から 300 秒のままだと考えられます。

参考:Pod Lifecycle | Kubernetes、Feature Gates | Kubernetes、Kubelet Configuration (v1beta1) | Kubernetes

手元で試す

ここまでの内容を手元の kind のクラスタで試す手順を示します。5 秒動いて失敗するコンテナを restartPolicy: Always の Pod で動かし、CrashLoopBackOff になって、再起動までの待ち時間が延びていく様子を観察します。そのあと、同じコンテナを restartPolicy: Never の Pod で動かし、再起動されずに Pod が Failed になることを比べます。待ち時間が延びる様子を上限まで見ると、観察には 10 分ほどかかります。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。

項目内容
kindv0.33.0
Kubernetesv1.37.0(kind v0.33.0 の既定のノードイメージ)
ノード構成シングルノード(クラスタ名 kind):コントロールプレーン 1
必要なものDocker、kubectl(v1.36〜v1.38)

参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes

kind でクラスタを作る手順は、以下の記事で解説しています。この回ではシングルノードのクラスタを使います。コンテナの再起動はノードの中で kubelet が行うもので、ノードの数に関係しないからです。

基本仕様 ・ 第2回 kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。

シングルノードのクラスタを用意する

まず、前の回のクラスタが残っていないかを確かめます。

console
$ kind get clusters

kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。

console
$ kind create cluster
$ kind get clusters

kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。

参考:Quick Start | kind

続けて、ノードを一覧します。

console
$ kubectl get nodes --context kind-kind

ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。

参考:kubectl get | Kubernetes、Node Status | Kubernetes

わざと失敗するコンテナを動かす

以下は、5 秒動いたあと終了コード 1 で終わるコンテナを、restartPolicy: Always で動かす Pod のマニフェストです。crash-demo.yaml という名前で、コマンドを実行するディレクトリに保存します。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: crash-demo
spec:
  restartPolicy: Always
  containers:
  - name: app
    image: busybox:1.28
    command: ['sh', '-c', 'echo "Started. Exiting with code 1 in 5 seconds."; sleep 5; exit 1']

参考:Pod Lifecycle | Kubernetes、Pod | Kubernetes API Reference

マニフェストのポイントを次の表にまとめます。

フィールド内容
spec.restartPolicyAlways。既定値なので書かなくても同じだが、試すことをはっきりさせるために書いている
spec.containers[*].imagebusybox:1.28。公式ドキュメントの restartPolicy の例でも使われているイメージ
spec.containers[*].commandコンテナで動かすコマンド。シェル(sh)で、1 行の文字を書き出し(echo)、5 秒待ち(sleep 5)、終了コード 1 で終わる(exit 1)

参考:Pod Lifecycle | Kubernetes、Define a Command and Arguments for a Container | Kubernetes

BusyBox はよく使う UNIX のコマンドを小さく作ったものを、1 つの小さなプログラムにまとめたものです。公式ドキュメントの Pod Lifecycle のページの例でも、busybox:1.28 のイメージで sh -c を使い、echo などのコマンドを動かしています(sleep は同じページの registry.k8s.io/busybox の例で使われています)。そのため、短いシェルのコマンドを動かすのに向いていると考えられます。これまで使ってきた nginx は動き続ける Web サーバーなので、この回では、決まった時間で終わるコマンドを動かせる busybox を使います。

参考:About BusyBox、Pod Lifecycle | Kubernetes

公式ドキュメントによると、command に書いたコマンドは、イメージに決められている既定のコマンドを上書きします。既定のコマンドはイメージを作るときに決めておく、コンテナを起動したときに動かすコマンドです。command を書かなければ、既定のコマンドが動きます。command は Pod を作ったあとで変えられません。

参考:Define a Command and Arguments for a Container | Kubernetes、Pod | Kubernetes API Reference

command の値の ['sh', '-c', '...'] は [ ] で囲んで , で区切った、YAML でリストを 1 行で書く書き方です。一般に、- で始まる 3 行に分けて書いたのと同じ意味になります。1 つ目の sh は動かすプログラム(シェル)で、-c は一般に、続く文字列をコマンドとして実行するようシェルに指示するものです。3 つ目の文字列の中身は ; で区切った echo、sleep 5、exit 1 の 3 つのコマンドです。command はシェルを通さずに実行されるので、; で区切った複数のコマンドを動かすために、sh -c でシェルに渡しています。公式ドキュメントの Pod Lifecycle のページの restartPolicy の例でも、busybox:1.28 のイメージで同じ書き方をしています。

参考:Pod | Kubernetes API Reference、Pod Lifecycle | Kubernetes、Define a Command and Arguments for a Container | Kubernetes

Pod を作り、すぐに Pod の監視を始めます。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。

console
$ kubectl apply -f crash-demo.yaml --context kind-kind
$ kubectl get pods --watch --context kind-kind

参考:kubectl get | Kubernetes

crash-demo の行が、状態が変わるたびに書き足されていくはずです。見るところを次の表にまとめます。表の内容は公式ドキュメントの説明から考えられる様子です。

見るところ起きると考えられること
STATUS 列コンテナが動いている 5 秒ほどは Running になると考えられる。終了と再起動を繰り返すうちに、CrashLoopBackOff が表示されるようになる
RESTARTS 列kubelet がコンテナを再起動するたびに 1 つ増えると考えられる
AGE 列Pod が作られてからの時間。RESTARTS が増えた行どうしの AGE の差が、再起動の間隔の目安になると考えられる

参考:Pod Lifecycle | Kubernetes、Debug Running Pods | Kubernetes

監視を始めてすぐは、STATUS 列が Running にならない時間があると考えられます。クラスタを作り直したばかりなら、ノードに busybox:1.28 のイメージがまだ無いからです。latest 以外のタグを付けたイメージは、ノードに無いときだけ取得されます(第5回、第8回)。イメージをダウンロードしているあいだ、Pod のフェーズは Pending で、STATUS 列には ContainerCreating のような値が出ると考えられます。コンテナが終了した直後にも、CrashLoopBackOff になる前に、終了の理由を表す Error のような値が出ると考えられます。そのあと、終了と再起動を繰り返すうちに CrashLoopBackOff が表示されるようになります。

参考:Pod Lifecycle | Kubernetes、Images | Kubernetes

間隔の見方を説明します。「CrashLoopBackOff と再起動までの待ち時間」で見たとおり、最初の再起動の前に待つかどうかは公式ドキュメントからは読み取れません。そのため、最初の間隔は短いことも、10 秒ほどのこともあると考えられます。2 回目からの間隔がおよそ倍に延びていくか を見てください。公式ドキュメントの待ち時間は 10 秒、20 秒、40 秒……で、160 秒の次からは 300 秒で頭打ちになります。AGE の差には、コンテナが動いていた 5 秒も含まれます。kubelet がノードの様子を API に反映するまでに少し遅れることもあるので、差はぴったりの値にはならないと考えられます。

参考:Pod Lifecycle | Kubernetes、Kubelet Sync Loop | Kubernetes

答え合わせの目安として、コンテナが最初に起動してから何秒後に RESTARTS が増えるかを、2 通りの読み方で計算した値を次の表にまとめます。表の値は、コンテナが毎回 5 秒動き、待ち時間が公式ドキュメントの値どおりだと仮定した計算で、実際の値ではありません。

RESTARTS最初はすぐ再起動する場合最初も 10 秒待つ場合
1約 5 秒後約 15 秒後
2約 20 秒後約 40 秒後
3約 45 秒後約 85 秒後
4約 90 秒後約 170 秒後
5約 175 秒後約 335 秒後
6約 340 秒後約 640 秒後
7約 645 秒後(約 945 秒後)

3 分(180 秒)ほどたったころの RESTARTS は、最初はすぐ再起動する場合なら 5、最初も 10 秒待つ場合なら 4 になる計算です。そのころに 4 か 5 になっていれば、どちらかの読み方に合っています。待ち時間が上限の 300 秒になるのは、最初はすぐ再起動する場合は 6 回目から 7 回目の再起動のあいだ、最初も 10 秒待つ場合は 5 回目から 6 回目の再起動のあいだです。どちらの場合も、300 秒の待ち時間のあとの再起動を見るには、全部で 10 分ほど見守ることになります。間隔が延びていくのを確かめたら、Ctrl+C を押して監視を止めます。

コンテナの状態と前のコンテナのログを確かめる

Pod を詳しく表示します。

console
$ kubectl describe pod crash-demo --context kind-kind

Containers: の下の app: の部分を見ます。「コンテナの状態」で引用した例と同じく、次のような表示が見つかるはずです。

見るところ表示されるはずのこと
State:再起動を待っているときは Waiting で、Reason: は CrashLoopBackOff。5 秒の実行中に表示したときは Running
Last State:Terminated。Exit Code: は 1。Reason: は例と同じく Error になると考えられる
Started:、Finished:1 つ前の実行を始めた時刻と終えた時刻。差はおよそ 5 秒になると考えられる
Restart Count:再起動された回数。kubectl get pods の RESTARTS 列と同じ値になると考えられる

参考:Debug Init Containers | Kubernetes、Debug Running Pods | Kubernetes、Pod Lifecycle | Kubernetes

表示の末尾には Events: の欄があります。第8回で引用した公式ドキュメントの例では、From 列が kubelet のイベントとして、イメージの取得(Pulling、Pulled)、コンテナの作成(Created)、起動(Started)が記録されていました。From 列はイベントを記録したコンポーネントを表します。この回の Pod でも、kubelet がコンテナを作り直して起動するたびのイベントが記録されると考えられます。

参考:Debug Running Pods | Kubernetes

次に、Pod のフェーズと、1 つ前のコンテナの終了コードを取り出します。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。containerStatuses[0] は一覧の最初の項目(この Pod の唯一のコンテナ app)を指します。

console
$ kubectl get pod crash-demo -o jsonpath='{.status.phase}{"\n"}{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}' --context kind-kind

参考:JSONPath Support | Kubernetes、Determine the Reason for Pod Failure | Kubernetes

1 行目はフェーズで、Running になると考えられます。STATUS 列に CrashLoopBackOff が表示されていても、フェーズの Running には「再起動の途中」が含まれるからです。2 行目は 1 つ前のコンテナの終了コードで、1 になるはずです。STATUS 列とフェーズが別物 であることを、ここで確かめてください。

参考:Pod Lifecycle | Kubernetes

最後に、1 つ前のコンテナのログを表示します。

console
$ kubectl logs crash-demo --previous --context kind-kind

参考:kubectl logs | Kubernetes、Logging Architecture | Kubernetes

マニフェストの echo で書き出した Started. Exiting with code 1 in 5 seconds. の 1 行が表示されるはずです。再起動のたびにコンテナは置き換わりますが、kubelet が終了したコンテナを 1 つ残しているので、前のコンテナのログを読めます。実際のアプリケーションが CrashLoopBackOff になったときも、このログが原因を探すいちばんの手がかりになります。

参考:Logging Architecture | Kubernetes、Pod Lifecycle | Kubernetes

restartPolicy を Never にして比べる

同じコンテナを restartPolicy: Never の Pod で動かします。以下のマニフェストを never-demo.yaml という名前で保存します。crash-demo.yaml との違いは metadata.name と spec.restartPolicy だけです。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: never-demo
spec:
  restartPolicy: Never
  containers:
  - name: app
    image: busybox:1.28
    command: ['sh', '-c', 'echo "Started. Exiting with code 1 in 5 seconds."; sleep 5; exit 1']

参考:Pod Lifecycle | Kubernetes

Pod を作ります。

console
$ kubectl apply -f never-demo.yaml --context kind-kind

20 秒ほど待ってから、Pod を一覧します。コンテナが 5 秒動いて終わるのを待つためです。

console
$ kubectl get pods --context kind-kind

never-demo の行の STATUS 列がまだ Running などのままなら、少し待ってからもう一度実行します。never-demo の行の RESTARTS 列は 0 のままになるはずです。Never では、kubelet は終了したコンテナを再起動しないからです。STATUS 列は CrashLoopBackOff にはならず、終了したときの理由を表す Error のような値が表示されると考えられます。crash-demo の行と見比べてください。

参考:Pod Lifecycle | Kubernetes

続けて、フェーズを取り出します。

console
$ kubectl get pod never-demo -o jsonpath='{.status.phase}{"\n"}' --context kind-kind

Failed が表示されるはずです。コンテナが失敗して終了し、再起動もされないので、Pod は終わりのフェーズに進みます。公式ドキュメントの Never の例でも、終了コード 1 で終わった Pod は Failed のままになると説明されています。crash-demo のフェーズが Running だったのと比べると、restartPolicy によって Pod のライフサイクルの行き先が変わることがわかります。

参考:Pod Lifecycle | Kubernetes

kubectl describe pod never-demo --context kind-kind で詳しく表示すると、State: は Terminated になり、Exit Code: に 1 が表示されるはずです。公式ドキュメントによると、Terminated のコンテナでは、理由、終了コード、実行を始めた時刻と終えた時刻が表示されます。

参考:Pod Lifecycle | Kubernetes

片付け

作った 2 つの Pod を、マニフェストを指定して削除し、消えたことを確かめます。-f を 2 つ並べると、2 つのファイルをまとめて指定できます。

console
$ kubectl delete -f crash-demo.yaml -f never-demo.yaml --context kind-kind
$ kubectl get pods --context kind-kind

kubectl delete は既定では、Pod が消えるまで待ってから終わります。動いているコンテナには終了までの猶予の時間(既定では 30 秒)があるので、終わるまで少しかかることがあります。kubectl get pods の一覧に crash-demo と never-demo が無ければ、削除できています。

参考:kubectl delete | Kubernetes、kubectl Quick Reference | Kubernetes、Pod Lifecycle | Kubernetes

最後に、クラスタを削除し、消えたことを確かめます。

console
$ kind delete cluster
$ kind get clusters

kind delete cluster は存在しないクラスタを指定してもエラーを返しません。kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。

参考:Quick Start | kind、kubectl config | Kubernetes

注意点

Pod のライフサイクルと再起動でつまずきやすい点と、後から変えられない点、バージョンで変わる点を、次の表にまとめます。

項目内容
STATUS 列はフェーズではないCrashLoopBackOff や Terminating は kubectl の表示。フェーズは status.phase で確かめる
Running でも動いているとは限らないフェーズの Running には、コンテナの起動や再起動の途中も含まれる。コンテナの様子はコンテナの状態で見る
restartPolicy はリソースによって制限されるDeployment の Pod は Always だけ、Job の Pod は Never か OnFailure だけ
再起動は同じ Pod の中で起きる同じノードの同じ Pod の中で、コンテナが置き換わる。Pod が削除されたりノードが故障したりしたときは、コンテナの再起動では元に戻らない(第8回)
再起動しても原因は直らないCrashLoopBackOff の原因はアプリケーションのエラーや設定の誤りなど。kubectl logs --previous と kubectl describe pod で原因を探す
前のコンテナのログは 1 つだけ残る既定では、kubelet が残す終了したコンテナは 1 つ。それより前のログは kubectl logs では読めない
command は後から変えられないPod を作ったあとでは変えられない。変えたいときは Pod を作り直す
待ち時間の値はバージョンと設定で変わるv1.37 時点の既定は 10 秒から倍に延びて 300 秒で頭打ち。上限はノードごとに短くできる。最初の待ち時間を 1 秒に、上限を 60 秒に縮める ReduceDefaultCrashLoopBackOffDecay の機能は、v1.37 時点ではアルファで既定では無効

参考:Pod Lifecycle | Kubernetes、Deployments | Kubernetes、Jobs | Kubernetes、Logging Architecture | Kubernetes、Define a Command and Arguments for a Container | Kubernetes、Feature Gates | Kubernetes

CrashLoopBackOff は Kubernetes の不具合ではなく、kubelet が restartPolicy に従って再起動を続けながら、間隔を空けている状態です。待ち時間を短くする設定はあっても、失敗の原因はアプリケーションや設定の側で直す必要があります。

参考:Pod Lifecycle | Kubernetes

関連記事

Pod の中に複数のコンテナを入れる init コンテナとサイドカーの仕組みと、サイドカーの再起動のしかたについては、以下の記事で解説しています。

基本仕様 ・ 第10回 init コンテナとサイドカーの仕組み1 つの Pod に複数のコンテナを入れる仕組みを図で説明します。起動時に先に動いて終わる init コンテナと、アプリケーションのコンテナの横で動き続けるサイドカーの違い、起動と終了の順番、コンテナどうしが共有するものを押さえ、kind のクラスタで起動の順番とファイルや通信の共有を確かめる手順を紹介します。

restartPolicy に Never か OnFailure を使う、終わりまで動いて止まる処理のための Job と CronJob については、以下の記事で解説しています。

基本仕様 ・ 第16回 ・ 準備中 Job と CronJob の仕組み 公開したら、ここから読めるようになります。

コンテナの再起動のきっかけにもなる liveness Probe などの Probe については、以下の記事で解説しています。

基本仕様 ・ 第34回 ・ 準備中 liveness・readiness・startup Probe の違い 公開したら、ここから読めるようになります。

Kubernetes 基本仕様の目次へ