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

Node障害・NetworkPolicy診断【CKA第16回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第16回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / kubeadm v1.35.5(2026-05-25 時点)。第2巻の最終回です。本回で CKA D5(Troubleshooting・30%)を完結させ、読者は正式に CKA 受験準備完了の状態となります。

広告

今ここマップ(第16回 / 全16回 / 第6部完結 ★完走)

今ここ: 第16回 / 全16回(第6部:トラブルシュート・完走)
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  100% ★完走

第1部(クラスタ構築):       ■■■■■ 5/5 回(完了)
第2部(ワークロード管理):   ■■■   3/3 回(完了)
第3部(ネットワーク):       ■■■   3/3 回(完了)
第4部(ストレージ):         ■     1/1 回(完了)
第5部(監視・運用):         ■■    2/2 回(完了)
第6部(トラブルシュート):   ■■    2/2 回(完了)★

第15回では kube-apiserver Static Pod 障害・etcd restore 失敗・Control Plane Node の kubelet 停止・kubeconfig 誤値の 4 シナリオを crictl / journalctl / etcdutl で診断・復旧しました。第16回は Workload Node 側(kubelet 停止による NotReady)・ネットワーク層(NetworkPolicy 過剰 deny)・アプリ層(CrashLoopBackOff・Pending)の診断ツール使い分けを実機で身につけ、CKA D5 Troubleshooting(30%)を完結させます。

第16回のキャッチコピー: 「Workload + Network + App 診断で D5 完結・第2巻完走宣言」

第16回終了時の達成状態:

  • Workload Node の kubelet 停止による NotReady を journalctl -u kubelet で診断し systemctl start で復旧できる
  • NetworkPolicy 過剰 deny を kubectl describe networkpolicy と busybox 一時 Pod からの wget 疎通テストで切り分けられる
  • CrashLoopBackOff を kubectl logs --previouskubectl describe pod で診断できる
  • Pending Pod の原因を kubectl describe pod/replicaset/resourcequota で特定し kubectl patch で復旧できる
  • kubectl debug による ephemeral container 注入の概念を理解している
  • CKA 5 ドメイン(D1〜D5)を全 16 回で網羅し、第2巻完走マイルストーンを達成した

第16回のスコープと最終目標

本セクションでは第16回で扱うことと扱わないことを明確にし、4 演習シナリオの全体像を提示します。

第16回で「やること」と「やらないこと」

やることやらないこと
Workload Node kubelet 停止 → 復旧(NotReady)Control Plane コンポーネント障害(第15回で完結)
NetworkPolicy 過剰 deny の切り分けIngress / Gateway API のトラブルシュート(第11回で扱い済み)
CrashLoopBackOff 診断(環境変数の誤設定起因)OOM Kill の詳細診断(次のシリーズの CKS 範囲)
Pending Pod 診断(ResourceQuota 上限超過)etcd トラブルシュート(第15回で完結)

4 演習シナリオの概観

各シナリオは「障害投入 → 診断ツール → 復旧」の三段構成を踏みます。CKA 試験の D5 設問もこの流れで設計されているため、本回の演習をなぞれば試験本番でも同じ手順で対応できます。

#演習障害投入診断ツール復旧
NotReady Workload Node 復旧systemctl stop kubeletjournalctl -u kubelet / kubectl describe nodesystemctl start kubelet
NetworkPolicy 過剰 deny の切り分けdeny-all NetworkPolicy 適用kubectl describe networkpolicy / busybox Pod + wgetNetworkPolicy 削除 または修正
CrashLoopBackOff 診断環境変数に不正な JVM オプションkubectl logs -p / kubectl describe podkubectl set env で環境変数を修正
Pending 原因診断ResourceQuota pods 上限超過kubectl describe pod/replicaset + describe resourcequotakubectl patch で上限引き上げ

第16回終了時点の各 VM 状態

VM第16回終了時の状態
k8s-cp-01〜03変更なし(Control Plane 3 ノード稼働継続)
k8s-wl-01変更なし
k8s-wl-02演習① で kubelet stop / start を実施し復旧済み
k8s-ops演習④の quota-demo Namespace は削除済み・fanclub Namespace は通常稼働

演習②と③は fanclub Namespace に対して破壊的操作を行うため、各演習の最後に必ず復旧手順を完走させてから次へ進んでください。

トラブルシュートの鉄則 3 ステップと診断ツールマトリクス

第15回で確認したトラブルシュートの鉄則 3 ステップを再掲します。第16回でも全演習で同じ流れを踏みます。

  1. 観察: kubectl get で異常コンポーネントを特定する
  2. 診断: kubectl describe / kubectl logs / journalctl でエラーメッセージを読む
  3. 復旧: 最小変更で直し、kubectl get で正常復帰を確認する

第15回の重点は Control Plane Node 側のコンポーネント(kube-apiserver Static Pod・etcd Static Pod・Control Plane Node の kubelet・kubeconfig)でした。第16回は Workload Node 側・ネットワーク層・アプリ層に重点を移します。障害レイヤーごとに使う診断ツールが異なるため、下記マトリクスを頭に入れてから演習に入ります。

障害レイヤー別 診断ツールマトリクス

障害レイヤー主ツールサブツール
Workload Nodesystemctl status kubelet / journalctl -u kubeletkubectl describe node
NetworkPolicykubectl describe networkpolicybusybox Pod + wget
アプリクラッシュkubectl logs --previouskubectl describe pod / kubectl debug
スケジューリングkubectl describe pod Eventskubectl describe replicaset / describe resourcequota

