本記事には広告(アフィリエイトリンク)が含まれます。

Kubernetes実践編 #09

【Kubernetes実践編 #09】障害対応 — 切り分け・復旧・再発防止の実践

9.1 はじめに

9.1.1 これまでの旅路 — 設計・構築・運用の積み上げ

実践編の第1回から第8回まで、長い道のりを歩いてきました。

第1回で構成図を描き、TaskBoardの全体像を可視化しました。第2回と第3回で基本設計書・詳細設計書を書き、すべてのパラメータに「なぜこの値か」という根拠を与えました。第4回で基盤を整備し、第5回で依存順序に沿った段階デプロイを実行し、第6回でGateway APIとNetworkPolicyによる通信制御を完成させました。第7回で監視・スケーリング・バックアップの運用設計書を仕上げ、第8回でHelmによる変更管理とNodeメンテナンスの手順を確立しました。

設計、構築、運用——インフラのライフサイクルを3つのフェーズにわたって回してきました。あなたの手元には、構成図、基本設計書、詳細設計書、構築手順書、通信制御マトリクス、運用設計書、変更管理手順書が揃っています。TaskBoardはこれらの設計書に基づいて構築され、Helmで管理され、正常に稼働しています。

しかし、ライフサイクルにはもう1つ、避けて通れないフェーズがあります。

9.1.2 本回の問題提起 — 「壊れたとき、あなたは何をする?」

VMの世界で障害対応の経験があるなら、深夜の電話で叩き起こされた記憶があるかもしれません。ESXiホストがダウンした、VMが応答しない、ストレージが見えなくなった——そのとき、何から調べましたか。おそらくvCenterのアラームを確認し、ホストの状態を見て、VMの配置を確認し、ストレージの接続を確認する。レイヤーを一つずつ切り分けて、原因を絞り込んだはずです。

K8sでも障害は起きます。Podがクラッシュする、Nodeが落ちる、通信が遮断される、ストレージがマウントできない。入門編第11回で「トラブルシュートの4種の神器」(get / describe / logs / exec)を学びました。しかし、個別のPodを調べるスキルと、システム全体の障害を体系的に切り分けて復旧するスキルは別物です。

本回では、TaskBoardを意図的に壊します。6つの異なるレイヤーで障害を注入し、検知し、切り分け、復旧し、再発防止策を講じます。これまでの8回分の知識——構成図、設計書、通信制御マトリクス、運用設計書——すべてが障害対応の「武器」として機能します。

9.1.3 本回のゴールと成果物

本回のゴールは、Pod / Node / Network / Storage / 設定の各レイヤーで発生する障害を体系的に切り分け、復旧し、再発防止策を講じ、その過程を障害報告書として文書化できるようになることです。

成果物は2つです。

  • 障害対応手順書: 切り分けフローチャートとシナリオ別対応手順をまとめた、チームで共有できる手順書
  • 障害報告書テンプレート(記入例付き): 障害の概要・タイムライン・原因分析・復旧対応・再発防止策を記録するテンプレート
【フェーズ1:設計】
  ✅ 第1回 ── 構成図
  ✅ 第2回 ── 基本設計
  ✅ 第3回 ── 詳細設計

【フェーズ2:構築】
  ✅ 第4回 ── 環境構築
  ✅ 第5回 ── アプリケーション構築
  ✅ 第6回 ── ネットワーク構築

【フェーズ3:運用】
  ✅ 第7回 ── 運用設計
  ✅ 第8回 ── 日常運用

【フェーズ4:障害対応と発展】
  ☐ 第9回 ── 障害対応        ← 今回 ★クライマックス
  ☐ 第10回 ── 本番への道

9.2 VMの障害対応とK8sの障害対応

9.2.1 VMの世界での障害対応 — ESXi/vCenter/Storageの切り分け

VMware環境で障害が発生したとき、ベテランのインフラエンジニアはレイヤーごとに切り分けます。「VMが応答しない」という連絡を受けたら、まずvCenterのアラームを確認し、次にESXiホストの状態を見て、VMの電源状態を確認し、ネットワーク接続を調べ、データストアのアクセス可否を確認する。このレイヤーの切り分けは、長年の経験で身体に染み込んだ「型」です。

レイヤーVMwareでの確認ポイントツール / 手段
プロセス層VMゲスト内のサービス状態、プロセス存否vCenter VMコンソール、SSH
ホスト層ESXiの状態(Connected / Disconnected)vCenterホスト一覧、ILO/iDRAC
ネットワーク層vSwitch / ポートグループ / NSXルールvCenter NW構成、NSX Manager
ストレージ層データストアの接続状態、VMFS/vSANの健全性vCenterストレージビュー、esxcli
設定層VM設定(vCPU/メモリ/NIC)、ゲストOS設定VM設定画面、構成ファイル

レイヤーを下から(ストレージ/ネットワーク)調べるか、上から(プロセス)調べるかは状況によりますが、「レイヤーごとに切り分ける」という思考フレームは変わりません。

9.2.2 K8sの障害対応 — Pod/Node/NW/Storageの切り分け

K8sでも同じレイヤー構造があります。名前が変わっただけで、思考フレームはそのまま使えます。

レイヤーK8sでの確認ポイントツール / コマンド
Pod層Pod状態(Running / CrashLoopBackOff / OOMKilled / Pending)kubectl get podskubectl describe podkubectl logs
Node層Node状態(Ready / NotReady)、リソース使用率kubectl get nodeskubectl describe nodekubectl top nodes
Network層Service疎通、NetworkPolicyルール、DNS解決kubectl exec(接続テスト)、kubectl describe networkpolicy
Storage層PVC状態(Bound / Pending)、StorageClasskubectl get pvckubectl describe pvc
設定層ConfigMap / Secret内容、環境変数、マニフェスト設定値kubectl describe configmapkubectl get -o yaml

9.2.3 共通する思考フレーム — レイヤーごとの切り分け

VMとK8sの障害対応で共通するのは、「まずレイヤーを特定し、次にそのレイヤー内で原因を絞り込む」という2段階のアプローチです。VMでESXiが落ちていればホスト層の問題、K8sでNodeがNotReadyならNode層の問題。レイヤーさえ特定できれば、調査対象が大幅に絞り込まれます。

