起動しない Pod の原因を調べる
v1.37 時点(2026年10月26日 確認)
目次
この記事では思いどおりに動かない Pod の原因を探す作業である Pod のデバッグ の進め方を、図で説明します。kubectl get・kubectl describe・イベント・kubectl logs をどの順に見て、Pod の状態ごとにどこを確かめればよいかがわかります。kubectl debug が動いている Pod にデバッグ用のコンテナを足すとき、誰が何をするのかもわかります。後半では kind のクラスタで、イメージを取得できない Pod と、起動してすぐ終わる Pod を作り、イベントとログで原因を確かめます。さらに kubectl debug で Pod のコピーやエフェメラルコンテナを使って、コンテナの中を調べる手順を示します。
この回から第9部(運用の入り口とまとめ)に入ります。コンテナの状態、restartPolicy、CrashLoopBackOff を扱った第9回の内容を前提にしています。第9回では「再起動を繰り返しても原因は取り除かれない」ことを見ました。この回はその原因の調べ方をまとめます。まだの方は先にこちらをどうぞ。
以下の図は kubectl debug で動いている Pod にエフェメラルコンテナを足すとき、誰が何をするかを表しています。後半の手順で作る Pod ephemeral-demo を例にしています。
API リファレンスによると、既存の Pod にエフェメラルコンテナを足すには、Pod の ephemeralcontainers というサブリソースを使います。第10回で「API の専用の窓口」と呼んだものです。サブリソースは第5回で見た /status と同じく、オブジェクトに付いた別のパスです。kubectl debug もこのサブリソースを通して kube-apiserver に追加を頼むことになります。追加されたコンテナは Pod の spec.ephemeralContainers に載ります。
kubelet は kube-apiserver から受け取った Pod の spec に書かれたコンテナを、コンテナランタイムに作らせて起動します。エフェメラルコンテナも spec に載るので、それを起動するのもそのノードの kubelet だと考えられます。図の緑の線は、kubelet が同じ Pod の中にエフェメラルコンテナを起動することを表します。kubectl debug のリファレンスによると、Pod 自体は再起動されません。図の --target は「エフェメラルコンテナ」の節で説明します。
参考:Ephemeral Containers | Kubernetes、Pod | Kubernetes API Reference、kubelet | Kubernetes、Kubelet Sync Loop | Kubernetes、kubectl debug | Kubernetes
Pod のデバッグとは
公式ドキュメントのタスクの Debug Pods は、Kubernetes にデプロイしたアプリケーションが正しく動かないときに、原因を探すための手引き です。クラスタそのものの不具合を調べる手引きは別にあり、この回では扱いません。この記事ではこのページと続きの Debug Running Pods、Determine the Reason for Pod Failure に沿って、思いどおりに動かない Pod の原因を探す作業を「Pod のデバッグ」と呼びます。
この回で使う道具を、何を見るためのものかで次の表にまとめます。
| 道具 | 見るもの |
|---|---|
kubectl get pods | Pod の一覧と STATUS 列。再起動の回数 |
kubectl describe pod | コンテナの状態(State:、Last State:)と、その Pod についての最近のイベント |
kubectl get events、kubectl events | Namespace の中のイベントの一覧 |
kubectl logs | コンテナのログ。--previous で 1 つ前のコンテナのログ |
kubectl debug | Pod にデバッグ用のコンテナを足したり、Pod のコピーやノードで動く Pod を作ったりして、中を調べる |
参考:Debug Running Pods | Kubernetes、kubectl Quick Reference | Kubernetes、kubectl debug | Kubernetes
kubectl debug は、クラスタのオブジェクトについて、よくあるデバッグの作業を自動で行うコマンド です。リソースの種類を書かなければ Pod が対象になります。リファレンスによると、対象によってすることが変わり、次の 3 つがあります。
- 既存の Pod の一部を変えたコピーを作る
- 動いている Pod にエフェメラルコンテナを足す(Pod を再起動せずにデバッグの道具を加える)
- ノードのホストの namespace で動き、ノードのファイルシステムにアクセスできる Pod を作る
エフェメラルコンテナ(ephemeral container)は 既存の Pod の中で一時的に動く、特別な種類のコンテナ です。ephemeral は一般に「一時的な」という意味の英語です。公式ドキュメントによると、トラブルシューティングのような利用者が始める作業のために使います。第10回では、Pods のページが挙げるコンテナの 1 つとして名前だけ紹介しました。v1.37 時点では v1.25 から安定版(stable)の機能で、機能ゲート EphemeralContainers はすでに取り除かれています。
参考:Ephemeral Containers | Kubernetes、Feature Gates (removed) | Kubernetes
なぜ Pod のデバッグが必要なのか
第9回で見たとおり、kubelet は失敗したコンテナを restartPolicy に従って再起動しますが、再起動しても原因は取り除かれません。STATUS 列の CrashLoopBackOff や ImagePullBackOff は、コンテナが今どうなっているかを表す表示です。公式ドキュメントはその原因を探すために、ログやイベントを見るよう案内しています。手がかりはイベント、ログ、終了の情報など別々の場所に残るので、どこに何が残るかを知らないと見落としやすいと考えられます。
参考:Pod Lifecycle | Kubernetes、Pull an Image from a Private Registry | Kubernetes
コンテナの中を直接調べたいときもあります。第10回で使った kubectl exec はコンテナの中でコマンドを動かすものです。公式ドキュメントによると、kubectl exec で足りないのは、コンテナが落ちてしまっているときや、イメージにデバッグの道具が入っていないときです。たとえば distroless のイメージがそうです。distroless のイメージは攻撃される箇所やバグ・脆弱性にさらされる部分を減らした、小さなイメージを使えるようにするものです。シェル(コマンドを打って実行するプログラム。第9回の sh)も道具も入っていないので、kubectl exec だけで調べるのは難しいとされています。エフェメラルコンテナはこうした場面で対話的に調べるのに役立ちます。
参考:Ephemeral Containers | Kubernetes、Debug Running Pods | Kubernetes
Pod のデバッグの仕組み
調べる順序と、情報の出どころ
以下の図は公式ドキュメントのタスクに沿った調べる順序と、それぞれの情報がどこから来るかを表しています。
Debug Pods のページによると、Pod のデバッグの最初の一歩は Pod を見ることです。kubectl describe pods で Pod の今の状態と最近のイベントを確かめ、コンテナがすべて Running か、最近再起動していないかを見ます。その先は Pod の状態によって分かれます。ノードに割り当てられたあとの Pod には、Debug Running Pods のページの方法が使えます。そのページは kubectl describe pod、ログ、kubectl exec、エフェメラルコンテナ、Pod のコピー、ノードの上のシェルの順に方法を挙げています。
参考:Debug Pods | Kubernetes、Debug Pods | Kubernetes、Debug Running Pods | Kubernetes
情報の出どころは 2 つに分かれます。公式ドキュメントによると、kubectl describe pod の最後に出るようなイベントは etcd(第4回)に保存されています。Pod の状態も Pod のオブジェクトの status として kube-apiserver から読みます。一方、ログは第4回で見たとおり、kube-apiserver がノードの kubelet へつないで取り出します。リファレンスによると、kubectl debug の -it はコンテナが動き出すのを待って、kubectl attach と同じようにつなぎます。第4回で見た attach と同じく、kube-apiserver から kubelet に向かう接続だと考えられます。図では kube-apiserver から kubelet への取り次ぎとしてログだけを描いています。
参考:Debug Running Pods | Kubernetes、Communication between Nodes and the Control Plane | Kubernetes、kubectl debug | Kubernetes
kubectl describe pod で見るところを、Debug Running Pods のページに沿って次の表にまとめます。
| 見るところ | 表すもの |
|---|---|
| コンテナの状態 | Waiting、Running、Terminated のどれか(第9回)。状態に応じて理由や時刻などが付く |
Ready | コンテナが最後の readiness Probe(第34回)に通ったか |
Restart Count | コンテナが再起動された回数。restartPolicy が Always のコンテナで、失敗の繰り返しに気づくのに役立つ |
Events: | 最近の出来事。From は記録したコンポーネント、Reason と Message は何が起きたか |
状態ごとに見る場所
第9回で見たとおり、フェーズの Pending にはノードが決まるまでの時間と、イメージをダウンロードする時間の両方が含まれます。そのため、表の 1 列目の見分け方には spec.nodeName(割り当てたノードの名前。第5回)が入っているかどうかも書いています。公式ドキュメントが挙げる、よくある状態と見る場所を次の表にまとめます。
| 見分け方 | 何が起きているか | まず見る場所 |
|---|---|---|
Pending で、spec.nodeName が空 | ノードに割り当てられない。一般には何かの資源が足りないためで、hostPort(この回では扱わない)を使っている場合もある | describe の Events: の、スケジューラーのメッセージ |
Pending で、spec.nodeName が入っていて、コンテナが Waiting | ノードには割り当てられたが、そこで動けない。最もよくある原因はイメージの取得の失敗 | describe の State: の理由と Events: |
コンテナの Waiting の理由が ImagePullBackOff(STATUS 列にも出る) | イメージを取得できず、間隔を延ばしながら試し続けている。原因は名前の誤りや、imagePullSecret(この回では扱わない)の無いプライベートなレジストリなど | イメージの名前と、レジストリにあるか |
コンテナの Waiting の理由が CrashLoopBackOff(STATUS 列にも出る) | 起動しては失敗を繰り返している。フェーズは Running のこともある(第9回) | kubectl logs(--previous)と、describe のイベント |
STATUS 列が Init:Error、Init:CrashLoopBackOff | init コンテナ(第10回)が失敗した、または失敗を繰り返している | kubectl logs <Pod の名前> -c <init コンテナの名前> |
参考:Pod Lifecycle | Kubernetes、Debug Pods | Kubernetes、Debug Pods | Kubernetes、Images | Kubernetes、Pod Lifecycle | Kubernetes、Debug Init Containers | Kubernetes
Debug Pods のページはノードに割り当てられない Pod を「Pending のまま」、割り当てられたのに動けない Pod を「Waiting のまま」と呼んでいます。ページの言う「Waiting のまま」は、コンテナの状態が Waiting のままという意味だと読め、表の 2 行目にあたります。表の 1 行目の Pod について、同じページは describe の表示にスケジューラーからの理由のメッセージがあるはずだ(should)としています。資源が足りないときの FailedScheduling のイベントは第29回で引用しました。
参考:Debug Pods | Kubernetes、Debug Pods | Kubernetes、Resource Management for Pods and Containers | Kubernetes
イメージの取得の失敗について、Debug Pods のページは確かめることを 3 つ挙げています。イメージの名前が正しいか、イメージをレジストリに上げてあるか、手でイメージを取得できるか(たとえば手元の PC で docker pull <image> を実行する)です。
CrashLoopBackOff について、Pod Lifecycle のページは原因を調べる方法として次の 5 つを挙げています(第9回で前の 2 つを紹介しました)。
| 方法 | すること |
|---|---|
| ログを確かめる | kubectl logs <Pod の名前> でコンテナのログを見る。多くの場合、いちばん直接的な方法 |
| イベントを調べる | kubectl describe pod <Pod の名前> で Pod のイベントを見る。設定や資源の問題の手がかりになる |
| 設定を見直す | 環境変数やマウントしたボリュームを含む Pod の設定が正しく、要る外部の資源がそろっているか |
| 資源の上限を確かめる | コンテナに十分な CPU とメモリが割り当てられているか(第29回) |
| アプリケーションを調べる | コードのバグや設定の誤りがあるかもしれない。手元や開発環境でイメージを動かすことが、調べるのに役立つ |
イベントを見る
イベントは第3回で見た、クラスタのどこかで起きた出来事の報告です。kubectl describe pod はその Pod についての最近のイベントを表示します。イベントは kubectl get events で一覧にもできます。公式ドキュメントは、イベントは Namespace ごとにある ことを覚えておく必要があるとしています。default 以外の Namespace(第3回、第37回)の Pod のイベントを見るには --namespace を付け、すべての Namespace を見るには --all-namespaces を付けます。
kubectl の早見表には作られた時刻で並べ替えて一覧にする kubectl get events --sort-by=.metadata.creationTimestamp と、Warning のイベントだけを出す kubectl events --types=Warning が載っています。kubectl events はイベントの大事な情報を表にして出すコマンドで、--for で特定のリソースについてのイベントに絞れます。
参考:kubectl Quick Reference | Kubernetes、kubectl events | Kubernetes
イベントはいつまでも残るものではありません。API リファレンスによると、イベントの保存期間は限られていて、イベントは参考のための、できる範囲で記録される(best-effort)補助のデータとして扱うべき(should)とされています。kube-apiserver の --event-ttl(イベントを残す時間)は、v1.37 時点の既定では 1 時間です。既定のままなら、1 時間より前の出来事のイベントは後から探しても残っていないと考えられます。
参考:Event | Kubernetes API Reference、kube-apiserver | Kubernetes
ログと終了メッセージ
Debug Running Pods のページは、まず影響を受けたコンテナのログを kubectl logs <Pod の名前> -c <コンテナの名前> で見るよう案内しています。コンテナが以前に落ちていれば、--previous で前のコンテナが落ちたときのログを読めます。リファレンスによると、--previous は前のコンテナがあればそのログを出すもので、Pod のコンテナが 1 つだけなら -c は省けます(第10回)。第9回で見たとおり、既定では kubelet が終了したコンテナを 1 つ、ログと一緒に残すので、--previous で読めるのは 1 つ前のコンテナのログです。
参考:Debug Running Pods | Kubernetes、kubectl logs | Kubernetes、Logging Architecture | Kubernetes
ログのほかに、コンテナが終わるときに短い終了メッセージ(termination message)を残す仕組みもあります。公式ドキュメントによると、終了メッセージは、致命的な出来事の情報を、ダッシュボードのような道具から取り出しやすい場所に書くための方法です。Kubernetes はコンテナの terminationMessagePath に書かれたファイルからメッセージを読み、成功のときも失敗のときも、コンテナの状態のメッセージに入れます。読み出したメッセージは第9回で見た status.containerStatuses の lastState.terminated.message などに入ります。終了メッセージは短い最後の状態を書くためのもので、長さにも上限があります。この回で使う決まりを次の表にまとめます。
| 項目 | 決まり |
|---|---|
terminationMessagePath | メッセージを書くファイルのパスで、既定は /dev/termination-log。Pod を起動したあとでは変えられない |
terminationMessagePolicy | 既定の File はファイルからだけ読む。FallbackToLogsOnError にすると、ファイルが空でエラーで終わったとき、ログの最後の部分を使う |
参考:Determine the Reason for Pod Failure | Kubernetes、Determine the Reason for Pod Failure | Kubernetes、Pod | Kubernetes API Reference
公式ドキュメントは、終了メッセージに書く情報は、たいていはふつうのログにも書いておくべき(should)だとしています。後半の手順ではログと終了メッセージの両方に同じ 1 行を書くコンテナを使います。
kubectl debug の 3 つの使い方
ログやイベントだけでは原因がわからないとき、kubectl debug でコンテナの中を調べます。3 つの使い方を次の表にまとめます。
| 使い方 | コマンドの形 | 作られるもの | 公式ドキュメントが挙げる場面 |
|---|---|---|---|
| エフェメラルコンテナ | kubectl debug <Pod> -it --image=<イメージ> | 動いている Pod の中の、デバッグ用のコンテナ | コンテナが落ちている、イメージにデバッグの道具が無い |
| Pod のコピー | kubectl debug <Pod> -it --copy-to=<名前> ... | 設定の一部を変えた、別の Pod | 起動してすぐ落ちる、道具やシェルの無いイメージを差し替えたい |
| ノード | kubectl debug node/<ノード> -it --image=<イメージ> | ノードのホストの namespace で動く Pod | ほかの方法がどれもうまくいかず、Pod が動くノードで調べたい |
参考:kubectl debug | Kubernetes、Debug Running Pods | Kubernetes、Debug Running Pods | Kubernetes、Debug Running Pods | Kubernetes
以下の図は 3 つの使い方で、デバッグ用のコンテナがどこで動くかを表しています。
エフェメラルコンテナだけが調べたい Pod そのものの中で動きます。コピーとノードのデバッグは新しい Pod を作ります。kubectl debug のリファレンスでは、元の Pod を削除するのは --replace(後で説明します)を付けたときだけなので、付けなければコピーを作っても元の Pod はそのまま残ると読めます。
エフェメラルコンテナ
公式ドキュメントによると、エフェメラルコンテナにはふつうのコンテナと違う点がいくつもあります。違いを次の表にまとめます。
| 項目 | 決まり |
|---|---|
| 保証 | 資源や実行の保証が無く、自動でも再起動されないので、アプリケーションを組み立てるのには向かない。Pod が割り当てを超える原因になると、kubelet が Pod を退避させることがある(may) |
| 再起動 | 自動では再起動されない。終了したときも、Pod が再起動されたときも同じ |
| 書けないフィールド | ポートを持てないので、ports、livenessProbe、readinessProbe は書けない。resources も書けない |
| 足し方 | pod.spec に直接書かず、ephemeralcontainers のサブリソースを通して作る。そのため kubectl edit では足せない |
| 足したあと | 変えることも、取り除くこともできない |
| static Pod | static Pod(第3回)には足せない |
参考:Ephemeral Containers | Kubernetes、Pod | Kubernetes API Reference
エフェメラルコンテナで調べるときは、プロセスの namespace(PID namespace)を共有すると役に立ちます。一般に、プロセスとは動いているプログラムのことです。PID namespace は第8回で名前を紹介した Linux の namespace の 1 つで、どのプロセスが見えるかを分けます。Debug Running Pods のページによると、kubectl debug の --target はほかのコンテナのプロセスの namespace を対象にします。共有すると、ほかのコンテナのプロセスが見えるようになります。
参考:Ephemeral Containers | Kubernetes、Debug Running Pods | Kubernetes、Share Process Namespace between Containers in a Pod | Kubernetes
--target はコンテナランタイムが対応していなければなりません(must)。対応していないときは、エフェメラルコンテナが起動しないかもしれません(may)。あるいは隔てられたプロセスの namespace で起動し、ps にほかのコンテナのプロセスが出ないかもしれません(may)。
Pod のコピー
Debug Running Pods のページによると、Pod の設定のせいで調べにくいことがあります。たとえば、イメージにシェルが無いときや、アプリケーションが起動時に落ちるときです。そうしたときは kubectl debug の --copy-to で、設定を変えた Pod のコピーを作ります。コピーで変えられる主なものを次の表にまとめます。
| 変えるもの | 指定 | 公式ドキュメントの説明 |
|---|---|---|
| コンテナを足す | --image=<イメージ> | 動いてはいるが思いどおりでないときに、道具の入ったコンテナを足す |
| コマンド | --container=<名前> -- <コマンド> | すぐ終わる・落ちるコンテナで、シェルを動かす。コマンドを変えるには --container で名前を指定しなければならない(must) |
| イメージ | --set-image=<コンテナ>=<イメージ> | 本番のイメージを、デバッグ用のビルドや道具の入ったイメージに差し替える。*=ubuntu ですべてのコンテナを変える |
参考:Debug Running Pods | Kubernetes、Debug Running Pods | Kubernetes、Debug Running Pods | Kubernetes
--container を書かずにコマンドを渡すと、kubectl debug はそのコマンドを動かす新しいコンテナを足します。コンテナの名前を書かなければ、kubectl debug が名前を自動で付けます。リファレンスによると、--copy-to と一緒に使うフラグには次のものもあります。
| フラグ | 説明 |
|---|---|
--share-processes | コピーでプロセスの namespace の共有を有効にする。既定は true |
--same-node | コピーを元の Pod と同じノードに置く |
--replace | 元の Pod を削除する |
--keep-labels、--keep-liveness など | true なら元の Pod のラベルや Probe を残す。--keep-init-containers(init コンテナを動かす)だけは既定で true |
参考:kubectl debug | Kubernetes、Debug Running Pods | Kubernetes
ラベルを残す --keep-labels と、Probe を残す --keep-liveness などは「true なら残す」と説明されています。そのため、付けなければ、コピーには元の Pod のラベルや Probe が引き継がれないと読めます。
ノードのデバッグ
ノードのデバッグはこの回の手順では試しません。-it を付けて kubectl debug node/<ノードの名前> を実行すると、調べたいノードにデバッグ用の Pod を置き、そのコンテナのシェルにつなぎます。シェルが動くのはデバッグ用の Pod のコンテナの中ですが、そのコンテナはホストの IPC(一般に、プロセスどうしのやり取り)・ネットワーク・PID の namespace で動きます。ノードのルートのファイルシステムは Pod の /host にマウントされます。ただし、この Pod は特権(privileged。一般に、コンテナにノードへの強い権限を与える設定)ではないので、一部のプロセスの情報が読めないことがあります(may)。
参考:Debug Running Pods | Kubernetes、Debugging Kubernetes Nodes With Kubectl | Kubernetes
デバッグ用の Pod の名前はノードの名前から自動で付き、使い終わったらその Pod を削除するよう案内されています。特権の Pod が要るときは、手で作るか、次に説明する --profile=sysadmin を使います。ノードのデバッグを使うには、Pod を作り、その Pod を好きなノードに割り当てる権限が要ります。権限の仕組みは第38回で見ました。
参考:Debug Running Pods | Kubernetes、Debugging Kubernetes Nodes With Kubectl | Kubernetes
プロファイル
kubectl debug の 3 つの使い方のどれにも --profile でプロファイルを当てられます。公式ドキュメントによると、プロファイルを当てると securityContext などの決まった設定が入り、場面に合わせられます。securityContext は第39回で見た、コンテナに与える権限などの設定です。
選べるプロファイルはページによって違います。Debug Running Pods のページは legacy、general、baseline、restricted、netadmin、sysadmin の 6 つを挙げています。v1.37 時点の kubectl のリファレンスは、選択肢に legacy を含めず、残りの 5 つを挙げています。この回で使う general と、システムの管理者(root)の権限を含む sysadmin を、タスクのページの説明に沿って次の表にまとめます。
| プロファイル | 公式ドキュメントの説明 |
|---|---|
general | デバッグの作業ごとに妥当な、一般的な設定 |
sysadmin | システムの管理者(root)の権限を含む設定。公式ドキュメントの例では、足したエフェメラルコンテナの securityContext が {"privileged":true} になる |
参考:Debug Running Pods | Kubernetes、Debug Running Pods | Kubernetes、kubectl debug | Kubernetes
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。存在しないイメージを指定した Pod と、起動してすぐ終わる Pod を作り、状態、イベント、ログ、終了メッセージで原因を確かめます。そのあと、kubectl debug で落ちる Pod のコピーにシェルを動かし、シェルの無い Pod にエフェメラルコンテナを足して中を調べます。ノードのデバッグはこの回では試しません。
この節で引用する出力の例は、公式ドキュメントに載っている、ほかのクラスタで取ったものです。名前や時刻などの値は手元とは違います。各段の最後に挙げる成功の目安は公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。
コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。この回では第9回と同じく、シングルノードのクラスタを使います。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。この回では端末を 2 つ使います。これまでの回と同じく、1 つ目の端末は変化の監視(この回では Pod)に取っておきます。クラスタの準備を含めて、操作は 2 つ目の端末で行います。kubectl debug で開くシェルも 2 つ目の端末で使います。最初に cd でマニフェストを保存するディレクトリに移動しておきます。
シングルノードのクラスタを用意する
2 つ目の端末で、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の multi-node が表示されても、この回では使いません。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードと、default の Namespace の Pod を一覧します。
$ kubectl get nodes --context kind-kind
$ kubectl get pods --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。作り直したばかりのクラスタなら、2 つ目のコマンドでは見つからないことを知らせる 1 行が出ると考えられます。この回で作る image-demo、myapp、myapp-debug、ephemeral-demo という名前の Pod が残っていたら、kubectl delete pod <名前> --context kind-kind で消してから進みます。
Pod の監視を始める
1 つ目の端末で、default の Namespace の Pod を監視します。この回では Pod を順に 3 つ作り、kubectl debug もコピーの Pod を作ります。どれもまだ無いので、名前を指定せずに Pod 全体を監視します。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch --context kind-kind
Pod がまだ無ければ、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
イメージを取得できない Pod を調べる
以下は存在しないタグのイメージを指定した Pod のマニフェストです。image-demo.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: image-demo
spec:
containers:
- name: app
image: busybox:no-such-tag
command: ['sleep', '3600']busybox:no-such-tag は、第9回で使った busybox のイメージに、わざと無いタグ(no-such-tag)を付けたものです。busybox の公式のイメージにこのタグは無いと考えられるので、イメージの取得は失敗するはずです。タグが latest ではなく、imagePullPolicy も書いていないので、imagePullPolicy は IfNotPresent になります(第5回)。ノードにこのイメージは無いので、kubelet は取得を試みます。
2 つ目の端末で Pod を作ります。
$ kubectl apply -f image-demo.yaml --context kind-kind
pod/image-demo が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には image-demo の行が書き足されていき、しばらくすると STATUS 列が ImagePullBackOff になると考えられます。途中で、取得の失敗を知らせる別の値が STATUS 列に出ることもあると考えられます。その場合も、最後に ImagePullBackOff になれば想定どおりです。公式ドキュメントによると、ImagePullBackOff の BackOff の部分は、待ち時間を延ばしながらイメージの取得を試し続けることを表し、待ち時間は 300 秒(5 分)で頭打ちになります。
STATUS 列が ImagePullBackOff になったら、ノードに割り当てられたかと、フェーズ、コンテナの Waiting の理由を取り出します。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。
$ kubectl get pod image-demo -o jsonpath='{.spec.nodeName}{"\n"}{.status.phase}{"\n"}{.status.containerStatuses[0].state.waiting.reason}{"\n"}' --context kind-kind
参考:JSONPath Support | Kubernetes、Pod | Kubernetes API Reference
1 行目にノードの名前(kubectl get nodes で表示された名前)が、2 行目に Pending が出るはずです。フェーズの Pending にはイメージをダウンロードする時間も含まれるので、ノードには割り当てられたのに Pending のまま、という状態になるはずです。「状態ごとに見る場所」の表の 2 行目にあたります。3 行目はコンテナが Waiting にある理由で、ImagePullBackOff などが出ると考えられます。
参考:Pod Lifecycle | Kubernetes、Debug Pods | Kubernetes、Pod Lifecycle | Kubernetes
続けて、Pod を詳しく表示します。
$ kubectl describe pod image-demo --context kind-kind
Containers: の下の app: の部分では、State: が Waiting で、Reason: に ImagePullBackOff などが出ると考えられます。末尾の Events: には、default-scheduler が Pod をノードに割り当てた行と、kubelet がイメージを取得しようとした行が並ぶはずです(第8回で引用した例の Scheduled と Pulling)。そのあとに、kubelet がイメージを取得できなかったことを知らせる Warning の行が続くと考えられます。メッセージにはイメージの名前が入り、どのイメージで失敗したかがわかるはずです。
参考:Debug Running Pods | Kubernetes、Event | Kubernetes API Reference
同じイベントを、イベントの一覧からも見ます。1 つ目は image-demo についてのイベントだけを、2 つ目は Namespace のすべてのイベントを作られた時刻で並べて表示します。
$ kubectl events --for pod/image-demo --context kind-kind
$ kubectl get events --sort-by=.metadata.creationTimestamp --context kind-kind
参考:kubectl events | Kubernetes、kubectl Quick Reference | Kubernetes
どちらも、describe の Events: と同じ出来事が 1 行ずつ並ぶはずです。2 つ目の一覧にはノードなど Pod 以外のオブジェクトについてのイベントも混ざると考えられます。この Pod の原因はイメージの名前(タグ)の誤りでした。Debug Pods のページの 3 つの確認のうち、「イメージの名前が正しいか」にあたります。
この段では次の 3 点が合っていれば成功です。
- 1 つ目の端末で、
image-demoのSTATUS列がImagePullBackOffになる spec.nodeNameにノードの名前が入り、フェーズはPendingのままEvents:に、イメージを取得できなかったことを知らせるWarningの行がある
起動してすぐ終わる Pod を調べる
以下は設定ファイルが無いと 1 行のメッセージを書いて終了コード 1 で終わるコンテナの Pod のマニフェストです。myapp.yaml という名前で保存します。Pod とコンテナの名前は公式ドキュメントの Pod のコピーの例に合わせて myapp にしています。
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp
image: busybox:1.28
command: ['sh', '-c', 'echo "myapp: starting"; if [ ! -f /config/app.conf ]; then echo "myapp: /config/app.conf not found" | tee /dev/termination-log; exit 1; fi; sleep 3600']参考:Pod | Kubernetes API Reference、Determine the Reason for Pod Failure | Kubernetes
restartPolicy を書いていないので、既定の Always になります(第9回)。command でシェルに渡している中身を次の表にまとめます。表の [ ! -f ... ]、|、tee の説明は、一般的な sh とコマンドの使い方を書いたものです。
| 部分 | 意味 |
|---|---|
echo "myapp: starting" | ログに 1 行書く |
if [ ! -f /config/app.conf ]; then ... fi | /config/app.conf というファイルが無ければ、then から fi までを実行する |
echo "..." | tee /dev/termination-log | メッセージを、ログ(標準出力)と終了メッセージのファイルの両方に書く。tee は受け取った文字を画面とファイルの両方に書き出す |
exit 1 | 終了コード 1 で終わる |
sleep 3600 | ファイルがあれば 1 時間待つ(アプリケーションが動き続ける代わり) |
このイメージに /config/app.conf は無いと考えられるので、コンテナは起動するたびにすぐ終わるはずです。このマニフェストは Pod の設定に問題がある場面を作るためのものです。
2 つ目の端末で Pod を作ります。
$ kubectl apply -f myapp.yaml --context kind-kind
1 つ目の端末には myapp の行が書き足されていきます。STATUS 列は、コンテナが失敗して終わったときの理由を表す Error のような値と、CrashLoopBackOff を行き来し、RESTARTS 列が増えていくと考えられます。
v1.37 時点の公式ドキュメントでは、再起動までの待ち時間は 10 秒、20 秒、40 秒……と延びます(第9回)。コンテナがすぐ終わり、待ち時間がこの値どおりだと仮定すると、1 分ほどで RESTARTS 列が 2 以上になり、CrashLoopBackOff が表示されるはずです。第9回で見たとおり、最初の再起動の前に待つかどうかは公式ドキュメントからは読み取れません。ただ、すぐ再起動する場合も 10 秒待つ場合も、1 分たつまでに 2 回は再起動される計算です。
RESTARTS 列が 1 以上になったら、Pod を詳しく表示します。
$ kubectl describe pod myapp --context kind-kind
公式ドキュメントの、false コマンドで落ちる Pod の例では、Containers: の部分は次のとおりです。
手元の表示は例と次の点が違うはずです。
| 違い | 手元の表示 |
|---|---|
| イメージ | Image: は busybox:1.28 |
| コマンド | Args: の false ではなく、Command: に sh、-c と、シェルに渡した文字列が出ると考えられる |
| 状態 | 再起動を待っているときは例と同じ Waiting、CrashLoopBackOff。コンテナが動いた一瞬に表示すると違うことがある |
Last State: の Exit Code: は例と同じく 1 になるはずです。第9回で見たとおり、Last State: は 1 つ前のコンテナの終わり方を表すと考えられます。Events: には、kubelet がコンテナを作り直して起動するたびのイベントと、再起動を待っていることを知らせる Warning の行が並ぶと考えられます。
次に、1 つ前のコンテナのログを表示します。Debug Running Pods のページと同じく、-c でコンテナの名前を指定します。
$ kubectl logs myapp -c myapp --previous --context kind-kind
参考:Debug Running Pods | Kubernetes、kubectl logs | Kubernetes
マニフェストの echo で書いた myapp: starting と myapp: /config/app.conf not found の 2 行が表示されるはずです。ログから、設定ファイルが無いことが原因だとわかるはずです。
最後に、1 つ前のコンテナの終了コードと終了メッセージを、これまでの回と同じく -o jsonpath で取り出します。
$ kubectl get pod myapp -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}{.status.containerStatuses[0].lastState.terminated.message}{"\n"}' --context kind-kind
参考:Determine the Reason for Pod Failure | Kubernetes、JSONPath Support | Kubernetes
1 行目に 1、2 行目に myapp: /config/app.conf not found が出るはずです。tee で /dev/termination-log に書いたメッセージを、Kubernetes が終了メッセージとして lastState.terminated.message に入れたからです。メッセージの末尾の改行のため、そのあとに空の行が続くこともあると考えられます。
この段では次の 3 点が合っていれば成功です。
- 1 つ目の端末で、
myappのSTATUS列にCrashLoopBackOffが出て、RESTARTS列が増える kubectl logs --previousに、設定ファイルが無いことを知らせる行がある- 終了コードが
1で、終了メッセージに同じ行が入っている
Pod のコピーでシェルを動かす
myapp のコンテナはすぐ終わるので、kubectl exec で中に入るのは難しいと考えられます。そこで、コマンドをシェル(sh)に変えた Pod のコピーを作り、同じイメージ、同じ設定の中を調べます。2 つ目の端末で次のコマンドを実行します。
$ kubectl debug myapp -it --copy-to=myapp-debug --container=myapp --profile=general --context kind-kind -- sh
参考:Debug Running Pods | Kubernetes、kubectl debug | Kubernetes
コマンドの各部分の意味を次の表にまとめます。
| 部分 | 意味 |
|---|---|
-it | -i は標準入力を開いたままにし、-t(第35回の --tty の短い形)は端末を割り当てる。-i を付けると、kubectl debug は既定で新しいコンテナにつなぐ |
--copy-to=myapp-debug | myapp-debug という名前でコピーを作る |
--container=myapp | コマンドを変えるコンテナ。コマンドを変えるには名前の指定が要る |
--profile=general | プロファイル。「プロファイル」の補足のとおり、既定に頼らないために書く |
-- sh | コピーの myapp のコンテナで動かすコマンド |
公式ドキュメントは --profile を付けずに実行していて、例の出力は次のとおりです。
/ # はシェルが入力を待っている印(プロンプト)です。イメージはもうノードにあるので、あまり待たずに表示されると考えられます。表示されなければ、案内のとおり Enter キーを押します。
ここから 2 つ目の端末で打つコマンドは、手元ではなくコピーの Pod myapp-debug の myapp コンテナの中で動きます。コードブロックの行頭の / # は入力しません。exit でシェルを抜けると、手元のシェルのプロンプト($)に戻ります。
1 つ目の端末には myapp-debug の行が現れ、STATUS 列が Running になると考えられます。元の myapp の行は、これまでどおり失敗と再起動を繰り返すはずです。
コピーのシェルで、設定ファイルを置くはずのディレクトリを表示します。
/ # ls /config
/config が見つからないことを知らせる 1 行が出ると考えられます。ディレクトリごと無いので、アプリケーションがファイルを読めないとわかるはずです。直すには、たとえば第17回の ConfigMap をボリュームとして /config にマウントする方法が考えられます。
exit を入力してシェルを抜けます。
/ # exit
シェルを抜けたあとのコピーの表示は、コピーに引き継がれた restartPolicy などによって変わると考えられます。どの表示になっても、このあと消すので気にしなくてかまいません。調べ終わったので、2 つ目の端末でコピーを消します。
$ kubectl delete pod myapp-debug --context kind-kind
参考:Debug Running Pods | Kubernetes、kubectl delete | Kubernetes
公式ドキュメントも使い終わったデバッグ用の Pod を消すよう案内しています。Pod には終了までの猶予の時間(既定では 30 秒)があるので、コマンドが 30 秒ほど戻ってこないことがありますが、そのまま待ちます。
この段では次の 3 点が合っていれば成功です。
- 2 つ目の端末にプロンプト(
/ #)が出て、コピーの中でコマンドを打てる - 1 つ目の端末に
myapp-debugの行が現れ、元のmyappは失敗を繰り返したまま ls /configで、/configが無いことがわかる
シェルの無いコンテナにエフェメラルコンテナを足す
公式ドキュメントの例に沿って、シェルもデバッグの道具も入っていない pause のイメージの Pod を作ります。一般に、pause のイメージは起動すると何もせずに待ち続けるだけのプログラムが入ったものです。公式ドキュメントによると、例が pause のイメージを使うのは、デバッグの道具が入っていないからです。エフェメラルコンテナを足すやり方そのものは、pause に限らずどのイメージの Pod にも使えます。
以下は公式ドキュメントの kubectl run ephemeral-demo --image=registry.k8s.io/pause:3.1 --restart=Never をマニフェストで書いたものです。ephemeral-demo.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: ephemeral-demo
spec:
restartPolicy: Never
containers:
- name: ephemeral-demo
image: registry.k8s.io/pause:3.1参考:Debug Running Pods | Kubernetes、Pod | Kubernetes API Reference
公式の例の --target=ephemeral-demo はコンテナの名前を指すので、コンテナの名前も ephemeral-demo にしています。2 つ目の端末で Pod を作ります。
$ kubectl apply -f ephemeral-demo.yaml --context kind-kind
1 つ目の端末で ephemeral-demo の STATUS 列が Running になるまで待ちます。初めて使うイメージなので、取得に少し時間がかかることがあります。取得しているあいだは、STATUS 列に ContainerCreating のような値が出ると考えられます(第9回)。
Running になったら、kubectl exec でシェルを動かしてみます。
$ kubectl exec -it ephemeral-demo --context kind-kind -- sh
公式ドキュメントの例では、出力は次のとおりで、イメージにシェルが無いためにエラーになります。
例は別のコンテナランタイムのクラスタのものと考えられ、kind(containerd。第2回)ではメッセージの文面が違うと考えられます。sh が見つからないことを知らせるエラーになれば、例と同じです。
次に、busybox のエフェメラルコンテナを足し、そのシェルにつなぎます。--target で、ephemeral-demo のコンテナのプロセスの namespace を対象にします。
$ kubectl debug -it ephemeral-demo --image=busybox:1.28 --target=ephemeral-demo --profile=general --context kind-kind
公式ドキュメントは --profile を付けずに実行していて、例の出力は次のとおりです。
コンテナの名前は自動で付くので、debugger- に続く部分は手元では違うはずです。公式ドキュメントによると、--target が要るのは、例の Pod(kubectl run で作ったもの)ではプロセスの namespace の共有が有効になっていないからです。この回のマニフェストにも shareProcessNamespace(Pod のコンテナどうしでプロセスの namespace を共有するかを決める、Pod の spec のフィールド)は書いていません。API リファレンスによると、shareProcessNamespace は書かなければ false です。
参考:Debug Running Pods | Kubernetes、Pod | Kubernetes API Reference
今度のシェルは Pod ephemeral-demo に足したエフェメラルコンテナ debugger-… の中で動きます。このシェルでプロセスを一覧します。一般に、ps は動いているプロセスを一覧するコマンドです。
/ # ps
--target が効いていれば、sh や ps のほかに、pause のコンテナのプロセス(/pause)も並ぶと考えられます。並ばなければ、「エフェメラルコンテナ」の節で見たとおり、コンテナランタイムが --target に対応していない可能性があります。
exit を入力してシェルを抜けます。
/ # exit
2 つ目の端末で、Pod を詳しく表示します。
$ kubectl describe pod ephemeral-demo --context kind-kind
公式ドキュメントの例では、エフェメラルコンテナの部分は次のとおりです。
手元の表示は例と次の点が違うはずです。
| 違い | 手元の表示 |
|---|---|
Container ID:、Image ID: | docker:// などではなく、kind のノードのコンテナランタイム(containerd)に合った書き方になると考えられる |
Image: | busybox:1.28 |
State: | 例はシェルにつないでいるあいだのもの。シェルを抜けたあとなので、Terminated になると考えられる |
エフェメラルコンテナは終了しても再起動されないので、Restart Count: は 0 のままのはずです。エフェメラルコンテナが Pod の spec に載ったことも確かめます。
$ kubectl get pod ephemeral-demo -o jsonpath='{.spec.ephemeralContainers[0].name}{"\n"}{.spec.ephemeralContainers[0].targetContainerName}{"\n"}' --context kind-kind
参考:Debug Running Pods | Kubernetes、Pod | Kubernetes API Reference
1 行目に debugger- で始まるエフェメラルコンテナの名前が出るはずです。API リファレンスによると、targetContainerName は指定したコンテナの namespace でエフェメラルコンテナを動かすフィールドで、ランタイムが対応していなければ結果は決まっていない(undefined)とされています。2 行目には、--target で指定した ephemeral-demo が出ると考えられます。マニフェストには書いていない spec.ephemeralContainers が、kubectl debug によって足されているはずです。1 つ目の端末では、ephemeral-demo の RESTARTS 列は 0 のままのはずです。Pod を再起動せずにコンテナを足せたことになります。
この段では次の 3 点が合っていれば成功です。
kubectl execでshが見つからないエラーになるkubectl debugでエフェメラルコンテナのシェルにつながり、psに pause のプロセスが並ぶspec.ephemeralContainersにエフェメラルコンテナの名前とtargetContainerNameが入り、ephemeral-demoのRESTARTS列は0のまま
片付け
1 つ目の端末で Ctrl+C を押して、Pod の監視を止めます。
2 つ目の端末で、マニフェストを指定して、作った 3 つの Pod を削除します。myapp-debug は前の段で消しましたが、残っていても消えるように名前でも指定します。
$ kubectl delete -f image-demo.yaml -f myapp.yaml -f ephemeral-demo.yaml --context kind-kind
$ kubectl delete pod myapp-debug --ignore-not-found --context kind-kind
エフェメラルコンテナは Pod から取り除けないので、ephemeral-demo と一緒に消えます。少し待ってから、Pod が残っていないかを確かめます。
$ kubectl get pods --context kind-kind
Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。見つからないことを知らせる 1 行が出れば、削除できています。
最後に、クラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。
$ kind delete cluster
$ kind get clusters
kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
Pod のデバッグでつまずきやすい点と、バージョンで変わる点を次の表にまとめます。
| 項目 | 内容 |
|---|---|
原因は STATUS 列以外の場所で探す | 公式ドキュメントは、describe の状態とイベント、ログを見るよう案内している。終了メッセージも手がかりになる |
| イベントは Namespace ごとで、長くは残らない | default 以外は --namespace を付ける。v1.37 時点の kube-apiserver の既定では、残す時間は 1 時間 |
--previous で読めるのは 1 つ前だけ | 既定では kubelet が残す終了したコンテナは 1 つ |
| 終了メッセージのパスは後から変えられない | terminationMessagePath は Pod を起動したあとでは変えられない |
| エフェメラルコンテナは取り除けない | 足したあとは変えることも取り除くこともできない |
--target はランタイムの対応が要る | 対応していないと、起動しないか、ほかのコンテナのプロセスが見えないかもしれない |
| コピーは別の Pod | 元の Pod はそのまま残る(--replace を除く)。既定ではラベルや Probe が引き継がれないと読める |
--profile は明示して使う | 既定はタスクのページでは legacy、v1.37 の kubectl のリファレンスでは general で、リファレンスの選択肢に legacy は無い |
| ノードのデバッグ用の Pod は特権ではない | 特権が要るときは --profile=sysadmin か、手で Pod を作る。使い終わったら削除する |
参考:Debug Running Pods | Kubernetes、kube-apiserver | Kubernetes、Logging Architecture | Kubernetes、Determine the Reason for Pod Failure | Kubernetes、Ephemeral Containers | Kubernetes、kubectl debug | Kubernetes、Debugging Kubernetes Nodes With Kubectl | Kubernetes
関連記事
次の回ではリソースに付けるラベルとアノテーションの違いと、ラベルで絞り込んでまとめて操作する方法を扱います。
ラベルとアノテーションの違いリソースに付けるキーと値の組であるラベルとアノテーションの違いを図で説明します。選ぶためのラベルと選ぶためではないアノテーションの区別、書き方の決まり、推奨のラベル、複数の次元で分ける設計を押さえ、kind のクラスタでラベルで絞り込んでまとめて操作する手順を紹介します。コンテナの状態、restartPolicy、CrashLoopBackOff と再起動までの待ち時間については、以下の記事で解説しています。
OOMKilled や FailedScheduling のように、requests と limits がかかわる失敗については、以下の記事で解説しています。