Certified Kubernetes Administrator (CKA) #23 Troubleshooting 2: ノードと kubelet (NotReady、disk/memory pressure)

#22 Troubleshooting 1 では Pod とアプリレベルの障害を扱いました。Pending・CrashLoopBackOff・ImagePullBackOff・OOMKilled のように、1 つの Pod の状態を見るだけで原因が絞られる問題でした。今回の記事では 1 段下に降ります。ノード自体が NotReady に落ちた状況 です。

ノード障害は Pod 障害とは質が異なります。ノードが 1 つ NotReady になると、その上にあった複数の Pod が一度に影響を受け、kubectl だけでは原因が見えないことが多いです。結局 ノードに直接接続して kubelet とコンテナランタイムをシステムレベルで覗き込む 必要があります。CKA で Linux 運用の感覚が求められる代表的な領域であり、Troubleshooting ドメインの中でも配点が付きやすいタイプです。

ノード NotReady とは何か #

各ノードの kubelet は一定周期で control plane に自分の状態を報告します。この報告が正常ならノードは Ready であり、定められた時間内に報告が途切れたり kubelet が問題を知らせたりすると、ノードは NotReady と表示されます。最初に見る画面がノード一覧です。

k get nodes
NAME     STATUS     ROLES           AGE   VERSION
master   Ready      control-plane   40d   v1.31.0
node01   NotReady   <none>          40d   v1.31.0
node02   Ready      <none>          40d   v1.31.0

node01 が NotReady です。ここですぐにノードへ接続したくなりますが、その前に control plane が受け取った情報から読むのが順序です。ノードに接続しなくても原因の半分は describe で明らかになります。

ステップ 1: k describe node で conditions を読む #

ノード診断の出発点はいつも describe です。ノードは複数の condition を持ち、各 condition の状態と最後の遷移時刻、そして人が読めるメッセージが一緒に出ます。

k describe node node01

出力で最初に見る場所が Conditions ブロックです。

Conditions:
  Type             Status    Reason                       Message
  ----             ------    ------                       -------
  MemoryPressure   False     KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False     KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False     KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            Unknown   NodeStatusUnknown            Kubelet stopped posting node status.

ノード condition の意味 #

Condition意味True のとき
Readyノードが Pod を受け入れられる正常状態正常。False/Unknown なら異常
MemoryPressureノードの利用可能メモリが閾値を下回るメモリ不足。eviction が発生し得る
DiskPressureノードのディスク容量または inode が閾値を下回るディスク不足。イメージ・Pod eviction が発生
PIDPressureノードの利用可能 PID が閾値を下回るプロセス数が飽和

Ready だけ他の condition と方向が逆です。Ready=True が正常で、残りの pressure 系は False が正常です。上の例では ReadyUnknown でメッセージが Kubelet stopped posting node status. なので、kubelet が状態報告を止めた という意味です。pressure 系はすべて False なので資源不足ではありません。この場合、原因はほぼ確実に kubelet 自体にあります。

Ready の Status 別の解釈 #

Ready Status解釈次の行動
True正常ノードの問題ではない。Pod レベルへ
Falsekubelet は生きているがノードが異常を報告メッセージの Reason を確認。ランタイム・ネットワーク・pressure を疑う
Unknownkubelet の報告が途切れたノードに接続。kubelet 停止・ノードダウン・ネットワーク断を疑う

Unknown は control plane がノードとの連絡を失ったというシグナルです。kubelet が死んだか、ノードが落ちたか、ノードと apiserver の間のネットワークが塞がれたかの 3 つの枝です。ここからはノードに直接入る必要があります。

ステップ 2: ノードに接続して kubelet 状態を確認 #

試験では接続するノードのホスト名が問題に提示されます。SSH で入ったあと、権限が必要なら sudo で上がります。

ssh node01
sudo -i

ノードの上で最初に確認する対象は kubelet サービスです。kubelet はノードのすべての Pod を起動して管理するエージェントなので、これが止まるとノード全体が NotReady になります。

systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; ...)
   Active: inactive (dead) since Sat 2026-06-06 09:12:41 UTC; 3min ago

Active: inactive (dead) なら kubelet が止まっています。active (running) なのにノードが NotReady なら、kubelet は起動しているが正常に動作できていない状況なので、ログを見る必要があります。

journalctl で kubelet ログを読む #

kubelet は systemd ユニットとして動くため、ログは journalctl で見ます。最近のログから逆にたどるのが速いです。

# kubelet ログ全体 (ページャ)
journalctl -u kubelet

# 最近 100 行だけ
journalctl -u kubelet -n 100 --no-pager

# リアルタイム追跡 (kubelet を再起動しながら一緒に見るとよい)
journalctl -u kubelet -f

ログで errorfailedunable to のようなキーワードを探すと、原因の 1 行がほぼ必ず出てきます。設定ファイルのパスエラー、証明書失効、ランタイムソケット接続失敗が代表的です。

よくある原因と解決 #

ノード NotReady の原因は大きく 5 つの枝です。一つずつ症状と解決を整理します。

原因 1: kubelet が停止した #

最も単純でありながら試験によく出るケースです。systemctl status kubeletinactive (dead) なら kubelet を再び起動します。

systemctl start kubelet

# 起動時の自動開始もオンにしておく
systemctl enable kubelet

# 状態を再確認
systemctl status kubelet

再び起動したのにすぐ死ぬなら、単純な停止ではなく設定の問題です。journalctl -u kubelet -n 50 --no-pager で死ぬ理由を確認し、次の項目に進みます。

原因 2: kubelet 設定が間違っている #

kubelet は複数の設定ファイルを読んで起動します。試験ではこのパスのうち 1 つがわざと壊されているケースがあります。

パス役割
/var/lib/kubelet/config.yamlkubelet の主設定。cgroup driver、eviction 閾値など
/etc/kubernetes/kubelet.confapiserver 接続用の kubeconfig
/etc/systemd/system/kubelet.service.d/10-kubeadm.confsystemd が kubelet を起動するときに使う引数
/var/lib/kubelet/kubeadm-flags.envkubeadm が追加するランタイム引数 (例: ランタイムソケットのパス)

ログに failed to load Kubelet config file または unable to load client CA file のような行が見えたら、上のファイルのパスと内容を点検します。よく出る罠の 1 つは kubeconfig が指す apiserver のポートやサーバアドレスが間違っているケース です。

# kubelet kubeconfig が指すサーバを確認
cat /etc/kubernetes/kubelet.conf | grep server

設定ファイルを直したあとは、systemd に変更を読み直させてから kubelet を再起動します。

systemctl daemon-reload
systemctl restart kubelet

原因 3: 証明書の問題 #

kubelet は apiserver と通信するときにクライアント証明書を使います。この証明書が失効すると、kubelet は起動していても apiserver に状態を報告できず、ノードが NotReady になります。ログに x509: certificate has expired or is not yet valid のような行が見えたら証明書失効です。

journalctl -u kubelet -n 50 --no-pager | grep -i x509

証明書失効はそれ自体で扱う内容が多いため、#25 Troubleshooting 4 で kubelet 証明書の自動更新と手動更新を別に整理します。今回の記事では NotReady の原因候補に証明書失効がある という点だけ覚えておけば十分です。

原因 4: コンテナランタイムが停止した #

kubelet はコンテナを直接起動せず、CRI を通じてコンテナランタイム (通常 containerd) に委任します。ランタイムが死ぬと kubelet は Pod を起動できず、ノードが NotReady になります。ログに failed to get container runtime または connection refused が見えたらランタイムを確認します。

systemctl status containerd

# 止まっていれば再び起動
systemctl start containerd
systemctl enable containerd

# ランタイムが応答するか確認
crictl info
crictl ps