もう1つ共通するのは、設計書の存在価値です。VMの構成図があれば「どのホストにどのVMが載っているか」がすぐわかります。K8sの構成図(第1回の成果物)があれば「どのNamespaceにどのPodがあり、どこと通信しているか」がすぐわかります。通信制御マトリクス(第6回の成果物)があれば「この通信は許可されているはずか、遮断されているはずか」が設計書から判断できます。

障害報告書を書く習慣も、VMでもK8sでも変わりません。プラットフォームが変わっても、プロの仕事の仕方は同じです。

9.3 障害対応の基本フレーム

9.3.1 障害対応フロー(検知→切り分け→調査→復旧→恒久対策→報告)

本回の6つの障害シナリオすべてで、以下の共通フローを踏みます。

1. 検知 — 障害の発生を認識する
     kubectl get pods / kubectl top / ヘルスチェック失敗

2. 切り分け — 原因のレイヤーを特定する
     Pod層 / Node層 / Network層 / Storage層 / 設定層

3. 調査 — 具体的な原因を特定する
     kubectl describe / kubectl logs / kubectl get events

4. 復旧 — サービスを回復させる(暫定対処)
     Pod再起動 / マニフェスト修正 / ロールバック

5. 恒久対策 — 再発を防止する
     設計変更 / パラメータ修正 / 監視追加

6. 報告 — 障害報告書を作成する

VMの障害対応でも「検知→一次切り分け→エスカレーション→暫定復旧→恒久対策→報告」という流れは同じです。K8sではkubectlがvCenterコンソールの代わりを果たし、マニフェストの修正がVM設定変更の代わりになります。フローの構造は変わりません。

9.3.2 切り分けフローチャート

障害発生時に「まず何を見るか」を迷わないよう、切り分けのフローチャートを整理します。このフローチャートは、本回の障害対応手順書の核になる部分です。

障害発生
  │
  ├── Podは起動しているか? ── kubectl get pods -n <ns>
  │     │
  │     ├── No(異常状態)
  │     │     ├── CrashLoopBackOff
  │     │     │     └── kubectl logs / kubectl describe pod
  │     │     │           ├── アプリエラー → シナリオ1(アプリクラッシュ)
  │     │     │           ├── OOMKilled   → シナリオ2(メモリ枯渇)
  │     │     │           └── 設定エラー  → シナリオ6(ConfigMapミス)
  │     │     │
  │     │     ├── Pending
  │     │     │     └── kubectl describe pod(Eventsセクション)
  │     │     │           ├── PVC関連エラー → シナリオ5(ストレージ障害)
  │     │     │           └── スケジュール不能 → Node障害 or リソース不足
  │     │     │
  │     │     └── ImagePullBackOff
  │     │           └── イメージ名・タグ・レジストリの確認
  │     │
  │     └── Yes(Running)── Podは動いているが正常か?
  │           │
  │           ├── Readiness失敗 ── kubectl describe pod(Conditions)
  │           │     └── DB接続エラー → シナリオ4(NW障害)or DB自体の障害
  │           │
  │           └── 動作異常(レスポンスエラー等)
  │                 └── シナリオ6(設定ミス)
  │
  ├── Nodeは正常か? ── kubectl get nodes
  │     └── NotReady → シナリオ3(Node障害)
  │
  └── 通信は正常か? ── kubectl exec でテスト用Pod起動 / 接続テスト
        └── 特定経路が遮断 → シナリオ4(NetworkPolicy誤設定)

このフローチャートを手元に置きながら、以降の6シナリオに取り組みます。各シナリオで「フローチャートのどの分岐に該当するか」を意識してください。

9.3.3 4種の神器の復習 — get / describe / logs / exec

入門編第11回で学んだトラブルシュートの4種の神器を、障害対応の文脈で整理し直します。

コマンド役割障害対応での使いどころ
kubectl getリソースの一覧と状態検知: Pod/Node/PVCの状態を素早く把握する。-o wideでNode配置も確認
kubectl describeリソースの詳細とEvents切り分け: Eventsセクションでエラーの経緯を時系列で追跡する
kubectl logsコンテナのログ出力調査: アプリケーションのエラーメッセージから原因を特定する。--previousでクラッシュ前のログも取得
kubectl execコンテナ内でのコマンド実行調査: ネットワーク接続テスト、ファイルシステム確認、環境変数確認

4種の神器に加えて、本回では以下のコマンドも多用します。

コマンド障害対応での使いどころ
kubectl get events --sort-by=.lastTimestampNamespace内のイベントを時系列で確認。複数リソースにまたがる障害の全体像を把握
kubectl top pods / nodesリソース使用量のリアルタイム確認。OOMKilled前のメモリ消費を観察
kubectl get pods -o widePodがどのNodeに配置されているかを確認。Node障害時の影響範囲を特定
docker stop / startkindのNode障害シミュレーション(Worker NodeはDockerコンテナ)

準備が整いました。ここからは、TaskBoardを意図的に壊して復旧する6つのシナリオに入ります。各シナリオの開始前に、TaskBoardが正常稼働していることを確認してください。

[Execution User: developer]
# TaskBoardの正常稼働確認
kubectl get pods -n app
kubectl get pods -n db
kubectl get nodes

# 期待される状態:
#   app Namespace: Nginx Pod x2 (Running), TaskBoard API Pod x2 (Running)
#   db Namespace:  MySQL Pod x1 (Running)
#   全Node: Ready

9.4 シナリオ1 — アプリクラッシュ(CrashLoopBackOff)

9.4.1 障害を注入する

最初のシナリオは、最も遭遇頻度の高い障害——CrashLoopBackOffです。TaskBoard APIのDeploymentを変更し、存在しないイメージタグを指定します。

[Execution User: developer]
# 障害注入: TaskBoard APIのイメージタグを存在しないものに変更
kubectl set image deployment/taskboard-api \
  taskboard-api=taskboard-api:99.99 -n app

障害が注入されました。ここから障害対応フローの「検知」に入ります。

9.4.2 検知と切り分け

まず、Podの状態を確認します。

[Execution User: developer]
# 検知: Podの状態を確認
kubectl get pods -n app

出力例:

NAME                              READY   STATUS             RESTARTS   AGE
nginx-xxxxxxxxxx-xxxxx            1/1     Running            0          1h
nginx-xxxxxxxxxx-yyyyy            1/1     Running            0          1h
taskboard-api-xxxxxxxxxx-xxxxx    0/1     ImagePullBackOff   0          30s
taskboard-api-xxxxxxxxxx-yyyyy    1/1     Running            0          1h

