Kubernetes Pod とコンテナのトラブルシューティング

KubernetesBeginner
オンラインで実践に進む

はじめに

Pending や CrashLoopBackOff などの Pod の状態は症状であって、根本原因そのものではありません。効果的なトラブルシューティングでは、オブジェクト設定、コンディション、イベント、コンテナごとの状態、ログ、実行中の環境の確認という順序で、一貫して証拠を集めます。

この実験では、あらかじめ用意された 2 つの障害を診断して修正し、その後、正常なマルチコンテナ Pod を調査します。目的は、個別の修正方法を暗記することではなく、再利用可能なトラブルシューティング手法を身につけることです。

各インシデントでは同じ順序で進めます。まず全体を広く確認し、対象を 1 つのオブジェクトに絞り、その現在の状態とイベントを読み取り、コンテナ固有の証拠を調べてから、管理元のコントローラーを修正します。これにより、無作為な変更を避け、なぜ修正が有効だったのかを説明できるようになります。

Pod の状態から調査を始める

このステップでは、正常なワークロードと調査が必要なワークロードを見分けます。まず、名前空間全体の状態を確認します。-o wide を指定すると、通常の一覧に Pod IP や割り当てられたノードなどの有用な列が追加されます。

cd /home/labex/project/pod-troubleshooting
kubectl get deployments,pods -n practice-debug -o wide

正常な Pod は 2/2 Running と表示されるはずです。Deployment の Pod の 1 つではイメージのプルに関する問題が表示され、worker は繰り返し再起動しているはずです。

テスト環境では、通常次のような結果になります(Pod のサフィックス、経過時間、再起動回数は異なります)。

NAME                                 READY   STATUS             RESTARTS   AGE
pod/broken-image-58758b4d69-c7j7g    0/1     ImagePullBackOff   0          33s
pod/crashy-worker-5557cfb974-xwpst   0/1     Error              2          33s
pod/web-with-sidecar                 2/2     Running            0          33s

worker の状態が現在 CrashLoopBackOff ではなく Error になっていても問題ありません。worker は短時間実行された後に終了し、Kubernetes が再度起動するまで待機するため、表示状態は監視中に変化します。RESTARTS の値が増えていることのほうが、このコンテナが繰り返し失敗していることを示す重要な手がかりです。

Pod 全体の YAML を確認せずに、コンテナ単位の待機理由を表示します。

kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[*].ready,WAITING_REASON:.status.containerStatuses[*].state.waiting.reason,RESTARTS:.status.containerStatuses[*].restartCount'

[*] は「リスト内の各要素」を意味するため、同じ表はマルチコンテナ Pod にも使用できます。この最初の確認結果から、次に実行すべき診断コマンドを判断します。

たとえば、WAITING_REASON=ImagePullBackOff であれば、イメージ設定またはレジストリへのアクセスに問題がある可能性が高いと判断できます。一方、worker は直近の試行が Terminated になっている間、待機理由を表示しないことがあります。そのため、この表は出発点であって、完全な診断結果ではありません。

この時点では、まだ何も修正しないでください。イメージの失敗と worker の再起動は異なる症状であり、それぞれ異なる証拠が必要です。適切な最初のコマンドを使えば、原因を決めつけることなく調査範囲を絞り込めます。

イメージのプル失敗を診断する

このステップでは、状態からイベントへとたどりながらイメージの失敗を調査します。生成された Pod 名を変数に保存し、後続のコマンドでも同じオブジェクトを対象にします。

IMAGE_POD=$(kubectl get pods -n practice-debug -l app=broken-image -o jsonpath='{.items[0].metadata.name}')
echo "$IMAGE_POD"
kubectl describe pod "$IMAGE_POD" -n practice-debug

下から上に向かって読みます。Events セクションには、Kubernetes が nginx:not-a-real-tag をプルできなかったことが表示されるはずです。設定されているイメージを直接確認します。

kubectl get pod "$IMAGE_POD" -n practice-debug -o jsonpath='{.spec.containers[0].image}{"\n"}'
kubectl events -n practice-debug --for "pod/${IMAGE_POD}"

イベントは失敗した操作を説明し、JSONPath による確認は、どのイメージが要求された結果として問題が発生したのかを明確にします。両方を使うことで、一時的なレジストリ障害を、恒久的に無効なタグの問題と取り違えずに済みます。