ランタイムを再び起動したあとは kubelet も再起動しておきます。ランタイムソケットのパスが間違っているケースなら、/var/lib/kubelet/kubeadm-flags.env--container-runtime-endpoint の値を確認します。

systemctl restart kubelet

原因 5: ディスクフルとメモリ圧迫 #

資源枯渇は condition にそのまま現れるので、describe node ですでに手がかりが得られます。DiskPressure=True ならディスクが閾値を下回った状態であり、kubelet はノードを保護するために Pod を eviction (退避) します。

# ディスク使用量を確認
df -h

# inode 枯渇も一緒に確認 (容量は残っているのに inode が埋まるケース)
df -i

ディスクが埋まっているなら、最も安全な回収対象は 使っていないコンテナイメージ です。

# 未使用イメージの整理
crictl rmi --prune

# 終了したコンテナの整理
crictl rm $(crictl ps -a -q --state Exited)

ログファイルが肥大化したケースも多いので、/var/log の下で容量が大きいファイルも一緒に点検します。ディスクを確保すると kubelet が再び DiskPressure=False を報告し、ノードが Ready に戻ります。

MemoryPressure=True はノードの利用可能メモリが eviction 閾値を下回った状態です。

# メモリ使用量
free -h

# メモリを多く使うプロセス
top -o %MEM