切り分けフローチャートに従います。Podが起動していない → ImagePullBackOff → イメージ問題。ローリングアップデート中なので、旧Podは1つRunningのまま残っています。これはDeploymentのstrategyで設定したmaxUnavailable: 0の効果です。

[Execution User: developer]
# 切り分け: Eventsで詳細を確認
kubectl describe pod -l component=api -n app | grep -A 10 "Events:"

Eventsセクションに以下のようなメッセージが表示されます。

Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  1m    default-scheduler  Successfully assigned app/taskboard-api-xxx to kind-worker2
  Normal   Pulling    1m    kubelet            Pulling image "taskboard-api:99.99"
  Warning  Failed     1m    kubelet            Failed to pull image "taskboard-api:99.99": ...
  Warning  Failed     1m    kubelet            Error: ImagePullBackOff

原因が明確です。taskboard-api:99.99というイメージが存在しません。

9.4.3 原因特定と復旧

正しいイメージタグに戻します。

[Execution User: developer]
# 復旧: 正しいイメージタグに戻す
kubectl set image deployment/taskboard-api \
  taskboard-api=taskboard-api:3.0.0 -n app

# ロールアウト状態を監視
kubectl rollout status deployment/taskboard-api -n app
deployment "taskboard-api" successfully rolled out
[Execution User: developer]
# 復旧確認: 全Podが正常稼働していること
kubectl get pods -n app

# ヘルスチェック確認
kubectl exec -n app deploy/taskboard-api -- \
  curl -s http://localhost:8080/health/ready

全PodがRunningに戻り、ヘルスチェックが正常であれば復旧完了です。

9.4.4 再発防止策

このシナリオの直接原因は「存在しないイメージタグの指定」です。根本原因は「イメージタグの管理ルールが不明確であること」です。

再発防止策として以下を検討します。

  • イメージタグの命名規則策定: latestタグの使用を禁止し、セマンティックバージョニング(3.0.03.1.0等)を義務化する
  • 変更管理手順書の遵守: 第8回で作成した変更管理手順書に従い、kubectl diffで変更内容を事前確認する
  • dry-runによる事前検証: kubectl apply --dry-run=serverでマニフェストの妥当性を確認してから適用する

なお、今回はkubectl set imageで直接変更しましたが、Helm管理下のリリースではhelm upgradevalues.yamlを使ってイメージタグを管理します(第8回で構築済み)。Helmの変更履歴(helm history)が残るため、問題発生時にhelm rollbackで即座に前バージョンに復元できます。

9.5 シナリオ2 — メモリ枯渇(OOMKilled)

2つ目のシナリオは、実務で最も調査が厄介な障害の1つ——OOMKilledです。このシナリオは本回で最も丁寧に扱います。理由は、Payara MicroのようなJVMアプリケーションでは、JVMヒープとK8sのresources.limitsの関係を正しく理解していないと原因特定が困難だからです。

9.5.1 障害を注入する

TaskBoard APIのresources.limits.memoryを極端に小さくします。Payara MicroのJVMは起動時に一定量のヒープメモリを確保するため、64Miでは確実にOOMKilledが発生します。

[Execution User: developer]
# 障害注入前の正常状態を記録
kubectl top pods -n app
# TaskBoard APIのメモリ使用量(正常時)を確認しておく
[Execution User: developer]
# 障害注入: limits.memoryを64Miに変更
kubectl patch deployment taskboard-api -n app --type='json' \
  -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "64Mi"},
       {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "64Mi"}]'

9.5.2 検知と切り分け

新しいPodが作成されますが、すぐにクラッシュします。少し待ってから状態を確認します。

[Execution User: developer]
# 30秒ほど待ってから確認
kubectl get pods -n app -w

出力例:

NAME                              READY   STATUS      RESTARTS      AGE
taskboard-api-xxxxxxxxxx-zzzzz    0/1     OOMKilled   3 (15s ago)   1m

切り分けフローチャートに従います。Podが起動していない → CrashLoopBackOff(OOMKilledによる再起動の繰り返し)。RESTARTSカウントが増加し続けています。

[Execution User: developer]
# 切り分け: OOMKilledの確認
kubectl describe pod -l component=api -n app | grep -A 5 "Last State:"
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      ...
      Finished:     ...

Reason: OOMKilledExit Code: 137——これはLinuxカーネルのOOM Killerがプロセスを強制終了したことを示しています。Exit Code 137はSIGKILL(128 + 9)です。

Payara MicroはJVMベースのアプリケーションサーバーです。JVMの起動時にヒープメモリ(デフォルトではコンテナのメモリ制限の約25%)、Metaspace、ネイティブメモリが確保されます。これらの合計がK8sのlimits.memory(64Mi)を超えた瞬間にOOMKilledが発生します。

第3回(詳細設計)で設計したlimits.memory: 512Miの根拠を振り返ります。

JVMメモリ領域概算使用量備考
ヒープ(-Xmx)約256Miコンテナメモリの50%程度をJVMが自動設定
Metaspace約64Miクラス情報の格納領域
スレッドスタック約50Miスレッド数に依存
ネイティブメモリ約30MiJNI、バッファ等
合計約400Mi512Miのlimitsに対して約80%

64Miではヒープの確保すら不可能で、JVM起動直後にOOMKilledとなります。

9.5.3 原因特定と復旧

resources.limits.memoryを設計値(512Mi)に戻します。

[Execution User: developer]
# 復旧: limitsを設計値に戻す
kubectl patch deployment taskboard-api -n app --type='json' \
  -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "512Mi"},
       {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "384Mi"}]'

# ロールアウト状態を監視
kubectl rollout status deployment/taskboard-api -n app
[Execution User: developer]
# 復旧確認
kubectl get pods -n app
kubectl top pods -n app

# ヘルスチェック確認
kubectl exec -n app deploy/taskboard-api -- \
  curl -s http://localhost:8080/health/ready

PodがRunningに戻り、kubectl topでメモリ使用量が正常範囲(200〜350Mi程度)に収まっていれば復旧完了です。

9.5.4 再発防止策

