インフラエンジニアの羅針盤

インフラエンジニア1〜3年目のための技術ガイド

CKA編⑯ ワークロード・アプリ障害の切り分け

公開更新

新卒インフラエンジニア向け「Kubernetes 実践教科書 ② CKA クラスタ構築・運用編」(全16回)の第16回、最終回です。前回の第15回では Control Plane 側の 4 大障害を扱いました。最終回となる本回は、その裏側——Workload Node・ネットワーク・アプリ側の障害を、同じ「わざと壊す → 自力で診断する → 直す」の型で仕上げます。CKA では D5(Troubleshooting)が配点の 30% を占める最重要ドメインで、前回と本回でその実技を集中訓練しました。本回を終えると CKA 5 ドメインすべてを一通り体験し終えます。最後に第2巻の完走宣言を行います。

本回も操作するホストが 2 種類あります。作業端末 k8s-ops192.168.1.122)から kubectl$developer)で、Workload Nodek8s-wl-02192.168.1.129)へ SSH して #(root)で、と使い分けます。ノードでは SSH 後に sudo -i で root になってください。各コマンドの前にどちらで実行するかを明記します。本回のシナリオはすべて破壊操作です。必ず本回開始時点のスナップショットを取り、そこへ戻せる状態で実施してください(1 つ壊して直したら次へ進みます)。

本回だけの前置きが 1 つあります。シナリオ 2・3 では fanclub-api 本体(NetworkPolicy や ConfigMap)を壊しますが、このアプリは第14回ArgoCD の GitOps 管理下に置き、selfHeal(自動修復)を有効にしました。つまり kubectl で手を加えても、ArgoCD が「Git と違う」と判断して数分で元に戻してしまいます。そこで壊す前に、そのシナリオの間だけ ArgoCD の自動同期を一時停止し、診断・修正が終わったら戻す——という手順を挟みます。GitOps を入れた環境で障害対応するときの実務そのものです。

目次
  1. 今ここマップ(全 16 回中の現在地)
  2. この回のゴール
  3. シナリオ1:Workload Node が NotReady(kubelet が起動しない)
  4. シナリオ2:NetworkPolicy で通信が切れる(半壊の切り分け)
  5. シナリオ3:Backend が DB に繋げず Ready にならない(→ CrashLoopBackOff)
  6. シナリオ4:Pod が作られない(ResourceQuota 超過)
  7. やってみよう
  8. まとめ
  9. 理解度チェック(○×形式・全 8 問)
  10. 第2巻完走宣言

今ここマップ(全 16 回中の現在地)

