Certified Kubernetes Application Developer (CKAD) #2 Pod とコンテナのライフサイクル: restart policy とコンテナの状態

#1 では、2 時間の実技を運用するための kubectl 環境 (alias・dry-run・generators・vim のインデント・context 切り替え) を手に馴染ませました。セットアップが終わったので、ここからは CKAD で最も小さいデプロイ単位である Pod に入ります。Pod はすべてのワークロード (Deployment・Job・DaemonSet) が結局のところ作り出す実体であり、試験で「この Pod がなぜ起動しないのか」を診断する能力は、ドメインを問わず点数に直結します。

この記事は 2 つの軸で整理します。1 つは Pod がどのような状態をたどるのか (ライフサイクルと restartPolicy) であり、もう 1 つは その中のコンテナがなぜその状態で止まっているのか (コンテナの状態と終了コード) です。2 つの軸をコマンドとマニフェストで直接確認しながら身につけます。

Pod がたどるライフサイクル #

Pod は作られてから消えるまで、決まった phase をたどります。k get podSTATUS 列に見える値の多くがこの phase であり、残りはコンテナの状態の reason です。両者を混ぜてしまうと診断がずれるので、まず phase から区別します。K8s 実務トラック #3 で Pod の基本概念を扱ったなら、ここではその状態遷移を試験の視点に絞って見ていきます。

phase意味
PendingAPI サーバーには登録されたが、まだコンテナが実行前。スケジュール待ち、イメージ pull、volume マウント中の段階
RunningPod がノードにバインドされ、すべてのコンテナが作成された。最低 1 つのコンテナが実行中、または開始・再起動中
Succeededすべてのコンテナが正常 (終了コード 0) に終了し、再起動されない。Job の正常完了の形
Failedすべてのコンテナが終了し、そのうち 1 つ以上が異常 (0 以外のコード) 終了
Unknownノードとの通信が切れて Pod の状態が分からない。通常はノード障害

phase は kubectl get pod -o jsonpath で直接取り出せます。

k get pod nginx -o jsonpath='{.status.phase}'

ここでの肝は、Pending が長く続けばスケジュール・イメージ・volume の問題であり、Failed や繰り返す再起動はコンテナ内部の問題であるという一次分岐です。どちら側かによって、見るべき場所が変わります。

restartPolicy: 誰が何を使うのか #

restartPolicy は、コンテナが 終了したときに kubelet が同じノードで再起動するか を決めます。Pod 単位の設定であり、値は 3 つです。

動作デフォルトで使うワークロード
Always終了コードに関係なく常に再起動。サービスが起動し続ける必要がある場合Deployment、ReplicaSet、DaemonSet、StatefulSet
OnFailure異常 (0 以外のコード) 終了のときだけ再起動。正常完了 (0) ならそのままJob、CronJob
Never終了コードに関係なく絶対に再起動しない一度だけ実行する単発の作業

ここに試験でよく出る落とし穴があります。Deployment が作る Pod は restartPolicy が常に Always に固定されます。 Deployment マニフェストに restartPolicy: OnFailure を入れると検証エラーになります。逆に Job は restartPolicy を明示する必要があり、Always は許可されません。つまり、ワークロードの種類が restartPolicy の取り得る値を制約します。

restartPolicy を直接指定した Pod マニフェストは次のとおりです。

apiVersion: v1
kind: Pod
metadata:
  name: oneshot
spec:
  restartPolicy: OnFailure   # 정상 완료면 멈추고, 실패하면 재시작
  containers:
    - name: worker
      image: busybox:1.36
      command: ["sh", "-c", "echo done; exit 0"]

上の Pod は exit 0 で正常終了するので、restartPolicy: OnFailure では再起動せず phase が Succeeded に進みます。commandexit 1 に変えると異常終了になり、kubelet が再起動を繰り返して次節の CrashLoopBackOff が再現されます。

restartPolicy の再起動は、常に 同じノードでコンテナを再起動 することであって、Pod を別のノードに移すことではありません。ノード自体が死んだときに別のノードに新しい Pod を立てるのは、Deployment・ReplicaSet のような上位コントローラーの役目です。

コンテナの状態と reason を読む #

phase が Pod 全体のマクロな状態だとすれば、コンテナの状態は コンテナ一つ一つのミクロな状態 です。k describe podState 項目に現れ、3 つあります。