OOMKilledの再発防止には、JVMとK8sの関係を理解した上での設計が不可欠です。

  • resources設計の文書化: 第3回の詳細設計書に記載した「JVMヒープ + Metaspace + ネイティブメモリ ≤ limits.memory × 80%」の設計根拠をチームで共有する
  • 監視閾値の設定: 第7回の運用設計書に「メモリ使用量がlimitsの70%を超えたらアラート」のルールを追加する
  • 負荷テストの実施: 実際のリクエストパターンでメモリ使用量のピークを測定し、limitsに十分なマージンがあることを確認する

このシナリオは後ほど9.10節の障害報告書の記入例として使用します。

9.6 シナリオ3 — Node障害

9.6.1 障害を注入する

3つ目のシナリオは、インフラエンジニアにとって最も馴染み深い障害——ホスト(Node)の停止です。VMの世界でESXiホストがダウンした場合にvSphere HAがVMを別ホストに退避させるのと同様に、K8sでもNode障害時にPodが自動で別Nodeに再配置されます。

まず、現在のPod配置を記録します。

[Execution User: developer]
# 障害注入前: Podの配置Nodeを記録
kubectl get pods -o wide -n app
kubectl get pods -o wide -n db

出力例:

# app Namespace
NAME                              READY   STATUS    NODE
nginx-xxxxxxxxxx-xxxxx            1/1     Running   kind-worker
nginx-xxxxxxxxxx-yyyyy            1/1     Running   kind-worker2
taskboard-api-xxxxxxxxxx-xxxxx    1/1     Running   kind-worker2
taskboard-api-xxxxxxxxxx-yyyyy    1/1     Running   kind-worker3

# db Namespace
NAME       READY   STATUS    NODE
mysql-0    1/1     Running   kind-worker3

記録しました。ここでkind-worker2を停止します。kind-worker2にはNginx Pod 1つとTaskBoard API Pod 1つが配置されています。

[Execution User: developer]
# 障害注入: Worker Nodeを停止(kindのNodeはDockerコンテナ)
docker stop kind-worker2

9.6.2 検知と切り分け

[Execution User: developer]
# 検知: Nodeの状態を確認
kubectl get nodes

出力例:

NAME                  STATUS     ROLES           AGE   VERSION
kind-control-plane    Ready      control-plane   1d    v1.32.x
kind-worker           Ready      <none>          1d    v1.32.x
kind-worker2          NotReady   <none>          1d    v1.32.x
kind-worker3          Ready      <none>          1d    v1.32.x

kind-worker2のStatusがNotReadyになっています。切り分けフローチャートの「Nodeは正常か? → NotReady → シナリオ3」に該当します。

[Execution User: developer]
# 切り分け: 影響を受けるPodを確認
kubectl get pods -o wide -n app
kubectl get pods -o wide -n db

kind-worker2上のPodがTerminating状態に遷移しています。ただし、NodeがNotReadyのためkubeletがPodの終了処理を実行できず、しばらくの間Terminatingのまま残ります。

9.6.3 Pod再配置の観察

K8sのコントローラーはNode障害を検出すると、一定時間(デフォルト5分)待機した後、該当Node上のPodを他のNodeに再スケジュールします。この待機時間はpod-eviction-timeoutで制御されます。

[Execution User: developer]
# Pod再配置を継続的に監視(5分ほど待つ)
kubectl get pods -o wide -n app -w

5分程度待つと、kind-worker2上にあったNginx PodとTaskBoard API Podがkind-workerまたはkind-worker3に再配置されます。Deploymentのreplicas数が維持されるようコントローラーが新しいPodを作成します。

PDB(PodDisruptionBudget)の効果も確認します。第2回(基本設計)で設計したPDB(minAvailable: 1)により、Node障害時も最低1つのPodがRunning状態を維持しています。これにより、サービスの完全断は発生しません。

[Execution User: developer]
# PDBの状態確認
kubectl get pdb -n app

9.6.4 Node復旧と再発防止策

[Execution User: developer]
# Node復旧
docker start kind-worker2

# Node復帰を確認
kubectl get nodes

数十秒後にkind-worker2のStatusがReadyに戻ります。Node復帰後、既に他Nodeに再配置されたPodがすぐにkind-worker2に戻るわけではありません。現在のPod配置はそのまま維持されます。次にPodの再作成やスケールアウトが発生したとき、スケジューラーがkind-worker2も配置先の候補として扱います。

[Execution User: developer]
# 復旧確認: 全Node Ready、全Pod Running
kubectl get nodes
kubectl get pods -o wide -n app
kubectl get pods -o wide -n db

再発防止策として以下を検討します。

  • PDB設計の重要性: PDB(minAvailable: 1)がNode障害時のサービス継続に寄与していることを確認できた。PDBが未設定のワークロードがないか確認する
  • Pod分散配置の検討: topologySpreadConstraintsを設定し、同一Deploymentの全Podが同じNodeに集中しないようにする。1つのNodeに全APIのPodが配置されていた場合、そのNode障害でAPIサービスが全断する
  • Node監視: kubectl get nodesを定期的に実行する監視を検討する(本番ではNode Exporterやクラウドプロバイダーの監視機能を使用)

9.7 シナリオ4 — NW障害(NetworkPolicy誤設定)

4つ目のシナリオは、切り分けが最も難しい障害——ネットワーク障害です。NetworkPolicyの誤設定によるものですが、症状としては「TaskBoard APIがMySQLに接続できない」という形で現れます。NW障害はログにDB接続エラーが出るため、一見するとDB障害に見えます。この「見た目と原因のズレ」がNW障害の切り分けを難しくしています。

9.7.1 障害を注入する

db NamespaceのNetworkPolicy(netpol-db-allow-api)のラベルセレクタを誤った値に変更します。第6回で構築した正しい設定では、namespaceSelectorlayer: applicationラベルを持つNamespace(app)からのcomponent: apiラベルを持つPodのIngressを許可しています。このlayer: applicationlayer: frontendに変更し、マッチしないようにします。

[Execution User: developer]
# 障害注入: NetworkPolicyのラベルセレクタを誤った値に変更
cat <<'EOF' > /tmp/netpol-db-allow-api-broken.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-ingress
  namespace: db
spec:
  podSelector:
    matchLabels:
      component: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              layer: frontend          # ← 誤り!正しくは layer: application
          podSelector:
            matchLabels:
              component: api
      ports:
        - protocol: TCP
          port: 3306