マトリクスの「主ツール」は最初に手を伸ばすコマンド、「サブツール」は主ツールで不足する情報を補うコマンドです。CKA 試験では制限時間が 2 時間しかないため、レイヤーを判定したら迷わず主ツールへ進める判断力が重要になります。

やってみよう①: NotReady Workload Node の復旧

Workload Node の kubelet を意図的に停止し、kubectl get nodesNotReady に遷移する過程を観察します。journalctl -u kubelet で停止原因を特定し、systemctl start kubelet で復旧して Pod の再分散まで確認します。

演習セットアップ

  • 対象 Namespace: kube-system(ノード確認)と fanclub(影響確認)
  • 対象ノード: k8s-wl-02(Workload Node の片系)
  • k8s-wl-01 は稼働継続するため、fanclub-api の Pod は k8s-wl-01 へ自動退避する想定

ステップ1: 障害投入

実行コマンド:k8s-wl-02 にログインし、kubelet を停止します。

$ ssh developer@k8s-wl-02
$ sudo systemctl stop kubelet

実行コマンド:k8s-ops に戻り、約 40 秒待ってから Node の状態を確認します。

$ kubectl get nodes

実行結果:

NAME         STATUS     ROLES           AGE   VERSION
k8s-cp-01    Ready      control-plane   42d   v1.35.5
k8s-cp-02    Ready      control-plane   42d   v1.35.5
k8s-cp-03    Ready      control-plane   42d   v1.35.5
k8s-wl-01    Ready      <none>          42d   v1.35.5
k8s-wl-02    NotReady   <none>          42d   v1.35.5

k8s-wl-02 のみ NotReady に遷移しました。Node Controller の node-monitor-grace-period(デフォルト 40 秒)が経過すると、kubelet からのハートビート停止により Node Controller が Ready Condition を False に書き換えます。

実行コマンド:fanclub Namespace の Pod 配置を確認します。

$ kubectl get pods -n fanclub -o wide

実行結果:

NAME                                  READY   STATUS        RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
fanclub-backend-7f8d9b4c6c-2pq8l      1/1     Running       0          12d   10.244.1.45   k8s-wl-01   <none>           <none>
fanclub-backend-7f8d9b4c6c-zk9xt      1/1     Terminating   0          12d   10.244.2.51   k8s-wl-02   <none>           <none>
fanclub-frontend-6c7b8d9f5d-4r6sw     1/1     Running       0          12d   10.244.1.46   k8s-wl-01   <none>           <none>
fanclub-db-0                    1/1     Running       0          12d   10.244.1.47   k8s-wl-01   <none>           <none>

k8s-wl-02 上の fanclub-backend-7f8d9b4c6c-zk9xtTerminating に入っています。pod-eviction-timeout(デフォルト 5 分)経過後、Controller Manager がこの Pod を強制削除し、ReplicaSet が k8s-wl-01 上で代替 Pod を生成します。

ステップ2: 診断

実行コマンド:k8s-ops から Node の Conditions と Events を確認します。

$ kubectl describe node k8s-wl-02

実行結果(抜粋):

Conditions:
  Type             Status    LastHeartbeatTime                 Reason              Message
  ----             ------    -----------------                 ------              -------
  MemoryPressure   Unknown   Sun, 25 May 2026 09:12:14 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  DiskPressure     Unknown   Sun, 25 May 2026 09:12:14 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  PIDPressure      Unknown   Sun, 25 May 2026 09:12:14 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  Ready            Unknown   Sun, 25 May 2026 09:12:14 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
Events:
  Type     Reason             Age    From                   Message
  ----     ------             ----   ----                   -------
  Normal   NodeNotReady       3m21s  node-controller        Node k8s-wl-02 status is now: NodeNotReady

Conditions の Reason がすべて NodeStatusUnknown、Message が Kubelet stopped posting node status. となっています。Node Controller から見て kubelet との通信が途絶えた状態です。次に k8s-wl-02 側で kubelet 本体の状態を確認します。

実行コマンド:k8s-wl-02 にログインし、kubelet サービスの状態を確認します。

$ ssh developer@k8s-wl-02
$ sudo systemctl status kubelet --no-pager

実行結果:

● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: inactive (dead) since Sun 2026-05-25 09:11:47 JST; 3min 50s ago
   Main PID: 1247 (code=exited, status=0/SUCCESS)

Active: inactive (dead) で kubelet が停止していることが確定しました。続いて停止原因を journalctl で確認します。

実行コマンド:直近 50 行のログを確認します。

$ sudo journalctl -u kubelet -n 50 --no-pager

実行結果(抜粋):

May 25 09:11:47 k8s-wl-02 systemd[1]: Stopping kubelet: The Kubernetes Node Agent...
May 25 09:11:47 k8s-wl-02 kubelet[1247]: I0525 09:11:47.823412    1247 server.go:1234] "Shutting down kubelet"
May 25 09:11:47 k8s-wl-02 systemd[1]: kubelet.service: Deactivated successfully.
May 25 09:11:47 k8s-wl-02 systemd[1]: Stopped kubelet: The Kubernetes Node Agent.

Stopping kubelet から Deactivated successfully へ遷移しており、systemd 経由で正常に停止されたことが確認できます。手動 systemctl stop 起因のため、設定ファイル不整合や CNI 障害といった本体起因の障害はありません。

ステップ3: 復旧

実行コマンド:k8s-wl-02 で kubelet を起動します。

$ sudo systemctl start kubelet
$ sudo systemctl status kubelet --no-pager

実行結果:

● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Sun 2026-05-25 09:16:02 JST; 4s ago
   Main PID: 4382 (kubelet)
      Tasks: 18 (limit: 38249)

実行コマンド:k8s-ops から Node 状態を再確認します。

$ kubectl get nodes

実行結果:

NAME         STATUS   ROLES           AGE   VERSION
k8s-cp-01    Ready    control-plane   42d   v1.35.5
k8s-cp-02    Ready    control-plane   42d   v1.35.5
k8s-cp-03    Ready    control-plane   42d   v1.35.5
k8s-wl-01    Ready    <none>          42d   v1.35.5
k8s-wl-02    Ready    <none>          42d   v1.35.5

k8s-wl-02 が Ready に復帰しました。kubelet 起動後、最初のハートビートが届くまで 10 秒〜30 秒かかります。続いて fanclub の Pod 分散を確認します。

実行コマンド:

$ kubectl get pods -n fanclub -o wide

実行結果:

NAME                                  READY   STATUS    RESTARTS   AGE   IP            NODE
fanclub-backend-7f8d9b4c6c-2pq8l      1/1     Running   0          25m   10.244.1.45   k8s-wl-01
fanclub-backend-7f8d9b4c6c-x4tv7      1/1     Running   0          6m    10.244.2.62   k8s-wl-02
fanclub-frontend-6c7b8d9f5d-4r6sw     1/1     Running   0          25m   10.244.1.46   k8s-wl-01
fanclub-db-0                    1/1     Running   0          25m   10.244.1.47   k8s-wl-01

新規 Pod fanclub-backend-7f8d9b4c6c-x4tv7 が k8s-wl-02 で起動しました。Scheduler が ReplicaSet の replica 2 を満たすために、Ready になった k8s-wl-02 へ Pod を再配置しています。

振り返り: NotReady の主な原因と判別ポイント

journalctl 出力原因対処
Deactivated successfullykubelet 手動停止systemctl start kubelet
failed to load Kubelet config filekubelet 設定ファイル破損設定ファイルの構文修復
Main process exited, code=killed, status=9/KILLOOM Kill による kubelet 強制終了メモリ確保・systemd ユニット制限見直し
CNI plugin not initializedCNI Pod(calico-node 等)未起動CNI Pod の状態確認・再適用
Condition DiskPressure=Trueディスク残量不足不要ログ・イメージ削除

CKA 試験では failed to load Kubelet config file パターンと CNI plugin not initialized パターンが頻出です。前者は /var/lib/kubelet/config.yaml の YAML 構文ミス、後者は CNI Pod の kubectl get pods -n kube-system 確認が初動になります。

現場ヒヤリハット①: drain なしの kubelet 停止

本番環境で kubectl cordonkubectl drain を経由せずに kubelet を停止すると、トラフィックを受けていた Pod が突然 Terminating になります。Service の EndpointSlice からの除外より先に Pod 自体が停止するため、クライアント側で接続エラーが発生します。メンテナンス前は kubectl drain k8s-wl-02 --ignore-daemonsets で Pod を退避してから kubelet を止め、メンテナンス完了後に kubectl uncordon k8s-wl-02 で受け入れ再開する手順を厳守します。第6回で扱った drain/cordon の手順がそのまま生きる場面です。

やってみよう②: NetworkPolicy 過剰 deny の切り分け

過剰な deny ポリシーで fanclub-backend への通信を遮断し、ブラウザから 502 エラーが返ることを確認します。kubectl describe networkpolicy で適用ルールを読み、busybox 一時 Pod の wget 疎通テストで切り分けてから NetworkPolicy を削除して復旧します。

演習セットアップ

  • 対象 Namespace: fanclub
  • 対象通信: fanclub-frontend Pod → fanclub-backend Service(port 8080)
  • 第10回で扱った NetworkPolicy のラベルセレクタ理解を前提とします

ステップ1: 障害投入

backend Pod に対して全 Ingress を拒否する NetworkPolicy を投入します。以下のマニフェスト全量を deny-all-backend.yaml として保存します。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-backend
  namespace: fanclub
spec:
  podSelector:
    matchLabels:
      app: fanclub-backend
  policyTypes:
  - Ingress

policyTypesIngress を含めつつ ingress ルールを 1 つも書かない場合、デフォルト deny になります。ingress: [] と明示的に書かなくても同じ結果です。

実行コマンド:

$ kubectl apply -f deny-all-backend.yaml

実行結果:

networkpolicy.networking.k8s.io/deny-all-backend created

ブラウザで https://fanclub.local にアクセスすると、画面に 502 Bad Gateway もしくは接続タイムアウトのエラーが表示されます。Traefik が backend Service に転送しようとしても、CNI 層(Calico)で SYN パケットが落とされて応答が返らない状態です。

ステップ2: 診断

実行コマンド:fanclub Namespace の NetworkPolicy 一覧を確認します。

$ kubectl get networkpolicy -n fanclub

実行結果:

NAME               POD-SELECTOR          AGE
deny-all-backend   app=fanclub-backend   1m

backend Pod を対象に持つ NetworkPolicy が 1 つ存在します。詳細を確認します。

実行コマンド:

$ kubectl describe networkpolicy deny-all-backend -n fanclub

実行結果:

Name:         deny-all-backend
Namespace:    fanclub
Created on:   2026-05-25 09:22:14 +0900 JST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     app=fanclub-backend
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Not affecting egress traffic
  Policy Types: Ingress

Allowing ingress traffic セクションが <none> となっており、Selected pods are isolated for ingress connectivity と明記されています。これは backend Pod への全 Ingress が遮断されている状態です。続いて frontend Pod から backend Service への疎通を確認します。

frontend Pod の nginx イメージには curlwget も含まれていないため(疎通テストツールが無いのは本番でもよくあります)、busybox の一時 Pod を作成して、そこから backend Service へ疎通テストします。

