はじめに
マニフェストを使って望ましい状態を記述し、Pod や Deployment を作成できるようになりました。次に身につけるべき重要なスキルは、Kubernetes がその望ましい状態を実現できないときに、何をすべきかを理解することです。
この実験では、2 つの小さな Deployment を扱います。一方は正常に動作し、もう一方には意図的なイメージタグの誤りが含まれています。同じクラスタ条件のもとで両者を比較し、広い範囲の症状から具体的な証拠へと調査を進めます。また、稼働中のオブジェクトだけを修正するのではなく、マニフェストを修正し、最後にログやコンテナ内でのコマンド実行を通じて復旧したアプリケーションを確認します。
目的は、起こりうるあらゆる障害を暗記することではありません。落ち着いてトラブルシューティングする習慣、つまり 観察し、範囲を絞り、証拠を調べ、望ましい状態を修正し、復旧を確認する という流れを身につけることです。
制御された障害を作成する
環境の起動: この実験では、完全な Kubernetes クラスターが起動します。コントロールプレーン、ノード、ネットワークコンポーネントの構成には通常 2~3 分かかります。環境の読み込みが完了するまで、そのままお待ちください。
実際のトラブルシューティングは、症状を確認するところから始まります。このステップでは、正常なワークロードと意図的に壊したワークロードをデプロイし、同じクラスタ条件のもとで比較します。
用意された作業ディレクトリへ移動し、ファイルを一覧表示します。cd は現在のディレクトリを変更し、ls はその中にある名前を一覧表示します。コマンドは別々の行に記述され、順番に実行されます。
cd /home/labex/project/debug-lab
ls
healthy-web.yaml と broken-web.yaml が表示されるはずです。どちらもレプリカ数 1 の Deployment を定義していますが、一方には後で診断する微妙な設定ミスが含まれています。
両方のマニフェストを適用します。kubectl apply は望ましい状態を API サーバーへ送信し、各 -f は入力ファイルを指定します。1 つのコマンドで複数の -f オプションを使用できます。
kubectl apply -f healthy-web.yaml -f broken-web.yaml
まず、正常な Deployment のロールアウトを待ちます。
kubectl rollout status deployment/healthy-web --timeout=60s
deployment "healthy-web" successfully rolled out というメッセージが表示されれば、クラスタが Pod をスケジュールし、キャッシュ済みの NGINX イメージを実行できることを確認できます。これは有用な基準点になります。
次に、もう一方の Deployment に短時間だけロールアウトの機会を与えます。
kubectl rollout status deployment/broken-web --timeout=15s || true
タイムアウトは想定された結果です。|| true は、この失敗が実験を中断する理由ではなく、演習のための証拠であるため、シェルに処理を続行させます。
Deployment の概要を比較します。
kubectl get deployments
healthy-web は 1/1 が ready になり、broken-web は 0/1 になるはずです。これで、問題がクラスタ全体ではなく、特定のワークロードに限定されていることを確認できました。
リソースの概要で問題を絞り込む
このステップでは、詳細に入る前に広い範囲から調査を始めます。Kubernetes のコントローラーはオブジェクトの連鎖を作成するため、Deployment の問題は多くの場合、まず ReplicaSet や Pod に現れます。
関連するオブジェクト種別をまとめて一覧表示します。カンマを使うと、1 回の kubectl get リクエストで複数のリソース種別を指定できます。また、-o wide により、ノードや IP アドレスなどの有用な列が追加されます。
kubectl get deployments,replicasets,pods -o wide
出力を上から順に確認します。
- Deployment は、望ましいレプリカ数と利用可能なレプリカ数を示します。
- ReplicaSet は、その望ましいレプリカ数を Pod に引き継ぎます。
- Pod は、コンテナの準備状態と短いステータス理由を示します。
ラベルを使って、壊れたアプリケーションだけに表示対象を絞ります。
kubectl get pods -l app=broken-web -o wide
Pod 名には自動生成されたサフィックスが含まれるため、変化する名前をスクリプトへ直接コピーするよりも、ラベルを使う方が安全です。
この段階で必要なフィールドだけを取得します。-o custom-columns='...' は、明示的に指定したオブジェクトフィールドから表を作成します。各項目には NAME のような見出しと、その値を提供する JSON フィールドのパスが含まれます。末尾のバックスラッシュは、表示上の 2 行を 1 つのコマンドとして連結します。
kubectl get pods -l app=broken-web \
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,WAITING_REASON:.status.containerStatuses[0].state.waiting.reason,NODE:.spec.nodeName'
待機理由は、最初は ErrImagePull、その後 ImagePullBackOff になる場合があります。どちらも、Kubernetes がイメージを取得できず、コンテナが一度も起動していないことを示します。単に「Pod が停止している」と表現するより、はるかに具体的な情報です。
describe で Pod を調査する
このステップでは、describe を使ってコンテナが待機している理由を確認します。概要表示からは問題の 内容 が分かりました。ここでは、その理由を詳しく調べます。
自動生成された Pod 名をシェル変数に保存します。$(...) はコマンド置換です。シェルは内部の kubectl コマンドを実行し、その出力を BROKEN_POD に代入します。JSONPath は最初に一致した Pod の名前を選択し、echo は保存された値を表示します。
BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
echo "$BROKEN_POD"
その Pod の詳細を表示します。
kubectl describe pod "$BROKEN_POD"
describe は、有用なフィールドと直近のイベントをまとめて表示します。次の 3 つの領域に注目してください。
- Containers → Image は、要求された正確なイメージを示します。
- State → Waiting → Reason は、現在のコンテナ状態を説明します。
- Events には、kubelet の試行内容とエラーメッセージが記録されています。
今回のシナリオでは、イベントメッセージに、タグ 1.27-alpine-missing が見つからないことが示されます。クラスタはマニフェストの要求どおりに動作しており、誤っているのは望ましい状態そのものです。
JSONPath を使ってイメージを直接確認します。
kubectl get pod "$BROKEN_POD" -o jsonpath='Image: {.spec.containers[0].image}{"\n"}'
大きな YAML や describe の出力に必要以上の情報が含まれている場合、JSONPath が役立ちます。ここでは、最終的に修正すべきフィールドだけを取り出しています。
イベントを時系列で読む
このステップでは、Kubernetes の活動を時系列で示す記録としてイベントを読み取ります。イベントは、スケジューリング、イメージの取得、コンテナの起動、再起動など、さまざまな状態遷移を説明する短期間の診断記録です。
名前空間内の最近のイベントを時系列順に一覧表示します。--sort-by は指定したメタデータフィールドでオブジェクトを並べ替えます。引用符は JSON 形式のフィールドパスを 1 つの引数として扱うためのものです。
kubectl get events --sort-by='.metadata.creationTimestamp'
通常、最後の行ほど新しいイベントです。OBJECT 列が壊れた Pod を指し、REASON に Pulling、Failed、BackOff などが含まれるエントリを探します。
生成された Pod 名でイベントを絞り込むと、不要な情報を減らせます。--field-selector はラベルではなく、サーバー側でオブジェクトフィールドを使ってフィルタリングします。カンマで区切られた 2 つの条件は、両方を満たす必要があります。バックスラッシュは、読みやすい複数行の記述を 1 つのコマンドとして継続させます。
BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
kubectl get events \
--field-selector involvedObject.kind=Pod,involvedObject.name="$BROKEN_POD" \
--sort-by='.metadata.creationTimestamp'
get、describe、events は、それぞれ補完し合う異なる視点として捉えてください。
getは異常なオブジェクトをすばやく見つけます。describeは、1 つのオブジェクトについて設定、状態、関連イベントをまとめて表示します。eventsは時系列の情報を提供し、繰り返し行われている試行を明らかにします。
BackOff が繰り返し表示されても、Kubernetes が Pod を放棄したわけではありません。失敗後、イメージ取得の再試行間隔を空けていることを意味します。
望ましい状態を修正し、復旧を確認する
このステップでは、望ましい状態を修正して復旧を確認します。行動に必要な証拠はそろいました。マニフェストが存在しないイメージタグを要求しています。まず保存済みのマニフェストを修正し、その後で適用して、ファイルとクラスタ上の状態を一致させます。
比較のため、両方のマニフェストに記述されたイメージ行を表示します。grep はテキストを検索し、-n は各一致行の先頭に行番号を付けます。1 つのコマンドで両方のファイルを検索できます。
grep -n 'image:' healthy-web.yaml broken-web.yaml
正常なマニフェストでは nginx:1.27-alpine が使用されています。一方、壊れたマニフェストには、存在しない -missing サフィックスが追加されています。
そのサフィックスだけを置換します。sed は s/old/new/ という形式で記述されたテキスト置換を実行します。-i を指定すると、変更後のテキストを表示するだけでなく、指定したファイルを直接編集します。
sed -i 's/nginx:1.27-alpine-missing/nginx:1.27-alpine/' broken-web.yaml
修正したファイルをローカルで検証します。
kubectl apply --dry-run=client -f broken-web.yaml
ファイルとクラスタ上のオブジェクトとの差分を確認します。
kubectl diff -f broken-web.yaml || true
差分が見つかった場合、kubectl diff は終了コード 1 を返します。そのため、|| true によって学習の流れを中断せずに続行できます。差分では、- で始まる行が古いイメージを、+ で始まる行が修正後のイメージを示します。
修正を適用し、復旧を待ちます。
kubectl apply -f broken-web.yaml
kubectl rollout status deployment/broken-web --timeout=60s
両方の Deployment が正常になったことを確認します。
kubectl get deployments
どちらも 1/1 ready になるはずです。Kubernetes は修正された Pod テンプレートから新しい ReplicaSet と Pod を作成しました。失敗した Pod を手動で修復する必要はありませんでした。
アプリケーションログを読む
このステップでは、コンテナが起動した後の新たな証拠として、アプリケーションログを使用します。kubectl logs は、コンテナの標準出力と標準エラーのストリームを取得します。
broken-web が管理する新しい正常な Pod を選択します。ここでは、先ほど使用したコマンド置換と JSONPath を再び利用します。--field-selector=status.phase=Running によって、選択対象が実行中の Pod に限定されます。
WEB_POD=$(kubectl get pods -l app=broken-web \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}')
echo "$WEB_POD"
まだ誰もページをリクエストしていないため、NGINX のアクセスログにはエントリがない可能性があります。Pod 内からリクエストを生成します。kubectl exec POD -- COMMAND では、-- によって kubectl のオプションとコンテナ内で実行するコマンドを分離します。wget -qO- は静かに取得し、ページを標準出力へ書き込みます。パイプ | はその出力を head に渡し、先頭部分だけを表示します。
kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 | head
HTML が <!DOCTYPE html> で始まっていれば、NGINX がポート 80 でローカルに応答したことを確認できます。
次に、直近のログを読み取ります。--tail=10 は最新 10 行だけを表示し、起動時のノイズによって有用なリクエスト情報が埋もれるのを防ぎます。
kubectl logs "$WEB_POD" --tail=10
GET / HTTP/1.1 を含む HTTP リクエストと、200 のレスポンスコードを探してください。ログは、コンテナは動作しているものの、アプリケーションの挙動に問題がある場合に特に役立ちます。一方、イメージ取得に失敗している場合はコンテナ自体が起動していないため、通常ログは確認できません。
コンテナ内部から調査する
このステップでは、復旧したアプリケーションをコンテナ内部から調査します。kubectl exec は、すでに実行中のコンテナ内でコマンドを実行できます。これにより、ファイルシステム、プロセス、環境変数、DNS の状態、ローカルネットワークの動作などを確認できます。
実行中の Pod 名を再利用します。
WEB_POD=$(kubectl get pods -l app=broken-web \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}')
コンテナのホスト名を確認します。
kubectl exec "$WEB_POD" -- hostname
Kubernetes はデフォルトで Pod のホスト名を Pod 名に設定するため、出力は Pod 名と一致します。
コンテナ内で NGINX の設定構文を確認します。
kubectl exec "$WEB_POD" -- nginx -t
syntax is ok と test is successful というメッセージが表示されれば、アプリケーション設定が内部的に有効であることが分かります。
最後に、コンテナ内部から外側へ向かう簡潔なヘルスチェックを実行します。>/dev/null は取得した HTML を破棄し、&& は wget が成功した場合にだけ echo を実行します。したがって、HTTP レスポンスを受信できた場合にのみ成功メッセージが表示されます。
kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 >/dev/null && echo "NGINX responded inside the Pod"
exec は状況を考えて使用してください。実行中のコンテナが必要なため、先ほどのイメージ取得失敗の診断には使えませんでした。今回のインシデントでたどった証拠の段階は次のとおりです。
get -> describe -> events -> repair manifest -> rollout status -> logs -> exec
障害の種類によって、調査が止まる段階は異なります。しかし、低コストな概要確認からより深い調査へ進むことで、トラブルシューティングを効率よく進められます。
まとめ
Kubernetes v1.35 を使って、初心者向けの一連のデバッグ手順を実践しました。正常なワークロードと異常なワークロードを比較し、ラベルと簡潔なフィールドで問題を絞り込み、describe とイベントから無効なイメージタグを特定しました。その後、宣言的な正しい状態の定義元を修正し、ロールアウトの状態、ログ、コンテナ内でのコマンド実行によって復旧を確認しました。
重要なポイントは、ワークロードの現在のライフサイクル段階に合った証拠を選ぶことです。コンテナが起動していない場合は、状態とイベントを調べます。コンテナが実行中になったら、ログと exec によってアプリケーションレベルの挙動を確認できます。次のチャレンジでは、この手順を自分で使って問題を解決します。