特定の Pod がノードメモリを過度に使うケースなら、そのワークロードの requests/limits を調整するか (#15 リソース管理)、他のノードへ分散するのが根本解決です。試験では通常、原因が明確な 1 つに設計されるので、condition が指す資源を回収することに集中すればよいです。

症状別の診断表 #

ここまでの流れを一目で整理します。NotReady に出会ったら、この表に沿って絞っていけばよいです。

症状 / 手がかり疑う原因確認コマンド解決
Ready=Unknown、“Kubelet stopped posting”kubelet 停止systemctl status kubeletsystemctl start/restart kubelet
kubelet が起動してすぐ死ぬ設定ファイルエラーjournalctl -u kubelet/var/lib/kubeletkubelet.conf 修正後 daemon-reload
ログに x509 ... expired証明書失効journalctl -u kubelet | grep x509証明書更新 (#25)
ログに runtime ... connection refusedランタイム停止systemctl status containerdsystemctl start containerd 後に kubelet 再起動
DiskPressure=Trueディスクフルdf -hdf -icrictl rmi --prune、ログ整理
MemoryPressure=Trueメモリ圧迫free -htopワークロード分散、requests/limits 調整
Ready=False、Reason がネットワーク系CNI プラグインの問題kubelet ログの NetworkPluginNotReadyCNI Pod 状態を確認 (#20)

問題ノードを隔離する: cordon と drain #

ノードを直している間、新しい Pod がそのノードへスケジューリングされないように防いだり、上にある Pod を安全に空けたりする必要があるときがあります。このとき使うコマンドが cordondrain です。

# 新しい Pod のスケジューリングだけ防ぐ (既存 Pod はそのまま)
k cordon node01

# 既存 Pod まで他のノードへ移す (cordon を含む)
k drain node01 --ignore-daemonsets --delete-emptydir-data

# 作業が終わったら再びスケジューリングを許可
k uncordon node01

drain はノードを点検・アップグレードする前に、その上のワークロードを安全に他のノードへ再配置します。DaemonSet Pod はすべてのノードに起動する必要があるため --ignore-daemonsets でスキップし、emptyDir ボリュームを使う Pod があれば、データ損失を認識したという意味で --delete-emptydir-data を付けないと進みません。drain を実行すると、そのノードは自動的に cordon 状態になります。

cordon されたノードは k get nodesSchedulingDisabled と表示されます。

NAME     STATUS                     ROLES    AGE   VERSION
node01   Ready,SchedulingDisabled   <none>   40d   v1.31.0

復旧が終わったら必ず uncordon でスケジューリングを再び開く必要があります。これを忘れるとノードは Ready なのに新しい Pod が上がってこず、あとで別の問題と誤認しやすくなります。

NotReady ノードの taint #

ノードが NotReady になると、control plane は自動で taint を付けて Pod スケジューリングを防ぎます。どの taint が付いているか見ると、ノード状態を素早く読めます。

k describe node node01 | grep -i taint
Taints:  node.kubernetes.io/not-ready:NoSchedule

主なノード taint は次のとおりです。これらはノード状態に応じて Kubernetes が自動で付与します。

Taint付与条件
node.kubernetes.io/not-readyノードの Ready=False
node.kubernetes.io/unreachableノードの Ready=Unknown (連絡断)
node.kubernetes.io/disk-pressureDiskPressure=True
node.kubernetes.io/memory-pressureMemoryPressure=True
node.kubernetes.io/unschedulablecordon されたノード

これらの taint はノードが正常に戻ると 自動で除去 されます。つまり kubelet を直してノードが再び Ready になると not-ready taint も消え、スケジューリングが正常化します。taint を手で剥がすのではなく、根本原因を直すこと が正解です。taint と toleration の動作は #14 Scheduling 2 で詳しく扱いました。

核心は kubectl レベル (describe) から始めてノードレベル (systemctl/journalctl) へ降りる という方向性です。k get nodes で対象を見つけ、k describe node で conditions を読んで方向を定め、ノードに接続して kubelet とランタイムを直したあと、設定を変えたなら systemctl daemon-reload 後に restart kubelet で締めます。この順序がほぼすべてのノード障害にそのまま適用されます。

試験ポイント #

  • describe node の conditions を先に読みます。 Ready=Unknown なら kubelet 報告の断、pressure 系 True なら資源枯渇です。ノードに接続する前に方向を定めると時間を節約できます。
  • kubelet は systemd ユニットです。 systemctl status kubeletjournalctl -u kubelet が手に馴染んでいる必要があります。kubelet 停止は systemctl enable --now kubelet で一度に起動できます。
  • 設定を直したら daemon-reload を忘れないようにします。 systemd ユニットファイルや引数を変えたあと daemon-reload なしで再起動すると、変更が反映されません。
  • ランタイムも候補です。 systemctl status containerdcrictl info でランタイム停止を素早く除外します。
  • ディスクは df -hdf -i の両方を見ます。 容量は残っているのに inode が埋まって DiskPressure が出るケースを見落としやすいです。
  • taint は手で剥がしません。 原因を直せば自動で消えます。taint を強制的に除去しても根本原因が残っていれば、ノードは再び NotReady になります。

まとめ #

この記事で押さえたこと:

  • ノード NotReady の診断は describe から始めてノードのシステムレベルへ降ります。 conditions (Ready/MemoryPressure/DiskPressure/PIDPressure) が方向を定めてくれます
  • kubelet はノードエージェントです。 systemctl status kubeletjournalctl -u kubelet で停止・設定エラー・証明書・ランタイムの問題を切り分けます
  • よくある 5 つの原因。 kubelet 停止、設定エラー (/var/lib/kubelet/etc/kubernetes/kubelet.conf)、証明書失効、ランタイム停止 (containerd)、ディスクフルとメモリ圧迫です
  • cordon と drain で隔離します。 点検前にワークロードを安全に空け、復旧後 uncordon でスケジューリングを再び開きます
  • NotReady taint は自動です。 node.kubernetes.io/not-ready は原因を直せば自動で除去されます

次へ — Troubleshooting 3 #

ノードを直しました。ここからより危険な層、control plane 自体がダウンした状況 へ上がっていきます。

#24 Troubleshooting 3: Control plane (apiserver/etcd/scheduler ダウン)、etcd リカバリ では、kube-apiserver が応答しないときに static Pod マニフェスト (/etc/kubernetes/manifests) とコンテナランタイムを直接覗き込む方法、scheduler と controller-manager が死ぬとクラスターに何が起きるのか、そして etcd が壊れたときにスナップショットでリカバリする手順まで扱います。

X