$ kubectl run nettest -n fanclub --image=192.168.1.123:5000/busybox:1.36 --restart=Never --command -- sleep 600
$ kubectl exec -n fanclub nettest -- wget -q -T 5 -O /dev/null http://fanclub-backend:8080/health/live; echo "exit=$?"

実行結果:

wget: download timed out
exit=1

wget が -T 5(5 秒タイムアウト)で download timed out となり exit=1 を返しました。SYN に応答が返らない状態で、NetworkPolicy 起因の遮断と確定します。

ステップ3: 復旧

本演習では NetworkPolicy 自体を削除する経路で復旧します。本番運用では「frontend からの 8080 を許可する明示的な ingress ルールを追加する」修正が望ましいですが、まずは最小変更で復旧を確認します。

実行コマンド:

$ kubectl delete networkpolicy deny-all-backend -n fanclub

実行結果:

networkpolicy.networking.k8s.io "deny-all-backend" deleted

実行コマンド:再度 busybox 一時 Pod から backend への疎通を確認します。

$ kubectl exec -n fanclub nettest -- wget -S -q -O /dev/null -T 5 http://fanclub-backend:8080/health/live 2>&1 | grep HTTP/

実行結果:

  HTTP/1.1 200 OK

HTTP 200 が返り、疎通が復旧しました。ブラウザで https://fanclub.local を再読込すると通常画面が表示されます。確認が済んだら一時 Pod を削除します(kubectl delete pod nettest -n fanclub)。

振り返り: Calico の NetworkPolicy と GlobalNetworkPolicy

本シリーズの CNI は Calico を採用しています。Calico は標準の Kubernetes NetworkPolicy(namespace スコープ)に加えて、Calico 独自の NetworkPolicy(CRD・namespace スコープ)と GlobalNetworkPolicy(CRD・クラスタスコープ)を提供します。kubectl describe networkpolicy は標準 Kubernetes NetworkPolicy のみを表示します。Calico 独自リソースは kubectl get networkpolicies.crd.projectcalico.orgkubectl get globalnetworkpolicies.crd.projectcalico.org で確認が必要です。CKA 試験範囲は標準 Kubernetes NetworkPolicy のみのため、本演習も標準リソースで完結しています。

現場ヒヤリハット②: deny-all を本番に直接適用

NetworkPolicy のベストプラクティスとして「最初に deny-all を適用してから許可ルールを追加する」という設計があります。ただしこの手順を staging 環境を経由せず本番に直接適用すると、既存通信がすべて遮断されてユーザ向けサービスが停止します。本番での NetworkPolicy 変更は staging で同一マニフェストを適用して E2E 通信テスト、本番では Namespace ごとに段階的に有効化、必ず元に戻せる kubectl delete または kubectl apply -f <old>.yaml の手順をランブックに準備、という 3 点を必ず確認してから実施します。

やってみよう③: CrashLoopBackOff 診断(DB 接続文字列誤設定)

環境変数 JAVA_TOOL_OPTIONS に不正な JVM オプションを注入して fanclub-backend を起動失敗させ、CrashLoopBackOff の発生から kubectl logs --previous による原因特定、環境変数の修正での復旧までを実機で確認します。JVM は起動時にオプションを検証するため、不正値があると即座にプロセスが落ちて確実に CrashLoopBackOff を再現できます。

演習セットアップ

  • 対象 Namespace: fanclub
  • 対象リソース: Deployment fanclub-backend(環境変数は Pod テンプレートに直接定義)
  • backend は Payara Micro 上の Jakarta EE アプリで、JVM 起動時に JAVA_TOOL_OPTIONS の内容を検証します

ステップ1: 障害投入

実行コマンド:現在の環境変数を確認します。

$ kubectl set env deployment/fanclub-backend --list -n fanclub | grep -vE '^#|^$'

実行結果(抜粋):

DB_HOST=fanclub-db
DB_PORT=5432
DB_NAME=fanclub

実行コマンド:不正な JVM オプションを環境変数 JAVA_TOOL_OPTIONS に注入します。kubectl set env は Deployment テンプレートを書き換えるため、自動でローリング再起動が走ります(rollout restart は不要です)。

$ kubectl set env deployment/fanclub-backend JAVA_TOOL_OPTIONS="-XX:ThisFlagDoesNotExist" -n fanclub

実行結果:

deployment.apps/fanclub-backend env updated

約 30 秒〜2 分待ってから Pod の状態を確認します。

実行コマンド:

$ kubectl get pods -n fanclub -l app=fanclub-backend

実行結果:

NAME                               READY   STATUS             RESTARTS      AGE
fanclub-backend-6d5b8c4f7d-h9pmt   0/1     CrashLoopBackOff   3 (15s ago)   2m
fanclub-backend-6d5b8c4f7d-vq2dx   0/1     CrashLoopBackOff   3 (22s ago)   2m

両 Pod が CrashLoopBackOff になりました。RESTARTS 列の値が増えており、kubelet がコンテナを繰り返し再起動していることが分かります。

ステップ2: 診断

実行コマンド:Pod の Events を確認します。

$ POD=$(kubectl get pod -n fanclub -l app=fanclub-backend -o jsonpath='{.items[0].metadata.name}')
$ kubectl describe pod $POD -n fanclub

実行結果(Events 抜粋):

Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  2m45s                default-scheduler  Successfully assigned fanclub/fanclub-backend-6d5b8c4f7d-h9pmt to k8s-wl-01
  Normal   Pulled     2m44s                kubelet            Container image "192.168.1.123:5000/fanclub-backend:0.2.0" already present on machine
  Normal   Created    2m44s (x4 over 2m44s)kubelet            Created container: backend
  Normal   Started    2m43s (x4 over 2m43s)kubelet            Started container backend
  Warning  BackOff    25s   (x10 over 2m20s)kubelet            Back-off restarting failed container backend in pod fanclub-backend-6d5b8c4f7d-h9pmt_fanclub