EOF

kubectl apply -f /tmp/netpol-db-allow-api-broken.yaml

9.7.2 検知と切り分け — readinessProbeの失敗から追跡する

障害注入後、しばらくするとTaskBoard APIのreadinessProbが失敗し始めます。readinessProbeは/health/readyエンドポイントを呼び出しており、このエンドポイントにはDB接続確認(応用編第8回で追加したHealthCheckReady.java)が含まれています。

[Execution User: developer]
# 検知: Podの状態を確認
kubectl get pods -n app

出力例:

NAME                              READY   STATUS    RESTARTS   AGE
nginx-xxxxxxxxxx-xxxxx            1/1     Running   0          1h
nginx-xxxxxxxxxx-yyyyy            1/1     Running   0          1h
taskboard-api-xxxxxxxxxx-xxxxx    0/1     Running   0          1h
taskboard-api-xxxxxxxxxx-yyyyy    0/1     Running   0          1h

READY0/1になっています。PodはRunningですが、readinessProbeが失敗しているため、Serviceのエンドポイントから外れています。Gateway API経由のリクエストがTaskBoard APIに届きません。

[Execution User: developer]
# 切り分け: ログでエラー内容を確認
kubectl logs -n app deploy/taskboard-api --tail=20

ログには以下のようなDB接続エラーが記録されています。

SEVERE: Connection refused to host mysql.db.svc.cluster.local:3306
javax.persistence.PersistenceException: Unable to acquire connection from pool
...

ここで、このエラーメッセージだけ見ると「MySQLが落ちている」と判断してしまいがちです。実際にMySQLの状態を確認しましょう。

[Execution User: developer]
# MySQLは正常か?
kubectl get pods -n db
kubectl exec -n db mysql-0 -- mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}

MySQLは正常に稼働しています。つまり、DB自体の問題ではなく、APIからDBへの通信経路に問題があります。

テスト用Podで通信テストを実行します。

[Execution User: developer]
# app Namespaceからdb NamespaceのMySQL(TCP 3306)への接続テスト
kubectl run test-netpol --rm -it --restart=Never \
  --image=busybox -n app \
  --labels="component=api" \
  -- nc -zv -w 3 mysql.db.svc.cluster.local 3306

タイムアウトします。component: apiラベルを持つPodからMySQLへの通信が遮断されています。

ここでAIを使って切り分けの方向性を確認してみましょう。

💬 あなた → AI(Claude):
TaskBoard APIのPodが以下のエラーを出しています。原因を分析してください。

SEVERE: Connection refused to host mysql.db.svc.cluster.local:3306
javax.persistence.PersistenceException: Unable to acquire connection from pool

MySQLのPodは正常にRunningです。app Namespaceのテスト用Podからdb NamespaceのMySQL(TCP 3306)に接続テストしたところ、タイムアウトしました。

🤖 Claude の回答:
MySQL Podが正常稼働しているにもかかわらず接続がタイムアウトするため、ネットワーク層の問題が最も疑わしいです。考えられる原因を優先度順に挙げます。

1. NetworkPolicyの設定ミス — db NamespaceのNetworkPolicyが、app Namespaceからのingressを正しく許可していない可能性があります。kubectl describe networkpolicy -n dbでルールを確認してください。特にnamespaceSelectorpodSelectorのラベルが実際のNamespace/Podのラベルと一致しているかを確認します。

2. DNS解決の問題mysql.db.svc.cluster.localが正しいIPに解決されているか、nslookupで確認してください。

3. Serviceの設定ミス — Headless Serviceのセレクタが正しくMySQLのPodにマッチしているか確認してください。

テスト用Podからのタイムアウトという情報から、NetworkPolicyが最も可能性が高いです。

🔍 ここで立ち止まって確認しましょう

AIの分析は一般論として的確です。「MySQLが正常 + 通信がタイムアウト → NetworkPolicyが疑わしい」という推論は正しい方向を示しています。しかし、AIはあなたの環境の具体的な構成を知りません。ここからは自分の設計書と照合して原因を特定します。

✅ 最近NetworkPolicyを変更していないか? → 変更した(障害注入で変更済み)
✅ 通信制御マトリクス(第6回成果物)と照合したか? → 次の節で照合する
✅ テスト用Podから直接接続テストを実行したか? → 実行済み(タイムアウト確認)

AIは有力な情報源ですが、最終判断は自分の環境知識に基づきます。「自分の設計書にはどう書いてあるか」が、切り分けの最も信頼できる基準です。

9.7.3 通信制御マトリクスとの照合で原因を特定する

第6回で作成した通信制御マトリクスを参照します。「API(app Namespace, component: api)→ MySQL(db Namespace, component: db): TCP 3306 ✅ 許可」と記載されています。設計上は許可されるべき通信が遮断されている——NetworkPolicyの設定が設計書と一致していない可能性が高いです。

[Execution User: developer]
# NetworkPolicyの詳細を確認
kubectl describe networkpolicy allow-api-ingress -n db

出力のうち、Allowing ingress trafficセクションに注目します。

Spec:
  PodSelector:     component=db
  Allowing ingress traffic:
    To Port: 3306/TCP
    From:
      NamespaceSelector: layer=frontend
      PodSelector: component=api

原因が判明しました。NamespaceSelectorlayer=frontendになっています。正しくはlayer=applicationです。app Namespaceのラベルを確認します。

[Execution User: developer]
# app Namespaceのラベルを確認
kubectl get namespace app --show-labels
NAME   STATUS   AGE   LABELS
app    Active   1d    kubernetes.io/metadata.name=app,layer=application

app Namespaceのラベルはlayer=applicationです。NetworkPolicyのnamespaceSelectorlayer=frontendを要求しているため、マッチするNamespaceが存在せず、通信が遮断されています。

9.7.4 復旧と再発防止策

[Execution User: developer]
# 復旧: 正しいNetworkPolicyを再適用
kubectl apply -f ~/k8s-production/netpol-db-allow-api.yaml

# 復旧確認: 通信テスト
kubectl run test-netpol --rm -it --restart=Never \
  --image=busybox -n app \
  --labels="component=api" \
  -- nc -zv -w 3 mysql.db.svc.cluster.local 3306