テスト環境では、決定的な行は次のような内容でした。

Image:          nginx:not-a-real-tag
State:          Waiting
  Reason:       ErrImagePull
Failed to pull image "nginx:not-a-real-tag": ... manifest unknown
Error: ImagePullBackOff

ErrImagePull は、イメージのプル試行が失敗したことを記録します。ImagePullBackOff は、Kubernetes が同じ操作を再試行する前に待機時間を置いている状態です。manifest unknown というメッセージと無効なタグによって、症状と原因が結び付きます。

生成された Pod を直接編集するのではなく、Deployment を修正します。kubectl set image は Pod テンプレートを変更するため、Deployment コントローラーが失敗した Pod を置き換えます。

kubectl set image deployment/broken-image web=nginx:1.27-alpine -n practice-debug
kubectl rollout status deployment/broken-image -n practice-debug --timeout=60s

コマンドが正常に完了すると、次のように表示されます。

deployment.apps/broken-image image updated
deployment "broken-image" successfully rolled out

古い Pod は使い捨ての実体ですが、Deployment テンプレートは永続的な望ましい状態です。ロールアウト後は新しい Pod 名が表示され、Deployment には利用可能なレプリカが 1 つあることが示されるはずです。

現在のログと以前のログを読む

このステップでは、起動後に終了するコンテナを診断します。対象の Pod を見つけ、再起動に関する情報を確認します。