本シリーズは 6 部構成です。現在地は第6部「トラブルシュート」の第16回——第2巻の最終回です。

  • 第1部 クラスタ構築(第1〜5回)
  • 第2部 ワークロード管理(第6〜8回)
  • 第3部 ネットワーク(第9〜11回)
  • 第4部 ストレージ(第12回
  • 第5部 監視・運用(第13〜14回)
  • 第6部 トラブルシュート(第15〜16回) ← 今ここ(第16回)
  • Kubernetes v1.36.2
  • kubeadm v1.36.2
  • kubectl v1.35.6(作業端末)
  • ArgoCD v3.4.5
  • crictl v1.35.0(第15回で Control Plane に導入)
  • containerd 2.2.x
  • AlmaLinux 10.2
  • 確認日 2026-07-18

この回のゴール

本回を終えると、次のことができるようになります。到達できたかは記事末の「やってみよう」と「理解度チェック」で確認します。

  • NotReady の Workload Node を、kubelet の起動失敗まで含めて journalctl で診断・復旧できる
  • NetworkPolicy に起因する通信遮断を kubectl describe で切り分けられる
  • アプリが Ready にならない設定ミス(DB 接続)を kubectl logsdescribe で特定し ConfigMap を直せる(放置すれば CrashLoopBackOff)
  • ResourceQuota 超過で Pod が作られないことを Events で読み、本当の Pending と区別できる
  • ArgoCD 管理下のアプリを安全に壊して直す(自動同期の一時停止 → 復帰)作法を身につける
Workload・ネットワーク・アプリの 4 大障害を層で切り分ける図。上から Admission 層(ResourceQuota / LimitRange・Pod が作られない・describe rs / get events の exceeded quota)、App 層(Pod / コンテナ / probe・Running でも READY が揃わない・logs でアプリエラー)、Network 層(NetworkPolicy・通信だけ切れる・describe networkpolicy)、Node 層(kubelet / systemd・NotReady・journalctl)の 4 バンドに、各シナリオの症状・診断・直し方を対応づけ、下段に共通の型(get nodes / pods で当たり → describe / logs → ノード、ArgoCD は壊す前に自動同期を止める)を示した図
図1:Workload・ネットワーク・アプリの 4 大障害を層で切り分ける

シナリオ1:Workload Node が NotReady(kubelet が起動しない)

前回はまだ触れていない「kubelet が起動できない」ケースを扱います。前回のシナリオ 3 では kubelet を単純に停止しましたが、本番でよくあるのは設定ミスや証明書の問題で kubelet が起動に失敗し、ノードが NotReady のまま上がってこない状況です。今回は k8s-wl-02 の kubelet 起動引数に存在しないコンテナランタイムのソケットを指定して、起動失敗を再現します。kubeadm 環境では kubelet の追加引数は /var/lib/kubelet/kubeadm-flags.env に入っており、既定は空です。

実行コマンド(k8s-wl-02・root・障害の仕込み):

# cp /var/lib/kubelet/kubeadm-flags.env /root/kubeadm-flags.env.bak
# echo 'KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///run/wrong.sock"' > /var/lib/kubelet/kubeadm-flags.env
# systemctl restart kubelet

kubelet は存在しないソケットに繋ごうとして起動に失敗します。作業端末からノードの状態を見ると、k8s-wl-02NotReady に変わります(前回同様、切り替わりまで 40〜60 秒かかります)。

実行コマンド(k8s-ops・developer):

$ kubectl get nodes

実行結果(wl-02 が NotReady):

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

原因をノードに入って確かめます。kubelet は systemd サービスなので systemctl statusjournalctl -u kubelet で読みます(kubectl logs は Pod 用で、kubelet 自身には使えません)。

実行コマンド(k8s-wl-02・root):

# systemctl status kubelet --no-pager
# journalctl -u kubelet --no-pager | tail -15

実行結果(起動と失敗を繰り返している。ログの最後に原因が出る):

● kubelet.service - kubelet: The Kubernetes Node Agent
     Active: activating (auto-restart) (Result: exit-code) since Sat 2026-07-18 23:53:19 JST; 9s ago
...
kubelet[17772]: E0718 23:53:19.439098   17772 run.go:72] "command failed" err="failed to run Kubelet: validate service connection: validate CRI v1 runtime API for endpoint \"unix:///run/wrong.sock\": rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial unix /run/wrong.sock: connect: no such file or directory\""
systemd[1]: kubelet.service: Main process exited, code=exited, status=1/FAILURE

「指定したコンテナランタイムのソケットに繋がらない」と分かりました。kubeadm-flags.env の中身が原因です。元の空の状態に戻して再起動すれば復旧します。

実行コマンド(k8s-wl-02・root・復旧):

# cp /root/kubeadm-flags.env.bak /var/lib/kubelet/kubeadm-flags.env
# systemctl restart kubelet

数十秒で kubectl get nodes の wl-02 が Ready に戻ります。NotReady のノードは、まず journalctl -u kubelet でログを読む——単純停止でも設定ミスでも、入口は同じです。本番では、ログに出た根本原因(証明書期限切れ・cgroup ドライバ不一致・設定ファイルの文法エラーなど)を直してから再起動します。

シナリオ2:NetworkPolicy で通信が切れる(半壊の切り分け)