接続が成功するはずです。readinessProbeも自動的に回復し、TaskBoard API PodのREADYが1/1に戻ります。

[Execution User: developer]
# Pod状態の復旧確認(readinessProbeの回復を待つ)
kubectl get pods -n app -w

再発防止策として以下を検討します。

  • NetworkPolicy変更時のテスト手順: 変更前にテスト用Podで通信テストを実施し、変更後に同じテストを実行して結果を比較する。第6回のPhase Cで実施したE2Eテストの手順をそのまま再利用する
  • 通信制御マトリクスとの照合をルール化: NetworkPolicy変更時は必ず通信制御マトリクス(第6回成果物)と照合し、設計書とマニフェストの一致を確認する
  • ラベルの一覧管理: Namespace / Podに付与しているラベルの一覧を設計書に明記し、NetworkPolicyで参照するラベルとの整合性を維持する

9.8 シナリオ5 — ストレージ障害(PVC設定ミス)

9.8.1 障害を注入する

5つ目のシナリオは、ストレージ障害です。MySQL StatefulSetのvolumeClaimTemplatesで参照するStorageClass名を存在しないものに変更します。

StatefulSetのvolumeClaimTemplatesはイミュータブル(変更不可)なフィールドです。直接変更するにはStatefulSetの削除・再作成が必要です。ここではStatefulSetを一度削除し、誤った設定で再作成します。

[Execution User: developer]
# 障害注入前: 現在のPVCの状態を記録
kubectl get pvc -n db

# StatefulSetを削除(PVCは保持される)
kubectl delete statefulset mysql -n db --cascade=orphan

--cascade=orphanを指定することで、StatefulSetは削除されますがPodとPVCはそのまま残ります。

[Execution User: developer]
# 障害注入: 存在しないStorageClassを指定したStatefulSetを作成
cat <<'EOF' > /tmp/mysql-statefulset-broken.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: db
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      component: db
  template:
    metadata:
      labels:
        component: db
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          ports:
            - containerPort: 3306
          envFrom:
            - secretRef:
                name: mysql-secret
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: mysql-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: non-existent-storage    # ← 存在しないStorageClass
        resources:
          requests:
            storage: 1Gi
EOF

# 既存のmysql-0 Podを削除してStatefulSetに管理を移す
kubectl delete pod mysql-0 -n db
kubectl apply -f /tmp/mysql-statefulset-broken.yaml

既存のPVC(mysql-data-mysql-0)はBound状態で残っていますが、StatefulSetが新しいPodを作成する際、volumeClaimTemplatesのStorageClass名と既存PVCのStorageClass名が一致していれば既存PVCが再利用されます。ここでは不一致のため、新しいPVCが作成されますが、存在しないStorageClassを指定しているためPendingのままになります。

9.8.2 検知と切り分け

[Execution User: developer]
# 検知: Podの状態を確認
kubectl get pods -n db

出力例:

NAME      READY   STATUS    RESTARTS   AGE
mysql-0   0/1     Pending   0          30s

切り分けフローチャートに従います。Podが起動していない → Pending → PVCかスケジュール不能。

[Execution User: developer]
# 切り分け: Pod Eventsを確認
kubectl describe pod mysql-0 -n db | grep -A 10 "Events:"
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  30s   default-scheduler  0/4 nodes are available:
    pod has unbound immediate PersistentVolumeClaims. ...
[Execution User: developer]
# PVCの状態を確認
kubectl get pvc -n db
NAME                STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS            AGE
mysql-data-mysql-0  Pending                                      non-existent-storage    30s

PVCがPendingのままです。STORAGECLASS列がnon-existent-storageになっています。

[Execution User: developer]
# 利用可能なStorageClassを確認
kubectl get storageclass

non-existent-storageというStorageClassは存在しないことが確認できます。kind環境ではstandardがデフォルトのStorageClassです。

9.8.3 PVC復旧とデータ保全

復旧手順として、誤ったStatefulSetを削除し、正しいStorageClass名で再作成します。

[Execution User: developer]
# 誤ったStatefulSetとPVCを削除
kubectl delete statefulset mysql -n db
kubectl delete pvc mysql-data-mysql-0 -n db

# 正しいStatefulSetマニフェストで再作成
kubectl apply -f ~/k8s-production/mysql-statefulset.yaml

# 復旧確認
kubectl get pods -n db -w
kubectl get pvc -n db

PVCがBound状態になり、MySQL PodがRunningになれば復旧完了です。

ただし、PVCを削除・再作成したため、以前のデータは失われています。第7回(運用設計)で設計したCronJobによるバックアップが存在すれば、バックアップからリストアできます。これが、バックアップ設計の重要性を実感する瞬間です。

[Execution User: developer]
# DB初期化Jobで初期データを復元(バックアップがない場合の最低限の復旧)
kubectl delete job db-init-job -n db --ignore-not-found
kubectl apply -f ~/k8s-production/db-init-job.yaml
kubectl wait --for=condition=complete job/db-init-job -n db --timeout=60s

# データ確認
kubectl exec -n app deploy/taskboard-api -- \
  curl -s http://localhost:8080/api/tasks

9.8.4 再発防止策

  • StorageClass名のバリデーション: マニフェスト適用前にkubectl get storageclassで利用可能なStorageClassを確認する
  • dry-runによる事前確認: kubectl apply --dry-run=server -fでマニフェストの妥当性を事前に検証する
  • バックアップの定期確認: CronJobによるバックアップが正常に実行されていることを定期的に確認する。バックアップは「存在するだけ」では不十分で、リストアテストまで含めて初めて信頼できる
  • PVC削除時のデータ消失リスクの周知: PVCの削除はデータの完全消失を意味する。変更管理手順書に「PVC操作は必ずバックアップ確認後に実施」と明記する

9.9 シナリオ6 — 設定ミス(ConfigMap値の誤り)

9.9.1 障害を注入する

最後のシナリオは、設定ミスによる障害です。Nginx ConfigMap(nginx-config)に構文エラーのあるnginx.confを設定し、rollout restartで反映します。第8回で学んだ「ConfigMap変更 → rollout restart」の手順が、誤った設定で実行された場合に何が起きるかを体験します。

[Execution User: developer]
# 障害注入前: 現在のConfigMapをバックアップ
kubectl get configmap nginx-config -n app -o yaml > /tmp/nginx-config-backup.yaml
[Execution User: developer]
# 障害注入: 構文エラーのあるnginx.confをConfigMapに設定
kubectl create configmap nginx-config -n app \
  --from-literal=nginx.conf='