WORKER_POD=$(kubectl get pods -n practice-debug -l app=crashy-worker -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$WORKER_POD" -n practice-debug
kubectl describe pod "$WORKER_POD" -n practice-debug

再起動を繰り返すコンテナに対して通常の kubectl logs を実行すると、現在の試行のログが表示されます。--previous を指定すると、コンテナランタイムから、その直前に終了した試行のログを取得します。

kubectl logs "$WORKER_POD" -n practice-debug -c worker
kubectl logs "$WORKER_POD" -n practice-debug -c worker --previous || \
  echo "The previous container log is no longer available; continue with the current log and describe output."

現在または以前のログには、アプリケーション自身が出力した次のメッセージが含まれているはずです。

configuration missing

新しい試行が始まる前に失敗した試行を確認できない場合、--previous は特に役立ちます。ただし、これは履歴情報であり、必ず保持されるとは限りません。ローカルのランタイムが、対象コンテナの記録をすでに削除している可能性があるためです。unable to retrieve container logs と表示された場合は、フォールバックメッセージが出力されます。利用できる現在のログと describe の結果を組み合わせてください。describe に表示される Last State、Exit Code、Restart Count によって、プロセスが繰り返し終了していることを確認できます。

-c worker オプションは、対象コンテナを明示的に指定します。失敗しているコマンドを長時間実行するループに置き換え、コントローラーを修正します。

kubectl patch deployment crashy-worker -n practice-debug --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","echo worker-ready; while true; do sleep 30; done"]}]'
kubectl rollout status deployment/crashy-worker -n practice-debug --timeout=60s

長いパッチに見えても、内側から順に読めば理解できます。path は最初のコンテナの command を選択し、op: replace は変更方法が置換であることを示し、value は置き換えるコマンド配列を指定します。変更するのは 1 つのフィールドだけで、Deployment の他の部分はそのまま維持されます。

この JSON パッチは、Deployment の Pod テンプレート内にある 1 つのフィールドだけを正確に置き換えます。本番環境では、同等の変更を元のマニフェストにも反映してください。

コマンドによって望ましい状態はすぐに変更されますが、Pod の置き換えと Ready 状態への移行は非同期で行われます。そのため、ロールアウトの状態を待つ必要があります。API が正常な応答を返しただけでは、ワークロードが正常になった証拠にはなりません。

マルチコンテナ Pod で対象コンテナを指定する

このステップでは、役割の異なるコンテナを持つ Pod を調査します。まずコンテナ名を一覧表示します。

kubectl get pod web-with-sidecar -n practice-debug -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'

JSONPath の range は、コンテナリストを順番に処理します。各コンテナのログを個別に読み取ります。

kubectl logs web-with-sidecar -n practice-debug -c reporter --tail=5
kubectl logs web-with-sidecar -n practice-debug -c web --tail=5

--tail=5 は、最後の 5 行だけに出力を制限します。reporter は通常、reporter-alive というメッセージを繰り返し出力します。NGINX には起動通知が表示されるか、まだリクエストログが表示されない場合があります。どちらも正常です。まだ誰も HTTP リクエストを送信していないためです。

web コンテナ内でコマンドを実行します。

kubectl exec web-with-sidecar -n practice-debug -c web -- nginx -v

テストしたイメージでは、次の結果が返されました。

nginx version: nginx/1.27.5

イメージが更新されると、パッチバージョンの正確な値は変わる可能性があります。重要なのは、web コンテナ内でコマンドが正常に実行されたことです。

最初の -- は、kubectl のオプションの終わりを示します。それ以降は、指定したコンテナ内で実行されるコマンドです。

この区別は重要です。-c web は kubectl が解釈するオプションですが、nginx -v はコンテナ内で実行されます。マルチコンテナ Pod で -c を省略すると、kubectl がデフォルトのコンテナを選択し、別のプロセスに関する証拠を表示する可能性があります。

同じ Pod 内のコンテナはネットワーク名前空間を共有します。そのため、BusyBox の reporter からループバックアドレス経由で NGINX にリクエストを送ることもできます。wget -qO- では、-q が進行状況メッセージを非表示にし、-O- がレスポンス本文を標準出力に書き出します。パイプによって本文が head に渡され、先頭の数行だけが表示されます。

kubectl exec web-with-sidecar -n practice-debug -c reporter -- wget -qO- http://127.0.0.1 | head

レスポンスは <!DOCTYPE html> で始まるはずです。これは、後続のチャレンジでも利用できる診断パターンです。用意された Pod 内で小さな HTTP クライアントを実行し、新しい外部ポートを公開せずにアプリケーションの動作を確認できます。

複数の観点から復旧を確認する

このステップでは、コントローラーを修正した結果、正常な置き換え Pod が作成されたことを確認します。コントローラーの利用可能数と Pod の Ready 状態をまとめて確認します。

kubectl get deployments,pods -n practice-debug
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,OWNER:.metadata.ownerReferences[0].name'

最後に、最近の Warning イベントが現在も継続中の障害を示しているか確認します。

--field-selector type=Warning は、API サーバーに Warning イベントだけを返すよう指示します。続く --sort-by=.metadata.creationTimestamp により、古いものから新しいものの順に並ぶため、過去の障害と現在の状態を区別できます。

kubectl get events -n practice-debug --field-selector type=Warning --sort-by=.metadata.creationTimestamp

復旧後も古い Warning が残ることがあります。現在の健全性を判断する際は、現在の Pod コンディションとコントローラーの利用可能状態を基準にしてください。イベントは、システムがその状態に至った経緯を説明するものです。

2 つの修正後、現在の状態は次のようになるはずです。

NAME                            READY   UP-TO-DATE   AVAILABLE
deployment.apps/broken-image    1/1     1            1
deployment.apps/crashy-worker   1/1     1            1

NAME                                 READY   STATUS    RESTARTS
pod/broken-image-...                 1/1     Running   0
pod/crashy-worker-...                1/1     Running   0
pod/web-with-sidecar                 2/2     Running   0

古い Failed、BackOff、あるいはクラスター起動時に発生した初期の FailedCreatePodSandBox Warning が表示される場合もあります。オブジェクト名とタイムスタンプを比較してください。これらの Warning は置き換えられた Pod に関するもので、新しい Pod は Running になり、両方の Deployment は利用可能になっています。イベントは履歴であり、リアルタイムのヘルスダッシュボードではありません。

最終的な結論では、現在の証拠を組み合わせます。両方の Deployment が利用可能で、意図したすべてのコンテナが Ready になっており、イメージのプル失敗やクラッシュループ状態の Pod が残っていないことを確認します。トラブルシューティングは、修正コマンドを実行した時点ではなく、検証まで行って完了します。

まとめ

コンディション、イベント、コンテナ状態、現在のログ、以前のログを使って、イメージ障害と起動障害を診断しました。生成された Pod ではなくコントローラーのテンプレートを修正し、logs と exec で個別のコンテナを対象にする方法も学びました。次のチャレンジでは、手順の案内なしで、この証拠を重視したワークフローを実践します。