状態意味
Waitingまだ実行前。イメージ pull、依存関係の待機など。reason になぜ待っているかが記される
Running正常に実行中。startedAt の時刻を伴う
Terminated実行が終わった。exitCodereasonstartedAtfinishedAt を伴う

診断の 8 割は、Waiting と Terminated に付く reason を読む作業です。試験で繰り返し出会う reason をまとめると次のとおりです。

reason状態意味と一次原因
ContainerCreatingWaitingコンテナ作成中。volume マウント・イメージ準備の段階。長く止まれば volume・secret の欠落を疑う
ImagePullBackOffWaitingイメージ pull 失敗が繰り返されてバックオフ中。イメージ名のタイプミス、タグなし、private レジストリの認証欠落
ErrImagePullWaitingイメージ pull が即座に失敗。ImagePullBackOff の直前の段階
CrashLoopBackOffWaitingコンテナが開始直後に死ぬのを繰り返し、再起動間隔を延ばしながら待機中。コンテナ内部のプロセスの問題
OOMKilledTerminatedメモリ上限 (limits.memory) 超過でカーネルが強制終了。終了コード 137
CompletedTerminated正常 (終了コード 0) 終了。Job の成功の形
ErrorTerminated0 以外のコードで終了。アプリケーションのエラー

ここで最も誤解が多いのが、CrashLoopBackOff はエラーではなく状態 だという点です。コンテナがしきりに死ぬので、kubelet が再起動間隔を 10 秒・20 秒・40 秒のように指数で延ばして (最大 5 分) 待機しているという意味です。原因は BackOff 自体ではなく コンテナがなぜ死ぬのか にあるので、ログと終了コードまで下りていく必要があります。

終了コードの読み方 #

Terminated 状態の exitCode は原因を素早く絞り込んでくれます。試験で覚えておくとよい値です。

終了コード意味
0正常終了。Completed
1一般的なアプリケーションのエラー。ログを見る必要がある
137128 + 9 (SIGKILL)。強制終了。OOMKilled か grace period 超過後の強制 kill
143128 + 15 (SIGTERM)。正常終了シグナルを受けて終わる。通常は正常な graceful shutdown

終了コードは次の一行ですぐ確認できます。

k get pod oneshot -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

137 を見たら、まず OOMKilled かどうかを確認し (describe の reason)、そうであれば limits.memory を増やすか、アプリケーションのメモリ使用を減らす方向です。リソース limits は #16 で詳しく扱います。

診断コマンドセット #

状態の分類が終わると、実際に見ていくコマンドは決まっています。この順序を手に馴染ませることが、試験のトラブルシューティング速度を決めます。

# 1) 넓게 본다: 어느 Pod가 어떤 상태이고 재시작 횟수는 몇 번인가
k get pod -o wide

# 2) 한 Pod를 깊게 본다: 컨테이너 State·reason·exitCode·이벤트가 한 화면에
k describe pod oneshot

# 3) 현재 컨테이너 로그
k logs oneshot

# 4) 직전(크래시 전) 컨테이너 로그: CrashLoopBackOff 진단의 핵심
k logs oneshot --previous

# 5) 클러스터 이벤트를 시간순으로 (스케줄 실패·pull 실패가 여기 찍힘)
k get events --sort-by=.metadata.creationTimestamp

k get podRESTARTS 列は一次シグナルです。数字が速く増えれば CrashLoop、0 のまま Pending ならスケジュール・イメージ・volume の問題です。describe 下部の Events セクションには Failed to pull imageBack-off restarting failed containerOOMKilled といったメッセージが直接出るので、reason の根拠をここで確認します。

CrashLoopBackOff の決定的な手がかりは k logs --previous です。コンテナがすでに死んで現在のログが空でも、直前のインスタンスのログには死んだ理由 (設定ファイルなし、ポート競合、誤った引数など) が残っているからです。

試験定番: 「この Pod がなぜ再起動を繰り返すのか」 #

CKAD でよく出る形式が、動作が壊れた Pod を渡して原因を見つけて直させるものです。次の順序で下りていけば、ほぼすべてのケースが解けます。

  1. k get pod -o wide で STATUS と RESTARTS を確認します。CrashLoopBackOff で RESTARTS が増えているかを見ます。
  2. k describe pod <名前> でコンテナの State、lastState の reason と exitCode、下部の Events を読みます。
  3. reason が ImagePullBackOff ならイメージ名・タグ・レジストリ認証を見ます (コンテナの中に入る必要はありません)。
  4. reason が CrashLoopBackOff なら k logs <名前> --previous で直前のログから死んだ原因を探します。
  5. 終了コードが 137 なら OOMKilled かどうかを確認し、limits.memory を点検します。
  6. 原因をマニフェストで直したあと k apply、または Pod を消して作り直し、STATUS が Running に進むかを確認します。