第10回で fanclub-api の ゼロトラストの NetworkPolicy(chart 同梱の 7 本)を読み解きました。その後、第11回で Gateway 用に 1 本、第13回で監視スクレイプ用に 1 本が加わり、いまは 9 本が効いています。あのときは「読み解くだけ」でしたが、本回では実際に 1 本壊して、通信が切れたときの切り分けを体験します。壊すのは allow-backend-from-frontend——Frontend から Backend(:8080)への通信を許可する ingress ルールです。ここが切れると、画面は表示されるのに会員一覧だけ取れない「半壊」が起きます。全部落ちるより、こういう部分的な不通のほうが現場では厄介で、切り分けの練習になります。

前置きどおり、まず ArgoCD の自動同期を止めます。これをしないと、壊した NetworkPolicy を ArgoCD が数分で元に戻してしまい、切り分けになりません。

実行コマンド(k8s-ops・developer・自動同期の停止):

$ kubectl patch application fanclub-api-prod -n argocd --type merge -p '{"spec":{"syncPolicy":null}}'

次に NetworkPolicy を壊します。allow-backend-from-frontend の ingress は「app: fanclub-frontend の Pod から」を許可していますが、このラベルをわざと誤値にして、Frontend が該当しないようにします。まず現物の全量を確認します。

実行コマンド(k8s-ops・developer):

$ kubectl get networkpolicy allow-backend-from-frontend -n fanclub -o yaml

実行結果(要点は spec.ingress[].from[].podSelector.matchLabels.app: fanclub-frontend——「app=fanclub-frontend の Pod からの :8080 を許可」):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-from-frontend
  namespace: fanclub
spec:
  podSelector:
    matchLabels:
      app: fanclub-backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: fanclub-frontend
    ports:
    - protocol: TCP
      port: 8080

この app: fanclub-frontend を、存在しない fanclub-frontend-typo に書き換えます(打ち間違いの再現)。kubectl patch でその 1 か所だけを差し替えます。

実行コマンド(k8s-ops・developer・障害の仕込み):

$ kubectl patch networkpolicy allow-backend-from-frontend -n fanclub --type json \
    -p '[{"op":"replace","path":"/spec/ingress/0/from/0/podSelector/matchLabels/app","value":"fanclub-frontend-typo"}]'

Frontend からのアクセスが default-deny-all に落ちるようになります。症状を確かめます。ブラウザ相当の確認は、Frontend の Pod から Backend へ直接 curl してみるのが確実です(作業端末からの curl は企業プロキシを経由するため、Pod 内から試します)。

実行コマンド(k8s-ops・developer):

$ kubectl exec -n fanclub deploy/fanclub-frontend -- curl -s -m 5 http://fanclub-backend:8080/api/members

実行結果(通信がブロックされ、タイムアウトする):

command terminated with exit code 28

curl の終了コード 28タイムアウトです(-m 5 で 5 秒待って諦めました)。到達不能=Backend 自体は正常なのに Frontend→Backend が遮断されている、と読めます。「接続拒否(Connection refused)ではなくタイムアウト」という点も手がかりです——相手が居ないなら即座に拒否が返りますが、NetworkPolicy はパケットを黙って落とすので、送った側は返事を待ち続けてタイムアウトします。第13回で Prometheus の Target が DOWN になったときも同じ context deadline exceeded(=待って諦めた)でした。

もし遮断されずに JSON が返ってきたら

前回(第15回シナリオ 2)で etcd をスナップショットから復元した直後は、Calico が NetworkPolicy を適用しなくなっていることがあります。etcd を巻き戻すと Calico が把握しているエンドポイントの情報とクラスタの実状態がずれ、ポリシーがどの Pod にも当たらない状態になるためです。この状態では「ポリシーを壊したのに通ってしまう」ので演習になりません。次のコマンドで calico-node を入れ替えると、Felix がポリシーを作り直して正しく遮断されるようになります(入れ替えには数分かかります)。

$ kubectl rollout restart daemonset calico-node -n kube-system