server {
    listen 8080;
    server_name _;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }

    # ← 閉じ括弧が不足(構文エラー)
' \
  --dry-run=client -o yaml | kubectl apply -f -
[Execution User: developer]
# ConfigMap反映のためrollout restart
kubectl rollout restart deployment/nginx -n app

9.9.2 検知と切り分け

[Execution User: developer]
# 検知: Podの状態を確認
kubectl get pods -n app

出力例:

NAME                              READY   STATUS             RESTARTS      AGE
nginx-xxxxxxxxxx-zzzzz            0/1     CrashLoopBackOff   3 (10s ago)   1m
nginx-xxxxxxxxxx-xxxxx            1/1     Running            0             1h
taskboard-api-xxxxxxxxxx-xxxxx    1/1     Running            0             1h
taskboard-api-xxxxxxxxxx-yyyyy    1/1     Running            0             1h

新しいNginx PodがCrashLoopBackOffになっています。ローリングアップデートの設定により、旧Podが1つRunningのまま残っています。

[Execution User: developer]
# 切り分け: Nginxのエラーログを確認
kubectl logs -n app -l component=frontend --tail=10
nginx: [emerg] unexpected end of file, expecting "}" in /etc/nginx/nginx.conf:12
nginx: configuration file /etc/nginx/nginx.conf test failed

nginx.confの構文エラーが原因です。Nginxは設定ファイルの構文チェックに失敗するとプロセスを起動できず、コンテナが即座に終了します。

9.9.3 復旧と変更管理手順の徹底

[Execution User: developer]
# 復旧: バックアップからConfigMapを復元
kubectl apply -f /tmp/nginx-config-backup.yaml

# 再度rollout restartで正しい設定を反映
kubectl rollout restart deployment/nginx -n app

# ロールアウト状態を監視
kubectl rollout status deployment/nginx -n app
[Execution User: developer]
# 復旧確認
kubectl get pods -n app
kubectl exec -n app deploy/nginx -- nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Nginx Podが全てRunningに戻り、nginx -t(構文チェック)が成功していれば復旧完了です。

9.9.4 再発防止策

  • ConfigMap変更前のバリデーション: Nginxの場合、ConfigMap更新前にテスト用Podでnginx -t相当の構文チェックを実施する。変更管理手順書(第8回成果物)に「設定ファイルのバリデーション手順」を追加する
  • 変更前バックアップの義務化: ConfigMap変更前に必ずkubectl get configmap <name> -o yaml > backup.yamlでバックアップを取得する
  • 変更管理手順書の遵守: 第8回で作成した手順書の「2.1 ConfigMap変更と反映」に従い、変更 → バリデーション → 反映 → 確認のフローを守る

6つのシナリオすべてを完了しました。TaskBoardは再び正常に稼働しています。

[Execution User: developer]
# 全シナリオ完了後の最終確認
kubectl get pods -n app
kubectl get pods -n db
kubectl get nodes
kubectl get pvc -n db

9.10 障害報告書を書く

9.10.1 障害報告書テンプレート

障害を復旧して終わり、ではプロの仕事ではありません。「何が起き、なぜ起き、どう直し、どう防ぐか」を文書として残すことで、チーム全体の知見になります。VMの世界でも障害報告書を書いていたはずです。K8sでも同じです。

以下のテンプレートをTaskBoardの障害対応手順書に組み込みます。

============================================================
障害報告書
============================================================

1. 障害概要
   発生日時:
   検知方法:
   影響範囲:
   障害レベル: □ 重大(サービス全断)  □ 中程度(一部機能停止)  □ 軽微(性能劣化)

2. タイムライン
   HH:MM  検知
   HH:MM  切り分け開始
   HH:MM  原因特定
   HH:MM  暫定復旧
   HH:MM  完全復旧

3. 原因分析
   直接原因:
   根本原因:

4. 復旧対応
   暫定対処の内容:
   暫定対処の確認結果:

5. 再発防止策
   恒久対策:
   実施期限:
   担当者:

6. 教訓
   今回の障害から学んだこと:

============================================================

9.10.2 シナリオ2(OOMKilled)の記入例

シナリオ2(OOMKilled)を題材に、障害報告書の記入例を示します。OOMKilledを選んだ理由は、原因分析が明確で、再発防止策がresources設計(第3回の成果物)に直結するためです。

============================================================
障害報告書
============================================================

1. 障害概要
   発生日時: 2026-01-15 14:30(JST)
   検知方法: kubectl get pods -n app で RESTARTS カウント増加を確認
   影響範囲: TaskBoard APIサービス停止
             (全API Podが再起動を繰り返し、Serviceのエンドポイントから外れた)
   障害レベル: ■ 重大(サービス全断)

2. タイムライン
   14:30  resources.limits.memory変更を含むマニフェスト適用
   14:32  API Podの再起動を検知(kubectl get pods で RESTARTS 増加)
   14:35  kubectl describe pod で OOMKilled を確認、原因特定
   14:38  limits.memory を設計値(512Mi)に修正、kubectl apply 実行
   14:40  全API PodがRunning + Ready、ヘルスチェック正常。完全復旧

3. 原因分析
   直接原因: TaskBoard APIの resources.limits.memory が 64Mi に設定された
   根本原因: Payara Micro(JVMアプリケーション)のメモリ要件と
             K8s limits の関係に対する理解不足。
             JVMヒープ + Metaspace + ネイティブメモリの合計が
             64Mi を大幅に超過し、OOM Killer が発動した

4. 復旧対応
   暫定対処の内容: resources.limits.memory を設計値(512Mi)、
                   requests.memory を設計値(384Mi)に修正して再デプロイ
   暫定対処の確認結果: 全API PodがRunning、readinessProbe正常、
                       kubectl top pods でメモリ使用量が正常範囲(約300Mi)

5. 再発防止策
   恒久対策:
     (1) 詳細設計書のresources設計セクションに
         「JVMヒープ + Metaspace + ネイティブメモリ ≤ limits × 80%」
         の設計ルールを明記する
     (2) 運用設計書の監視項目に
         「メモリ使用量 > limits × 70% でアラート」を追加する
     (3) resources変更を含むマニフェスト適用前に
         kubectl diff で差分確認を必須とする
   実施期限: 2026-01-22
   担当者: インフラチーム