Back-off restarting failed container が CrashLoopBackOff の典型 Event です。次に現在コンテナと直前コンテナのログを確認します。

実行コマンド:現在稼働中コンテナのログ確認です(クラッシュ直後で空の場合あり)。

$ kubectl logs $POD -n fanclub --tail=20

実行結果(コンテナ再生成中は取得できないことがあります):

Error from server (BadRequest): container "backend" in pod "fanclub-backend-6d5b8c4f7d-h9pmt" is waiting to start: ContainerCreating

現在のコンテナはまだ起動しておらず、ログが取得できません。クラッシュ原因を確実に掴むには直前コンテナのログを参照します。

実行コマンド:

$ kubectl logs $POD -n fanclub --previous --tail=30

実行結果:

Unrecognized VM option 'ThisFlagDoesNotExist'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

Unrecognized VM option 'ThisFlagDoesNotExist' が決定的な手がかりです。JVM が不正な起動オプションを検出し、Java 仮想マシンの生成に失敗してコンテナが exit code 1 で終了しています。環境変数の値を確認します。

実行コマンド:

$ kubectl set env deployment/fanclub-backend --list -n fanclub | grep JAVA_TOOL_OPTIONS

実行結果:

JAVA_TOOL_OPTIONS=-XX:ThisFlagDoesNotExist

環境変数に不正な JVM オプションが入っていることが確定しました。

補足: kubectl debug による ephemeral container 注入

本演習の backend イメージは shell を含むため kubectl exec -- sh でも調査可能ですが、distroless ベースイメージや scratch ベースイメージなど shell を含まない Pod でも CKA D5 範囲では調査が求められます。kubectl debug による ephemeral container 注入が答えです。

実行コマンド:元 Pod に ephemeral container として busybox を注入する例です。

$ kubectl debug $POD -n fanclub -it --image=192.168.1.123:5000/busybox:1.36 --target=backend -- sh

--target=backend でターゲットコンテナのプロセス名前空間と pid 名前空間を共有し、busybox の nslookupnc で接続先確認ができます。元コンテナを停止せずに調査できる点が kubectl exec との違いです。Pod のコピーを作って調査する場合は kubectl debug $POD -it --copy-to=$POD-debug --container=backend -- sh を使います。

ステップ3: 復旧

実行コマンド:不正な環境変数を削除します。kubectl set env の末尾 -(ハイフン)で変数を削除でき、Deployment テンプレートが変わるため自動でローリング再起動が走ります。

$ kubectl set env deployment/fanclub-backend JAVA_TOOL_OPTIONS- -n fanclub
$ kubectl rollout status deployment fanclub-backend -n fanclub --timeout=3m

実行結果:

deployment.apps/fanclub-backend env updated
Waiting for deployment "fanclub-backend" rollout to finish: 1 of 2 updated replicas are available...
deployment "fanclub-backend" successfully rolled out

実行コマンド:

$ kubectl get pods -n fanclub -l app=fanclub-backend

実行結果:

NAME                               READY   STATUS    RESTARTS   AGE
fanclub-backend-5c8a7b6e9d-kj4mn   1/1     Running   0          1m
fanclub-backend-5c8a7b6e9d-tx7vp   1/1     Running   0          1m

両 Pod が Running に復帰し、RESTARTS も 0 です。ブラウザで https://fanclub.local から CRUD 操作が通常通り動作することを確認します。

振り返り: kubectl logs と –previous の使い分け

コマンド取得対象使うタイミング
kubectl logs <pod>現在稼働中コンテナのログRunning 状態・起動直後の調査
kubectl logs <pod> --previous-p直前にクラッシュしたコンテナのログCrashLoopBackOff の原因特定
kubectl logs <pod> -c <container>複数コンテナ Pod の特定コンテナInit Container 障害・サイドカー障害
kubectl logs <pod> --since=10m指定時間内のログのみ長時間稼働 Pod の直近事象調査

現場ヒヤリハット③: ConfigMap 変更が反映されない

ConfigMap を環境変数として参照する場合、変更は Pod 再起動時にしか反映されません。Volume マウントの場合は kubelet のキャッシュ TTL(デフォルト 1 分前後)で自動反映されますが、env でマウントしている場合は kubectl rollout restart deployment が必須です。本番運用では ConfigMap も Git 管理し ArgoCD で同期する設計(第14回で扱った GitOps)にしておくと、意図しない変更や反映漏れを Pull Request レビューで検出できます。

やってみよう④: Pending Pod 原因診断(ResourceQuota 上限超過)

新規 Namespace quota-demopods: "3" の ResourceQuota を設定し、replicas: 5 の Deployment で上限超過を発生させます。kubectl describe pod/replicaset/resourcequota による診断と kubectl patch での上限引き上げを学びます。

演習セットアップ

  • 対象 Namespace: quota-demo(演習専用・終了後に削除)
  • ResourceQuota: pods: "3" など低い上限を設定
  • Deployment: replicas: 5 で上限超過を発生

実行コマンド:演習用 Namespace を作成します。

$ kubectl create namespace quota-demo

実行結果:

namespace/quota-demo created

ステップ1: 障害投入

ResourceQuota マニフェスト全量を demo-quota.yaml として保存します。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: demo-quota
  namespace: quota-demo
spec:
  hard:
    pods: "3"
    requests.cpu: "1"
    requests.memory: "512Mi"
    limits.cpu: "2"
    limits.memory: "1Gi"

Deployment マニフェスト全量を quota-test.yaml として保存します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: quota-test
  namespace: quota-demo