これはetcd 復元という重い操作の後始末にあたります。「クラスタは戻ったが、CNI が古い状態を握ったままだった」——本番の復旧でも起こりうるので、etcd を復元したら CNI の再起動まで含めて 1 つの手順と覚えておいてください。

Backend の Pod は動いている(kubectl get pods で Running)のに、Frontend からは届かない。Pod もアプリも正常なのに通信だけ切れている——こういうときは NetworkPolicy を疑います。describe で当該ルールの中身を読みます。

実行コマンド(k8s-ops・developer):

$ kubectl describe networkpolicy allow-backend-from-frontend -n fanclub

実行結果(from のラベルが誤っていると分かる):

Name:         allow-backend-from-frontend
Namespace:    fanclub
Spec:
  PodSelector:     app=fanclub-backend
  Allowing ingress traffic:
    To Port: 8080/TCP
    From:
      PodSelector: app=fanclub-frontend-typo
  Not affecting egress traffic
  Policy Types: Ingress

From のラベルが実在しない fanclub-frontend-typo になっており、Frontend の Pod(app=fanclub-frontend)が該当しなくなっています。Not affecting egress traffic と出ているとおり、この 1 本は Backend の受け(ingress)だけを扱うルールです。

第10回で学んだ切り分けの型——「Pod は正常なのに通信が切れる → ラベル/セレクタを疑う」——がそのまま効きました。通信は送る側の egress と受ける側の ingress の両方が許可されて初めて通ります。今回は Frontend 側の egress(allow-egress-frontend-to-backend)は無事で、受ける Backend 側の ingress(allow-backend-from-frontend)の from ラベルが壊れていたので、両方を確認する癖をつけると切り分けが速くなります。ラベルを正しい fanclub-frontend に戻します。

実行コマンド(k8s-ops・developer・復旧):

$ kubectl patch networkpolicy allow-backend-from-frontend -n fanclub --type json \
    -p '[{"op":"replace","path":"/spec/ingress/0/from/0/podSelector/matchLabels/app","value":"fanclub-frontend"}]'
$ kubectl exec -n fanclub deploy/fanclub-frontend -- curl -s -m 5 http://fanclub-backend:8080/api/members

会員一覧の JSON が返れば復旧です。この後シナリオ 3 でも fanclub を壊すので、自動同期はまだ止めたままにしておきます。

シナリオ3:Backend が DB に繋げず Ready にならない(→ CrashLoopBackOff)

次はアプリ本体の障害です。設定ミスで Backend が正しく起動しない状況を作ります。Backend は接続先 DB を fanclub-config という ConfigMap の DB_HOST から読み込みます。ここを存在しないホスト名に書き換えると、Backend の Java プロセス自体は立ち上がるもののDB に繋げずアプリのデプロイに失敗し、ヘルスチェック(Startup probe)が通らず Pod が Ready になりません。この状態を放置すると Startup probe がやがて諦めてコンテナが再起動され、CrashLoopBackOff に至ります。いずれにせよ「新しい Pod が Ready にならない」障害で、診断の入口は同じです。自動同期はシナリオ 2 から止めたままです。

実行コマンド(k8s-ops・developer・障害の仕込み):

$ kubectl patch configmap fanclub-config -n fanclub --type merge -p '{"data":{"DB_HOST":"fanclub-db-wrong"}}'
$ kubectl rollout restart deployment fanclub-backend -n fanclub

ConfigMap を変えただけでは動いている Pod には反映されませんenvFrom は Pod 起動時に一度だけ読み込むため)。だから rollout restart で Pod を作り直しています。新しい Pod は誤った DB ホストのせいで Ready にならず、ロールアウトは途中で止まります(ローリング更新なので古い Pod が残ってサービスが一時的に続きますが、着目するのはReady にならない新しい Pod です)。状態を見ます。

実行コマンド(k8s-ops・developer):

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

実行結果(新しい Pod だけ 1/2 で止まる。古い Pod は元の設定のまま Running):