6. 教訓
   JVMアプリケーションのK8s resources設計では、
   アプリケーション固有のメモリ消費特性を理解した上で
   limitsを設定する必要がある。
   NginxやMySQLとは異なるメモリ消費パターンであり、
   コンポーネントごとの特性に応じた設計が不可欠。

============================================================

9.10.3 障害報告書を書く意義

障害報告書を書く目的は「犯人探し」ではありません。目的は3つです。

1つ目は、再発防止策の実行を確実にすること。障害のたびに「次は気をつけよう」と口頭で話すだけでは、同じ障害が繰り返されます。文書化して期限と担当者を明記することで、再発防止策が確実に実行されます。

2つ目は、チーム全体の知見にすること。障害対応した本人だけが知っている情報は、その人が不在のときに役立ちません。報告書として残すことで、次に同じ障害が発生したとき、チームの誰でも対応できます。

3つ目は、設計の改善サイクルを回すこと。障害報告書の再発防止策は、設計書の修正につながります。シナリオ2の報告書では「詳細設計書のresources設計セクションにルールを追記する」「運用設計書に監視項目を追加する」という対策を挙げました。障害は、設計を磨く機会でもあります。

9.11 この回のまとめ

9.11.1 成果物の確認 — 障害対応手順書 + 障害報告書テンプレート

本回の成果物は2つです。

障害対応手順書: 9.3節の障害対応フローと切り分けフローチャート、および6シナリオの対応手順をまとめたものです。チームに共有し、障害発生時の初動対応に使用します。

障害報告書テンプレート(記入例付き): 9.10節のテンプレートとOOMKilledの記入例です。障害対応後、このテンプレートに沿って報告書を作成し、再発防止策の実行を管理します。

9.11.2 6シナリオの振り返りと切り分けの体系化

6つの障害シナリオを一覧表で振り返ります。

シナリオ障害レイヤー検知の手がかり切り分けの鍵復旧方法
1. CrashLoopBackOffPod層kubectl get pods: ImagePullBackOffdescribe podのEvents正しいイメージタグに修正
2. OOMKilledPod層kubectl get pods: OOMKilled + RESTARTS増加describe podのLast State: OOMKilledlimits.memoryを設計値に修正
3. Node障害Node層kubectl get nodes: NotReady影響Podの-o wideでNode配置確認Node復旧(docker start
4. NW障害Network層kubectl get pods: READY 0/1(readiness失敗)テスト用Podの接続テスト + describe networkpolicyNetworkPolicyのラベルセレクタ修正
5. ストレージ障害Storage層kubectl get pods: Pendingdescribe podのEvents + get pvcStorageClass名修正 + StatefulSet再作成
6. 設定ミス設定層kubectl get pods: CrashLoopBackOffkubectl logsのNginxエラーメッセージConfigMap復元 + rollout restart

6つのシナリオはそれぞれ異なるレイヤーの障害ですが、すべて同じ障害対応フロー(検知→切り分け→調査→復旧→恒久対策→報告)で対処しました。このフローが身に染みたら、未知の障害に遭遇しても「まず何を見るか」を迷わなくなります。

各シナリオでは、これまでの実践編で作成した成果物が切り分けと復旧に活用されました。

実践編の成果物障害対応での活用
構成図(第1回)「どこが壊れたか」を構成図上で特定する
基本設計書(第2回)PDB設計、NetworkPolicy方針が切り分けの参照情報に
詳細設計書(第3回)Probeパラメータ、resources設計値が障害原因の分析に
通信制御マトリクス(第6回)NW障害時に「設計上は許可/遮断どちらか」を判断する基準
運用設計書(第7回)バックアップ設計がストレージ障害からのデータ復旧に
変更管理手順書(第8回)ConfigMap変更手順、Helmロールバックが復旧手段に

設計書は「作って終わり」ではなく、障害対応のときに最も価値を発揮します。

9.11.3 次回予告 — 本番への道、そしてシリーズの締めくくり

実践編最後の第10回では、kindの学習環境から本番環境への橋渡しをします。

kindで学んだこと——マニフェスト、kubectl操作、設計書の書き方、障害対応の手法——はマネージドK8s(EKS / AKS / GKE)でもそのまま活かせます。しかし、「そのまま使えるもの」と「変わるもの」があります。LoadBalancer、StorageClass、IAM連携、監視——kindと本番の差分を整理し、次のステップを明確にします。

本回の障害対応手順書も、マネージドK8s移行時に「何が変わるか」を差分リストで整理します。kindではNode障害をdocker stopでシミュレーションしましたが、本番ではクラウドプロバイダーのインフラ障害として発生します。対応の考え方は同じですが、具体的な手段は変わる。その違いを整理するのが次回です。

入門編から始まった旅も、いよいよ終着点が見えてきました。

AI活用 — エラーメッセージの分析・切り分け補助

本回のAI活用は、9.7.2節のNW障害シナリオに組み込みました。ここでは、AI活用のポイントを補足します。

障害対応でAIが最も役立つのは、「エラーメッセージの分析」と「切り分けの方向性の提示」です。kubectl describekubectl logsの出力をAIに貼り付けて「原因の候補を挙げてほしい」と依頼すると、一般的な原因のリストと対処法を素早く得られます。

ただし、AIの分析には重要な限界があります。

  • AIはあなたの環境を知らない: NetworkPolicyの設定状況、Probeのパラメータ、resources設計値——これらは設計書にしか記載されていません。AIの分析は「一般論として正しい」けれど、「あなたの環境でどの原因が最も可能性が高いか」は自分で判断する必要があります
  • AIは最近の変更を知らない: 「5分前にNetworkPolicyを変更した」という情報は、AIに明示的に伝えない限り考慮されません。障害対応では「最近何を変更したか」が最も重要な手がかりになることが多い
  • AIの回答を鵜呑みにしない: AIが「DBが落ちている可能性が高い」と言っても、kubectl get pods -n dbでMySQLが正常に稼働していれば、別の原因を疑うべきです

AIは「切り分けの出発点を素早く得るためのツール」として活用し、最終判断は自分の設計書と実際の環境から下す——これが障害対応におけるAI活用の正しい姿勢です。