spec:
  replicas: 5
  selector:
    matchLabels:
      app: quota-test
  template:
    metadata:
      labels:
        app: quota-test
    spec:
      containers:
      - name: nginx
        image: 192.168.1.123:5000/nginx:1.27
        resources:
          requests:
            cpu: "100m"
            memory: "64Mi"
          limits:
            cpu: "200m"
            memory: "128Mi"

実行コマンド:両マニフェストを適用します。

$ kubectl apply -f demo-quota.yaml
$ kubectl apply -f quota-test.yaml

実行結果:

resourcequota/demo-quota created
deployment.apps/quota-test created

実行コマンド:

$ kubectl get pods -n quota-demo

実行結果:

NAME                          READY   STATUS    RESTARTS   AGE
quota-test-7d8c6b5f4d-2hkqr   1/1     Running   0          45s
quota-test-7d8c6b5f4d-9mt8s   1/1     Running   0          45s
quota-test-7d8c6b5f4d-xv7pl   1/1     Running   0          45s

Deployment は replicas 5 を要求していますが、Running の Pod は 3 つしか存在しません。残り 2 つは ReplicaSet によって作成を試みられても、ResourceQuota が拒否するため作成自体が失敗しています。Pending ではなく Pod が存在しない状態となる点が重要です。

ステップ2: 診断

実行コマンド:Deployment のロールアウト状況と ReplicaSet の Events を確認します。

$ kubectl get deployment quota-test -n quota-demo

実行結果:

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
quota-test   3/5     5            3           1m

READY 3/5 でロールアウトが完走していません。ReplicaSet を確認します。

実行コマンド:

$ kubectl describe replicaset -n quota-demo

実行結果(Events 抜粋):

Events:
  Type     Reason            Age                From                   Message
  ----     ------            ----               ----                   -------
  Normal   SuccessfulCreate  1m                 replicaset-controller  Created pod: quota-test-7d8c6b5f4d-2hkqr
  Normal   SuccessfulCreate  1m                 replicaset-controller  Created pod: quota-test-7d8c6b5f4d-9mt8s
  Normal   SuccessfulCreate  1m                 replicaset-controller  Created pod: quota-test-7d8c6b5f4d-xv7pl
  Warning  FailedCreate      1m (x4 over 1m)    replicaset-controller  Error creating: pods "quota-test-7d8c6b5f4d-" is forbidden: exceeded quota: demo-quota, requested: pods=1, used: pods=3, limited: pods=3

exceeded quota: demo-quota, requested: pods=1, used: pods=3, limited: pods=3 が決定的な手がかりです。ResourceQuota の pods 上限超過で ReplicaSet Controller が Pod を作成できない状態です。ResourceQuota の現状を確認します。

実行コマンド:

$ kubectl describe resourcequota demo-quota -n quota-demo

実行結果:

Name:            demo-quota
Namespace:       quota-demo
Resource         Used   Hard
--------         ----   ----
limits.cpu       600m   2
limits.memory    384Mi  1Gi
pods             3      3
requests.cpu     300m   1
requests.memory  192Mi  512Mi

pods 行の Used 3 / Hard 3 で完全に使い切っています。これが残り 2 Pod 作成失敗の根本原因です。

ステップ3: 復旧

実行コマンド:ResourceQuota の pods 上限を 10 に引き上げます。

$ kubectl patch resourcequota demo-quota -n quota-demo --type merge -p '{"spec":{"hard":{"pods":"10"}}}'

実行結果:

resourcequota/demo-quota patched

実行コマンド:Pod 作成が進行することを確認します。

$ kubectl get pods -n quota-demo

実行結果:

NAME                          READY   STATUS    RESTARTS   AGE
quota-test-7d8c6b5f4d-2hkqr   1/1     Running   0          3m
quota-test-7d8c6b5f4d-5p9rd   1/1     Running   0          18s
quota-test-7d8c6b5f4d-9mt8s   1/1     Running   0          3m
quota-test-7d8c6b5f4d-jw4qb   1/1     Running   0          18s
quota-test-7d8c6b5f4d-xv7pl   1/1     Running   0          3m

5 Pod すべて Running となり、ReplicaSet の宣言と実態が一致しました。演習後のクリーンアップに移ります。

実行コマンド:

$ kubectl delete namespace quota-demo

実行結果:

namespace "quota-demo" deleted

振り返り: Pending Pod の原因別チェックポイント

kubectl describe pod / replicaset Events原因対処
exceeded quota: ... podsResourceQuota の pods 上限超過kubectl patch resourcequota / 別 Namespace へ分散
exceeded quota: ... requests.cpuResourceQuota の CPU request 上限超過quota 上限引き上げ または requests 削減
Insufficient cpu / Insufficient memoryノードのリソース残量不足ノード追加 または requests 削減
0/5 nodes are available: ... didn't match node selectornodeSelector / affinity 不一致nodeSelector ラベル修正
persistentvolumeclaim "X" not foundPVC 未作成 または未バインドPVC 作成 / StorageClass 確認
X node(s) had untolerated tainttaint / toleration 不一致toleration 追加 または taint 解除

本演習ではあえて「Pod は存在せず Quota で弾かれている」ケースを扱いました。Pending Pod が見える場合(Insufficient cpu や taint 不一致)はノード側の状態を、Pod 自体が作成されない場合は ReplicaSet と ResourceQuota の Events を見る、という切り分けが鉄則です。

現場ヒヤリハット④: ResourceQuota だけ設定して LimitRange 未設定