NAME                               READY   STATUS    RESTARTS   AGE
fanclub-backend-6896d48bd9-2xf5n   2/2     Running   0          20m
fanclub-backend-6896d48bd9-6j6fv   2/2     Running   0          20m
fanclub-backend-6896d48bd9-tghrw   2/2     Running   0          20m
fanclub-backend-64c8c744b8-nqsbd   1/2     Running   0          3m

新しい Pod は 1/2 Running——コンテナは動いているのに Ready が 1/2で止まっています。DB 待ちの initContainer は DB の FQDN(fanclub-db.fanclub.svc…)を直接見ているので通り、Backend 本体は起動しますが、DB_HOST=fanclub-db-wrong で DB に繋げずヘルスチェックが通りません。Startup probe には猶予(failureThreshold)があるので、すぐには CrashLoopBackOff にならず、しばらく 1/2 Running のまま——猶予(本アプリでは約 5 分)を過ぎて初めて再起動=CrashLoopBackOff に移ります。Running だからと安心せず、READY 列(1/2を見るのが勘所です。

ここで -l app=fanclub-backend のまま logs を見ると正常な古い Pod のログも混ざって紛らわしいので、上で見つけた 1/2 で止まっている Pod 名を指定して調べます(以下は例。実際の Pod 名に置き換えてください)。describeEvents(どのヘルスチェックが失敗しているか)を確かめ、Ready でないコンテナ(backendのログを見ます。

実行コマンド(k8s-ops・developer):

$ kubectl describe pod fanclub-backend-64c8c744b8-nqsbd -n fanclub | sed -n '/Events:/,$p'
$ kubectl logs fanclub-backend-64c8c744b8-nqsbd -n fanclub -c backend --tail=30

実行結果(describe の Events は Startup probe の失敗、logs は DB 接続エラーを示す):

Events:
  Type      Reason     Age                   From      Message
  ----      ------     ----                  ----      -------
  Normal    Started    3m                    kubelet   spec.containers{backend}: Container started
  Warning   Unhealthy  0s (x16 over 2m30s)   kubelet   spec.containers{backend}: Startup probe failed: HTTP probe failed with statuscode: 503

logs(backend コンテナ):

Exception [EclipseLink-4002] ... org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Error in allocating a connection.
  Cause: Connection could not be allocated because: The connection attempt failed.
  Boot Command deploy failed ... Exception ... The connection attempt failed.

「接続先の DB ホストがおかしい」と分かりました。設定は fanclub-config にあります。中身を確認して、正しい fanclub-db に戻します。

実行コマンド(k8s-ops・developer・復旧):

$ kubectl get configmap fanclub-config -n fanclub -o jsonpath='{.data.DB_HOST}'; echo
$ kubectl patch configmap fanclub-config -n fanclub --type merge -p '{"data":{"DB_HOST":"fanclub-db"}}'
$ kubectl rollout restart deployment fanclub-backend -n fanclub

新しい Pod が起動して 2/2 Running になれば復旧です(本アプリは起動に 2〜3 分かかります)。ConfigMap/Secret の設定ミスは、Pod のログにアプリのエラーとして現れる——「Running でも READY が揃わない → describe で失敗中のヘルスチェックを確かめ、そのコンテナの logs を読む」が鉄則です。直したら、止めていた自動同期を戻します。これで ArgoCD がまた「Git を正」として見張る状態に戻ります。

実行コマンド(k8s-ops・developer・自動同期の復帰):

$ kubectl patch application fanclub-api-prod -n argocd --type merge \
    -p '{"spec":{"syncPolicy":{"automated":{"prune":true,"selfHeal":true}}}}'

自動同期を戻したので、以後にうっかり手で変えても ArgoCD が Git の状態へ引き戻します。戻した直後、Backend がもう一度作り直される(ロールアウトが走る)ことがあります——rollout restart が付けた注釈は Git に無いので、selfHeal がそれを消して Deployment を Git の姿へ揃えるためです。これも「Git を正とする」動作の一部で、少し待てば健全に収束します(ブラウザで会員一覧が出れば完了)。「壊して直す」演習が終わったら必ず自動同期を戻す——止めっぱなしにしないのが運用の作法です。

シナリオ4:Pod が作られない(ResourceQuota 超過)

最後は紛らわしいやつです。第8回fanclub Namespace に ResourceQuota(CPU・メモリ・Pod 数の上限)を掛け、第14回の prod overlay で上限を limits.cpu: 8 へ拡張しました。この上限を超える Deployment を作ると、どうなるでしょうか。Pod が「Pending」で止まるのではなく、そもそも作られません。ここを正確に理解しておくと、CKA でも本番でも迷いません。fanclub とは別の使い捨て Deployment で試します。

Quota の limits.cpu は 8 コアで、fanclub 本体が 5.5 コア使用中=残りは約 2.5 コアです。ここで注意が 1 つ——第8回で入れた LimitRange が「1 コンテナあたり limits.cpu は最大 2 コア」に制限しているので、1 コンテナで残枠を超えさせようとすると先に LimitRange に弾かれ、Quota の話にたどり着けません。そこで 1 つの Pod に 2 コンテナを置き、各 2 コア=合計 4 コアを要求させます。各コンテナは 2 コア(LimitRange の上限ちょうど)なので LimitRange は通り、Pod 合計 4 コアが残枠 2.5 コアを超えて Quota で拒否されます。

実行コマンド(k8s-ops・developer・障害の仕込み):

$ cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: quota-test
  namespace: fanclub
spec:
  replicas: 2
  selector:
    matchLabels:
      app: quota-test
  template:
    metadata:
      labels:
        app: quota-test
    spec:
      containers:
      - name: hog-a
        image: registry.k8s.io/pause:3.10
        resources:
          requests:
            cpu: "100m"
            memory: "32Mi"
          limits:
            cpu: "2"
            memory: "64Mi"
      - name: hog-b
        image: registry.k8s.io/pause:3.10
        resources:
          requests:
            cpu: "100m"
            memory: "32Mi"
          limits:
            cpu: "2"
            memory: "64Mi"
EOF

Deployment は作られますが、Pod が一向に増えません。get podsquota-test が出てこないのです。

実行コマンド(k8s-ops・developer):

$ kubectl get deploy quota-test -n fanclub
$ kubectl get pods -n fanclub -l app=quota-test

実行結果(Deployment は READY 0/Pod は 1 つも無い):

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
quota-test   0/2     0            0           ...
No resources found in fanclub namespace.

Pod が無い=describe する Pod も無いのがポイントです。原因は Pod ではなく、その手前の ReplicaSet にあります。ReplicaSet の Events を読みます。

実行コマンド(k8s-ops・developer):

$ kubectl describe rs -n fanclub -l app=quota-test | tail -12

実行結果(Quota 超過で作成が拒否されている):

Events:
  Type     Reason        Age   From                    Message
  ----     ------        ----  ----                    -------
  Warning  FailedCreate  5s    replicaset-controller   Error creating: pods "quota-test-6bfdc55cd-r47ht" is forbidden: exceeded quota: fanclub-quota, requested: limits.cpu=4, used: limits.cpu=5500m, limited: limits.cpu=8
  Warning  FailedCreate  5s    replicaset-controller   Error creating: pods "quota-test-6bfdc55cd-ldfpm" is forbidden: exceeded quota: fanclub-quota, requested: limits.cpu=4, used: limits.cpu=5500m, limited: limits.cpu=8

「Quota を超えたので Pod を作れない」と ReplicaSet が言っています。復旧は、要求を下げるか Quota を調整します。今回は使い捨てなので Deployment を削除します(本番なら要求の見直しか Quota の増枠を検討します)。

実行コマンド(k8s-ops・developer・後片付け):

$ kubectl delete deployment quota-test -n fanclub

「Pod が作られない」と「Pod は作られるが Pending」は別物です。前者は ResourceQuota や LimitRange による admission 拒否——Pod オブジェクト自体が生まれず、describe rskubectl get events に理由が出ます。後者の本当の Pending は、Pod は生まれたがスケジュールできない状態で、kubectl describe pod の Events に Insufficient cpu(ノードの空き不足)・node(s) didn't match node selectornodeSelector 不一致)・pod has unbound PersistentVolumeClaims(PVC 未バインド)などが出ます。「Pod があるか無いか」で見る場所が変わる——これが Pending 系を切り分ける勘所です。

Pod が動かないときに状態で診断先が変わることを示す判別フロー図。まず「Pod は存在する?」で分岐し、無い(作られない)なら Admission 拒否=describe rs / get events の exceeded quota(本回シナリオ4)。有るなら「STATUS は?」で分岐し、Pending はスケジュール不可=describe pod の Events(Insufficient cpu / nodeSelector 不一致 / PVC 未バインド)、Running だが 1/2 はアプリ / probe 失敗=logs でアプリエラー(本回シナリオ3)、CrashLoopBackOff は起動後に落ちる=logs(--previous)、という 4 つの見る場所へ導く図
図2:Pod が動かないときの診断先の判別フロー

やってみよう

すべて破壊操作です。必ず本回開始時点へ戻せるスナップショットを取ってから行ってください。fanclub を壊す演習(2・3)では、先に ArgoCD の自動同期を止め、終わったら戻すのを忘れないでください。

起動しない kubelet を直す

k8s-wl-02 の /var/lib/kubelet/kubeadm-flags.env に不正なランタイムエンドポイントを設定して systemctl restart kubelet → 1 分ほど待って kubectl get nodes で NotReady を確認 → journalctl -u kubelet で起動失敗の原因を読み取り → ファイルを元に戻して復旧してください。

切れた通信を NetworkPolicy から直す

自動同期を止めてから allow-backend-from-frontend の from ラベルを誤値にし、Frontend の Pod から curl http://fanclub-backend:8080/api/members が届かなくなることを確認 → kubectl describe networkpolicy で from の誤りを特定 → ラベルを戻して復旧してください。続けて演習 3 を行う場合は自動同期を止めたまま、行わない場合はここで自動同期を戻してください(戻し方は演習 3 の手順を参照)。

Backend が Ready にならない障害を ConfigMap から直す

fanclub-configDB_HOST を誤値にして rollout restart → 新しい Pod が 1/2 Running のまま Ready にならないことを確認 → 該当 Pod を describe(Startup probe の失敗)と kubectl logs -c backend(DB 接続エラー)で原因特定 → ConfigMap を fanclub-db に戻して復旧 → 自動同期を戻してください。

Pod が作られない理由を Events から読む

Quota を超える使い捨て Deployment を作り、Pod が作られないことを確認 → kubectl describe rs -n fanclubkubectl get events -n fanclubexceeded quota を読み取り → Deployment を削除してください。あわせて「本当の Pending」との違いを説明できるか確かめてください。

まとめ

本回では Workload Node・ネットワーク・アプリ側の 4 大障害を、仕込み → 診断 → 復旧の型で通しました。①kubelet 起動失敗——journalctl -u kubelet でログを読む。②NetworkPolicy——Pod は正常なのに通信が切れたらセレクタ/ラベルを describe で疑う。③アプリが Ready にならない(DB 接続失敗)——Running でも READY が揃わなければ describe でヘルスチェックの失敗を、logs でアプリのエラーを読む(放置すれば CrashLoopBackOff)。ConfigMap 変更は rollout restart で反映。④ResourceQuota——超過すると Pod は作られず、describe rsevents に理由が出る(本当の Pending とは別物)。加えて、ArgoCD 管理下のアプリを壊すときは自動同期を止め、直したら戻すという GitOps 環境の作法も身につきました。前回とあわせ、「上から順に切り分け、慌てて手を広げない」——CKA D5 の核心を反復できました。

理解度チェック(○×形式・全 8 問)

次の各文が正しいか(○)誤りか(×)を判断してください。下の「解答と解説」を開くと答え合わせができます。

  1. kubelet の起動失敗の原因は kubectl logs で読む。
  2. Pod もアプリも正常なのに通信だけ切れているときは、NetworkPolicy のセレクタ/ラベルを疑うのが定石である。
  3. ConfigMap を変更すると、動いている Pod の環境変数もその場で更新される。
  4. ResourceQuota の上限を超える Deployment を作ると、Pod は Pending で待機する。
  5. ArgoCD の selfHeal が有効なままだと、kubectl で fanclub の設定を変えても元に戻される。
  6. 「Pod が作られない」原因は kubectl describe rskubectl get events で読める。
  7. 本当の Pending(スケジュール不可)の原因は kubectl describe pod の Events に出る。
  8. 壊して直す演習で止めた自動同期は、終わったら元に戻すべきである。
解答と解説

1=×(kubelet は systemd サービス。journalctl -u kubelet で読む。kubectl logs は Pod 用)/2=○(第10回の型。ラベル/セレクタの不一致を describe で確認)/3=×(envFrom は Pod 起動時に読み込む。rollout restart で作り直して反映)/4=×(Quota 超過は admission 拒否で Pod が作られない。Pending ではない)/5=○(Git を正として引き戻す。壊す前に自動同期を止める)/6=○(Pod が無いので原因は ReplicaSet の Events)/7=○(Insufficient cpu・nodeSelector 不一致・PVC 未バインド など)/8=○(止めっぱなしにしないのが運用の作法)

第2巻完走宣言

これで第2巻は完走です。第1巻(CKAD)で既存クラスタにアプリを載せた読者は、本巻でクラスタそのものを自力で構築・運用・トラブルシュートできるところまで来ました。下のチェックリストで到達を確かめてください。

  • kubeadm HA クラスタ(Control Plane ×3 + Workload Node ×2)を自力構築できた(第2〜4回)
  • etcdctl による etcd の backup/restore を自力実施できた(第5・15回)
  • kubeadm upgrade によるクラスタのマイナーアップグレードができた(第6回
  • fanclub-api が Longhorn + ArgoCD GitOps + Prometheus 監視付きで稼働している(第12〜14回)
  • Velero でクラスタのバックアップ・リストアができた(第14回)
  • Control Plane・Workload Node・ネットワーク・アプリの障害を診断・復旧できた(第15・16回)
  • CKA の 5 ドメイン(D1〜D5)を全回で一通り体験した
第2巻の完走マップ。土台の第1部クラスタ構築(kubeadm HA クラスタ・etcd quorum・HAProxy・etcd backup / restore)の上に、第2部ワークロード管理(upgrade / 証明書 / drain・RBAC / Quota / HPA)、第3部ネットワーク(Service / MetalLB・NetworkPolicy・Gateway API / Traefik / cert-manager)、第4部ストレージ(Longhorn)、第5部監視・運用(Prometheus / Grafana / Loki・Helm / Kustomize / ArgoCD / Velero)、第6部トラブルシュートを積み上げ、頂点に「CKA 5 ドメイン網羅・受験準備完了」を置いた、16 回の到達を表す図
図3:第2巻の完走マップ(6 部 16 回の到達)

すべてに ○ が付いた読者は、CKA の受験準備が整いました。あとは Killer.sh(CKA 申込に 2 回分付属する模擬試験環境)で本番同等の緊張感に慣れれば、合格は目の前です。時間配分・kubectl の速度・ドキュメント参照の勘所を、模擬で仕上げてください。第1巻の CKAD とあわせて 2 冠、本番の実技試験に自信を持って臨めます。ここまで完走した力は、資格の枠を超えて現場でそのまま通用します。次のセキュリティ・ハードニング編(CKS)で、この基盤をさらに堅牢にしていきます。ここは終わりではなく、新しいスタートラインです。

前の記事
次の記事