肝は コンテナの中に exec する前に describe と logs で十分に絞り込むこと です。試験時間は限られており、ほとんどの原因は describe の一画面と直前のログで明らかになります。

わざと失敗するコンテナで CrashLoop を再現する #

自分で一度作ってみると、診断画面が手に馴染みます。次の Pod は開始直後 1 秒後に異常終了して CrashLoopBackOff を再現します。

apiVersion: v1
kind: Pod
metadata:
  name: crasher
spec:
  restartPolicy: Always   # 비정상 종료마다 계속 재시작 → CrashLoopBackOff
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "echo starting; sleep 1; echo boom >&2; exit 1"]
k apply -f crasher.yaml
k get pod crasher -w          # STATUS가 Running → CrashLoopBackOff로 가는 것을 관찰
k describe pod crasher        # lastState.terminated: reason=Error, exitCode=1
k logs crasher --previous     # "starting" 과 "boom" 이 직전 로그에 남아 있음

-w (watch) で見ると、再起動間隔がだんだん広がるバックオフを直接確認できます。原因が exit 1 であることをログで確認したら、マニフェストの command を exit 0 に直して再適用すると phase が Succeeded に整理されます。

マルチコンテナ Pod の個別コンテナを扱う #

Pod の中にコンテナが複数あるときは、ログと exec の対象に -c でコンテナを指定する必要があります。マルチコンテナパターン自体は #3 で扱いますが、診断コマンドの形だけ先に身につけておきます。

# 멀티 컨테이너 Pod에서 특정 컨테이너 로그·셸
k logs mypod -c sidecar
k exec -it mypod -c app -- sh

-c を外すと最初のコンテナをデフォルトに取り、コンテナが複数あるときはどのコンテナかの警告が出ます。試験で「sidecar コンテナのログを確認しなさい」のような指示が出たら、-c の指定が正解の一部です。

試験ポイント #

  • phase 5 種。 Pending (実行前)、Running (実行中)、Succeeded (正常完了)、Failed (異常終了)、Unknown (ノード通信途絶)
  • restartPolicy 3 種と制約。 Always (Deployment など、固定)、OnFailure・Never (Job は 2 つのうち 1 つ、Always 不可)
  • コンテナの状態 3 種。 Waiting・Running・Terminated。診断は Waiting・Terminated の reason 読み
  • reason 定番。 ImagePullBackOff・ErrImagePull (イメージ)、CrashLoopBackOff (内部プロセス)、OOMKilled (メモリ)、Completed (正常)
  • 終了コード。 0 (正常)、1 (アプリエラー)、137 (SIGKILL・OOM)、143 (SIGTERM・graceful)
  • 診断順序。 get pod -o widedescribe podlogs --previousget events。exec は最後
  • CrashLoopBackOff はエラーではなく再起動バックオフの状態。 原因は logs --previous にある
  • マルチコンテナは -c でコンテナを指定 して logs・exec

まとめ #

この記事で押さえたこと:

  • Pod の phase (マクロな状態) と コンテナの状態 (ミクロな状態) を分けて、診断の一次分岐を立てました。
  • restartPolicy の取り得る値がワークロードの種類によって制約される点 (Deployment = Always 固定、Job = OnFailure/Never) を確認しました。
  • CrashLoopBackOff・ImagePullBackOff・OOMKilled といった reason と、0・1・137・143 の 終了コード で原因を素早く絞り込む方法を身につけました。
  • describelogs --previousevents とつながる 診断コマンドの順序 を、自分で再現した Pod で確認しました。

状態を読む目ができたので、次は 1 つの Pod の中に意図的にコンテナを複数置く設計に進みます。

次へ — Multi-container パターン #

この記事でコンテナ 1 つの状態と診断を扱ったなら、次は 1 つの Pod の中に複数のコンテナを一緒に置くパターン です。

#3 Multi-container パターン: Init container、sidecar、ambassador、adapter では、本コンテナの前に順次実行される init container、本コンテナの横で補助する sidecar、そして ambassador・adapter パターンを、それぞれいつどのような形で使うのかをマニフェストと一緒に整理します。

X