ResourceQuota で requests.cpu / requests.memory を設定した Namespace に、resources.requests / resources.limits を持たない Pod を作成しようとすると must specify limits エラーで作成自体が拒否されます。第8回で扱った LimitRange を併用して requests / limits のデフォルト値を設定しておくと、開発者が requests を書き忘れた場合でも自動で補完されて Quota 違反を防げます。本番 Namespace では ResourceQuota と LimitRange は常にセットで設計します。

完走マイルストーン達成チェックリスト

第2巻 16 回を完走した今、curriculum.md の第16回 完走基準が満たされているか自己点検します。下記すべてに ✓ が付けば、CKA 受験準備完了です。

【第2巻 完走マイルストーン達成確認チェックリスト】

□ kubeadm HA クラスタ(Control Plane ×3 + Workload Node ×2)が自力構築できた
□ etcdctl による etcd backup / restore を自力実施できた
□ kubeadm upgrade plan / apply によるクラスタアップグレードができた
□ fanclub-api が Longhorn + ArgoCD GitOps + Prometheus 監視付きで稼働している
□ Velero でクラスタバックアップが取れている
□ Control Plane 障害・Workload Node 障害・NetworkPolicy 問題・CrashLoopBackOff を診断できた
□ CKA 5 ドメインを全回で体験した

各チェック項目と対応回

チェック項目対応回CKA ドメイン
kubeadm HA クラスタ構築第3〜4回D1(25%)
etcd backup / restore第5回D1(25%)
kubeadm upgrade第6回D1(25%)
Pod スケジューリング / HPA / Quota第7〜8回D2(15%)
Service / NetworkPolicy / Gateway API第9〜11回D3(20%)
Longhorn ストレージ第12回D4(10%)
Prometheus 監視第13回D5(30%)
ArgoCD GitOps + Velero backup第14回D1(25%)
Control Plane トラブルシュート第15回D5(30%)
Workload Node + App トラブルシュート第16回D5(30%)

★ 第2巻完走宣言(CKA 受験準備完了)

★ 第2巻完走宣言

第2巻全 16 回を完走した読者は、kubeadm HA クラスタを自力構築し、etcd backup / restore、kubeadm upgrade、Longhorn ストレージ、Prometheus 監視、ArgoCD GitOps、Velero バックアップ、Control Plane / Workload Node トラブルシュートを実機で体験しました。CKA 5 ドメイン(D1〜D5)を全回で網羅した今、あなたは CKA 受験準備完了 です。

CKA 受験に向けた最終ステップ

  1. Killer.sh 模擬試験 1 周目: 制限時間 2 時間・15〜20 問程度(公式公開仕様の範囲・本番 CKA の問題数も同水準)を通しで受験し、現状スコアを把握する。CKA 試験申し込み時に Killer.sh が 2 回分付属する
  2. 弱点回の復習: 模擬試験のスコアが低かったドメインに対応する回(D1 なら第3〜6回・D5 なら第15〜16回)を再読し、実機で再演習する
  3. Killer.sh 模擬試験 2 周目: 復習後に 2 周目を受験し 66% 以上を確認する。本番試験の合格ラインも 66% である
  4. 試験予約: training.linuxfoundation.org で CKA 試験を予約し、事前に PSI ブリッジ接続テストを実施する
  5. 試験当日: Ubuntu ベースの試験端末 + kubectl + kubernetes.io ドキュメント参照可。ブラウザのブックマーク登録は試験前に済ませる

CKA ドメイン別カバー状況

ドメイン配点カバー回
D1: Cluster Architecture, Installation & Configuration25%第1〜6回・第14回
D2: Workloads & Scheduling15%第7〜8回
D3: Services & Networking20%第9〜11回
D4: Storage10%第12回
D5: Troubleshooting30%第13〜16回(← 今回完結)

次のシリーズへの橋渡しと Kubestronaut 5 冠

第2巻では本番運用に必要なクラスタ構築・運用・トラブルシュートを扱いましたが、Kubernetes 本番運用にはまだ習得すべき領域が残っています。下記は次のシリーズ(第3巻・CKS + SRE 編)で扱うトピックです(言及のみ・本記事からのリンクはありません)。

  • OPA Gatekeeper v4 によるポリシー管理
  • Falco v0.43.0 によるランタイムセキュリティ
  • Trivy によるイメージスキャン
  • SealedSecrets / ExternalSecrets Operator による Secret 管理
  • AppArmor / seccomp / SELinux によるコンテナ OS ハードニング
  • kube-bench による CIS ベンチマーク監査
  • SLO / SLA / Error Budget 設計と Runbook / Incident Response 運用
  • CKS(Certified Kubernetes Security Specialist)受験準備

Kubestronaut 5 冠達成パス

三部作完走 + KCNA + KCSA の自習で Kubestronaut 5 冠 を達成できます。Linux Foundation から Kubestronaut として認定され、ロゴ / ジャケット / LinkedIn バッジが授与されます。

[第1巻完走(19 回)] → CKAD 受験準備完了
         ↓
[第2巻完走(16 回)] → CKA 受験準備完了 ★ ← 今ここ
         ↓
[第3巻完走(13 回)] → CKS 受験準備完了
         ↓
[KCNA 自習(多肢選択・約 1 ヶ月)] → KCNA 取得
         ↓
[KCSA 自習(多肢選択・約 1 ヶ月)] → KCSA 取得
         ↓
★ Kubestronaut 5 冠達成(CKAD + CKA + CKS + KCNA + KCSA)
   Linux Foundation より Kubestronaut 認定・ジャケット授与

CKAD + CKA + CKS は実技試験のため本三部作で網羅します。KCNA と KCSA は多肢選択式で、各 1 ヶ月程度の自習で取得可能です。三部作の知識基盤があれば KCNA / KCSA の出題範囲(Kubernetes 全般概念とセキュリティ全般概念)はカバーできます。

