新卒インフラエンジニア向け 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回のスコープと最終目標
- トラブルシュートの鉄則 3 ステップと診断ツールマトリクス
- やってみよう①: NotReady Workload Node の復旧
- やってみよう②: NetworkPolicy 過剰 deny の切り分け
- やってみよう③: CrashLoopBackOff 診断(DB 接続文字列誤設定)
- やってみよう④: Pending Pod 原因診断(ResourceQuota 上限超過)
- 完走マイルストーン達成チェックリスト
- ★ 第2巻完走宣言(CKA 受験準備完了)
- 次のシリーズへの橋渡しと Kubestronaut 5 冠
- 第16回まとめと現場ヒヤリハット集
- シリーズ一覧
今ここマップ(第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 --previousとkubectl 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 kubelet | journalctl -u kubelet / kubectl describe node | systemctl start kubelet |
| ② | NetworkPolicy 過剰 deny の切り分け | deny-all NetworkPolicy 適用 | kubectl describe networkpolicy / busybox Pod + wget | NetworkPolicy 削除 または修正 |
| ③ | CrashLoopBackOff 診断 | 環境変数に不正な JVM オプション | kubectl logs -p / kubectl describe pod | kubectl set env で環境変数を修正 |
| ④ | Pending 原因診断 | ResourceQuota pods 上限超過 | kubectl describe pod/replicaset + describe resourcequota | kubectl 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回でも全演習で同じ流れを踏みます。
- 観察:
kubectl getで異常コンポーネントを特定する - 診断:
kubectl describe/kubectl logs/journalctlでエラーメッセージを読む - 復旧: 最小変更で直し、
kubectl getで正常復帰を確認する
第15回の重点は Control Plane Node 側のコンポーネント(kube-apiserver Static Pod・etcd Static Pod・Control Plane Node の kubelet・kubeconfig)でした。第16回は Workload Node 側・ネットワーク層・アプリ層に重点を移します。障害レイヤーごとに使う診断ツールが異なるため、下記マトリクスを頭に入れてから演習に入ります。
障害レイヤー別 診断ツールマトリクス
| 障害レイヤー | 主ツール | サブツール |
|---|---|---|
| Workload Node | systemctl status kubelet / journalctl -u kubelet | kubectl describe node |
| NetworkPolicy | kubectl describe networkpolicy | busybox Pod + wget |
| アプリクラッシュ | kubectl logs --previous | kubectl describe pod / kubectl debug |
| スケジューリング | kubectl describe pod Events | kubectl describe replicaset / describe resourcequota |
マトリクスの「主ツール」は最初に手を伸ばすコマンド、「サブツール」は主ツールで不足する情報を補うコマンドです。CKA 試験では制限時間が 2 時間しかないため、レイヤーを判定したら迷わず主ツールへ進める判断力が重要になります。
やってみよう①: NotReady Workload Node の復旧
Workload Node の kubelet を意図的に停止し、kubectl get nodes が NotReady に遷移する過程を観察します。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-zk9xt が Terminating に入っています。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 successfully | kubelet 手動停止 | systemctl start kubelet |
failed to load Kubelet config file | kubelet 設定ファイル破損 | 設定ファイルの構文修復 |
Main process exited, code=killed, status=9/KILL | OOM Kill による kubelet 強制終了 | メモリ確保・systemd ユニット制限見直し |
CNI plugin not initialized | CNI 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 cordon と kubectl 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
policyTypes に Ingress を含めつつ 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 イメージには curl も wget も含まれていないため(疎通テストツールが無いのは本番でもよくあります)、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.org や kubectl 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 の nslookup や nc で接続先確認ができます。元コンテナを停止せずに調査できる点が 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-demo に pods: "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: ... pods | ResourceQuota の pods 上限超過 | kubectl patch resourcequota / 別 Namespace へ分散 |
exceeded quota: ... requests.cpu | ResourceQuota の CPU request 上限超過 | quota 上限引き上げ または requests 削減 |
Insufficient cpu / Insufficient memory | ノードのリソース残量不足 | ノード追加 または requests 削減 |
0/5 nodes are available: ... didn't match node selector | nodeSelector / affinity 不一致 | nodeSelector ラベル修正 |
persistentvolumeclaim "X" not found | PVC 未作成 または未バインド | PVC 作成 / StorageClass 確認 |
X node(s) had untolerated taint | taint / 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 受験に向けた最終ステップ
- Killer.sh 模擬試験 1 周目: 制限時間 2 時間・15〜20 問程度(公式公開仕様の範囲・本番 CKA の問題数も同水準)を通しで受験し、現状スコアを把握する。CKA 試験申し込み時に Killer.sh が 2 回分付属する
- 弱点回の復習: 模擬試験のスコアが低かったドメインに対応する回(D1 なら第3〜6回・D5 なら第15〜16回)を再読し、実機で再演習する
- Killer.sh 模擬試験 2 周目: 復習後に 2 周目を受験し 66% 以上を確認する。本番試験の合格ラインも 66% である
- 試験予約: training.linuxfoundation.org で CKA 試験を予約し、事前に PSI ブリッジ接続テストを実施する
- 試験当日: Ubuntu ベースの試験端末 + kubectl + kubernetes.io ドキュメント参照可。ブラウザのブックマーク登録は試験前に済ませる
CKA ドメイン別カバー状況
| ドメイン | 配点 | カバー回 |
|---|---|---|
| D1: Cluster Architecture, Installation & Configuration | 25% | 第1〜6回・第14回 |
| D2: Workloads & Scheduling | 15% | 第7〜8回 |
| D3: Services & Networking | 20% | 第9〜11回 |
| D4: Storage | 10% | 第12回 |
| D5: Troubleshooting | 30% | 第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 本まとめ)
kubectl drainなしで kubelet を停止し、トラフィックを受けていた Pod が突然 Terminating になる- NetworkPolicy を段階的適用せず本番に deny-all を直接適用して全通信が断たれる
- ConfigMap を環境変数で参照しているのに
rollout restartを忘れて設定変更が反映されない - 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 問)
kubectl get nodesでNotReadyのノードは、対象ノードに ssh してjournalctl -u kubeletでエラーログを確認できる- NetworkPolicy を 1 つも適用していない Pod には、他の Pod からのすべての ingress トラフィックが自動的に拒否される
kubectl logs <pod>でログが空の場合、kubectl logs <pod> --previousで直前にクラッシュしたコンテナのログを確認できる- ResourceQuota で
pods: "3"を設定した Namespace で Deployment のreplicas: 5を要求すると、超過した 2 Pod はPendingステータスで Namespace 内に表示される kubectl debug <pod> --copy-to=<pod>-debugは、元 Pod に ephemeral container を直接注入するコマンドである- CrashLoopBackOff 状態の Pod の再起動間隔は指数的に増加し、最大で約 5 分間隔で再起動が試みられる
- Workload Node を安全にメンテナンスする手順は
kubectl cordon→kubectl 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 pod の LastSeen 時刻を確認します。
問 7: ○ — メンテナンス対象ノードに新規 Pod を割り当てないために kubectl cordon、稼働中 Pod を退避するために kubectl drain(cordon 相当の処理を内包)、メンテナンス完了後に新規受け入れを再開するために kubectl uncordon という流れです。第6回で扱った手順がここでも生きます。
シリーズ一覧
第1部:クラスタ構築
- 第1回 第2巻スコープ + CKA 試験形式紹介 + kubeadm 概要 + kubeadm vs RKE2/k0s/OKD 概観
- 第2回 kubeadm シングルノード起動(pkgs.k8s.io + kubeadm init + Calico CNI)+ alma-proxy whitelist 構築
- 第3回 kubeadm HA 設計 + HAProxy LB(API Server LB 構成)
- 第4回 kubeadm HA クラスタ構築(CP×3 + WL×2)+ fanclub-api HA 移行
- 第5回 etcd backup/restore(etcdutl)+ ノード drain/uncordon
第2部:ワークロード管理
- 第6回 kubeadm upgrade + ノード drain/cordon + ミニトラブルシュート演習
- 第7回 Pod スケジューリング(taint/toleration/affinity/anti-affinity/PriorityClass)
- 第8回 HPA + ResourceQuota + LimitRange
第3部:ネットワーク
- 第9回 Service 詳細 + MetalLB(LoadBalancer 動作確認)
- 第10回 NetworkPolicy 詳細 + Calico/Cilium 比較 + ミニトラブルシュート演習
- 第11回 Gateway API + Traefik + cert-manager + CoreDNS(HTTPS 公開完成)
第4部:ストレージ
第5部:監視・運用
- 第13回 Prometheus + Grafana + Loki + Fluent Bit + fanclub-api 監視ダッシュボード
- 第14回 Helm + Kustomize でクラスタコンポーネント install + ArgoCD GitOps + Velero backup
第6部:トラブルシュート
- 第15回 Control Plane + etcd トラブルシュート演習(Static Pod / etcdutl / kubelet 復旧)
- 第16回 Workload Node + Network + App トラブルシュート演習 + 第2巻完走宣言 ★ ← 今ここ