第16回まとめと現場ヒヤリハット集

第16回で学んだこと

  • NotReady Workload Node を journalctl -u kubelet で診断し systemctl start kubelet で復旧する流れ
  • NetworkPolicy 過剰 deny を kubectl describe networkpolicy と busybox 一時 Pod の wget の組み合わせで切り分ける方法
  • CrashLoopBackOff の決定的な手がかりは kubectl logs --previous で得られる直前コンテナのスタックトレース
  • Pending Pod 診断は kubectl describe pod / replicaset / resourcequota の 3 段階で原因特定
  • kubectl debug による ephemeral container 注入と Pod コピー(--copy-to)の 2 形態

現場ヒヤリハット集(4 本まとめ)

  1. kubectl drain なしで kubelet を停止し、トラフィックを受けていた Pod が突然 Terminating になる
  2. NetworkPolicy を段階的適用せず本番に deny-all を直接適用して全通信が断たれる
  3. ConfigMap を環境変数で参照しているのに rollout restart を忘れて設定変更が反映されない
  4. ResourceQuota のみ設定して LimitRange を併用しないため、requests / limits 未指定 Pod の作成が must specify limits で拒否される

第16回 CKA D5 対応表

CKA D5 スキル第16回演習での対応
Evaluate cluster and node logging演習① journalctl -u kubelet による Workload Node 診断
Troubleshoot clusters and nodes演習① NotReady 状態の切り分けと復旧
Troubleshoot application failure演習③ CrashLoopBackOff の kubectl logs --previous 診断
Troubleshoot networking演習② NetworkPolicy 過剰 deny の describe + busybox 一時 Pod の wget 切り分け
Manage container stdout & stderr logs演習③ kubectl logs--previous の使い分け
Monitor applications and cluster components演習④ kubectl describe replicaset / resourcequota による Pod 作成失敗診断

第15回と第16回の 2 回で CKA D5 Troubleshooting(30%)に網羅的に対応しました。第6部「トラブルシュート」進捗: 2 回中 2 回完走です。

理解度チェック(○×・7 問)

  1. kubectl get nodesNotReady のノードは、対象ノードに ssh して journalctl -u kubelet でエラーログを確認できる
  2. NetworkPolicy を 1 つも適用していない Pod には、他の Pod からのすべての ingress トラフィックが自動的に拒否される
  3. kubectl logs <pod> でログが空の場合、kubectl logs <pod> --previous で直前にクラッシュしたコンテナのログを確認できる
  4. ResourceQuota で pods: "3" を設定した Namespace で Deployment の replicas: 5 を要求すると、超過した 2 Pod は Pending ステータスで Namespace 内に表示される
  5. kubectl debug <pod> --copy-to=<pod>-debug は、元 Pod に ephemeral container を直接注入するコマンドである
  6. CrashLoopBackOff 状態の Pod の再起動間隔は指数的に増加し、最大で約 5 分間隔で再起動が試みられる
  7. Workload Node を安全にメンテナンスする手順は kubectl cordonkubectl drain → メンテナンス → kubectl uncordon である

各問の正解と解説を以下に示します。先に自分で○か×を考えてから読み進めてください。

問 1: ○ — Node Controller から見た NotReady 原因は kubectl describe node で観察できますが、kubelet 自体の停止理由を確定するには対象 Workload Node に ssh して journalctl -u kubelet を読むのが定石です。本演習①ではこの手順で Deactivated successfully ログを確認しました。

問 2: × — NetworkPolicy のデフォルトは「許可」です。NetworkPolicy が 1 つも適用されていない Pod は全 ingress / egress を受け付けます。NetworkPolicy が適用された Pod のみ、その NetworkPolicy で許可されていない通信が遮断されます。podSelector: {} + policyTypes: [Ingress] + ingress 空 の組み合わせを書いて初めて Namespace 内の deny-all になります。

問 3: ○ — kubectl logs はコンテナ単位でログを保持しており、コンテナ再起動時に旧コンテナのログは --previous 経由でのみアクセス可能になります。CrashLoopBackOff の根本原因は再起動直前のスタックトレースに残るため、--previous は CKA D5 の必須コマンドです。

問 4: × — ResourceQuota の pods 上限を超えると、ReplicaSet Controller の Pod 作成 API リクエスト自体が Admission Controller(ResourceQuota plugin)で拒否されます。Pod オブジェクト自体が作成されないため、kubectl get pods には表示されません。診断は kubectl describe replicaset の Events で FailedCreate / exceeded quota を確認します。

問 5: × — kubectl debug <pod> --copy-to=<pod>-debug は元 Pod の コピーを作成し、コピー側で調査するコマンドです。元 Pod に ephemeral container を注入する場合は kubectl debug <pod> -it --image=192.168.1.123:5000/busybox:1.36 --target=<container> 形式を使います。本演習③の補足セクションで両方を扱いました。

問 6: ○ — kubelet の再起動バックオフは 10 秒 → 20 秒 → 40 秒 → 80 秒 → 160 秒 → 300 秒(5 分)と指数的に増加し、上限 5 分でクランプされます。長時間 CrashLoopBackOff のままの Pod を見つけたら、まず最新のエラーログが古い可能性を疑い kubectl describe podLastSeen 時刻を確認します。

問 7: ○ — メンテナンス対象ノードに新規 Pod を割り当てないために kubectl cordon、稼働中 Pod を退避するために kubectl drain(cordon 相当の処理を内包)、メンテナンス完了後に新規受け入れを再開するために kubectl uncordon という流れです。第6回で扱った手順がここでも生きます。

シリーズ一覧

第1部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク