何が起きたか
クラスターを確認したところ、ポッドが Pending 状態のまま止まっていることに気づきました。kubectl get nodes を実行すると、1つ以上のノードが NotReady ステータスになっています。コントロールプレーンのイベントログにはこう記録されています:
Node node-1 status is now: NodeNotReady
スケジューラーはすぐにそのノードへの新しいポッドの配置を停止します。そこで既に動いているポッドは、tolerations の設定によっては退避させられる場合があります — デフォルトでは通常5分後です。実際の原因を特定しましょう。
クイックトリアージ — まず確認すること
何よりも先に、影響を受けているノードにSSHでログインしてください。根本原因のほとんどは、APIサーバーではなくノード自体にあります。
1. ノードのステータスとコンディションを確認する
kubectl get nodes
kubectl describe node node-1
describe の出力にある Conditions セクションまでスクロールしてください。4つのフィールドがほぼすべてを教えてくれます:
- Ready —
Trueであるべきです。FalseまたはUnknownの場合、kubelet がコントロールプレーンと通信できていません。 - MemoryPressure / DiskPressure / PIDPressure — いずれかが
Trueの場合、ノード上のリソースが枯渇しています。 - NetworkUnavailable — CNIプラグインの問題を示しています。
2. ノード上のkubeletのステータスを確認する
systemctl status kubelet
journalctl -u kubelet -n 100 --no-pager
kubelet はノードの健全性をコントロールプレーンに報告するエージェントです。クラッシュまたは停止すると、ノードは40秒以内に NotReady に切り替わります — これはデフォルトの node-monitor-grace-period です。ジャーナルのログは、最初の10行以内に何が問題だったかをほぼ必ず教えてくれます。
よくある原因と対処法
原因1:kubelet が停止またはクラッシュしている
十中八九これが原因です。kubelet プロセスが静かに終了し、何も再起動させていないのです。
# kubelet を再起動する
systemctl restart kubelet
systemctl enable kubelet
# ノードの回復を監視する
kubectl get nodes -w
それでもクラッシュする場合は、リアルタイムでログを追いましょう:
journalctl -u kubelet -f
繰り返し現れるサブ原因が3つあります:/var/lib/kubelet/config.yaml の設定ミス、TLS証明書の期限切れ、cgroupドライバーの不一致です。最後のものがよく引っかかります — ノードは cgroupfs で動いているのに、クラスターは systemd で設定されている、またはその逆のケースです。
不一致を確認するには:
# Docker ランタイム
docker info | grep -i cgroup
# containerd ランタイム
containerd config dump | grep -i cgroup
# kubelet が期待する設定
cat /var/lib/kubelet/config.yaml | grep cgroupDriver
異なっている場合は、/var/lib/kubelet/config.yaml をランタイムに合わせて更新し、kubelet を再起動してください。
原因2:ディスクプレッシャー
ディスクは、負荷の高いノードでは多くの人が予想するよりも速く埋まります。使用量が退避閾値(デフォルトは85%)を超えると、kubelet は DiskPressure=True を設定し、ノードをスケジュール不可にします。
# ノード上で実行
df -h
du -sh /var/lib/docker/* # Docker ランタイム
du -sh /var/lib/containerd/ # containerd ランタイム
# 退避メッセージを確認する
journalctl -u kubelet | grep -i evict
コンテナイメージと古いログが通常の原因です。すぐに片付けましょう:
# Docker
docker system prune -af
# containerd
crictl rmi --prune
# 古いジャーナルログを削除する
journalctl --vacuum-time=3d
原因3:メモリプレッシャーまたはOOMキル
# 最近のOOMキルを確認する
dmesg | grep -i oom
cat /proc/meminfo | grep -i available
カーネルのOOMキラーは素早く動作します — メモリを回収するために kubelet 自体を終了させることもあります。kubelet を再起動すればノードは復帰しますが、ポッドにリソース制限がなければ、1時間後に同じことが再発します。メモリを消費しているものを特定し、該当するポッドに resources.limits.memory を追加してください。
原因4:CNIプラグインの障害(NetworkUnavailable)
NetworkUnavailable=True のコンディションは、CNIレイヤー(Flannel、Calico、Cilium、Weave、その他使用中のもの)を直接示しています。プラグインがクラッシュするか、その特定のノード上でステートを失っています。
# 壊れたノード上で動いている CNI ポッドを見つける
kubectl get pods -n kube-system -o wide | grep -E 'flannel|calico|cilium|weave'
# ログを確認する
kubectl logs -n kube-system <cni-pod-on-node-1>
# CNI バイナリと設定がノード上に存在するか確認する
ls /opt/cni/bin/
ls /etc/cni/net.d/
CNI ポッドを削除して Kubernetes に新たにスケジュールさせましょう。再初期化によって通常は問題が解消されます:
kubectl delete pod -n kube-system <cni-pod-on-node-1>
原因5:クロックスキューまたは証明書の期限切れ
クロックがずれるとTLS認証が壊れます。2分を超えるスキューは、APIサーバーが kubelet の証明書を完全に拒否する原因になります。
# ノードの時刻と期待値を比較する
date
timedatectl status
# 即座に再同期する
chronyc makestep # chrony ユーザー向け
ntpdate -u pool.ntp.org # ntp ユーザー向け
証明書の期限切れは別の問題です — カレンダーベースで発生します。kubeadm が発行した証明書はちょうど1年で期限切れになります。コントロールプレーンから確認・更新してください:
# すべての証明書の有効期限を確認する
kubeadm certs check-expiration
# 一括で全て更新する
kubeadm certs renew all
systemctl restart kubelet
原因6:コントロールプレーンからノードに到達できない
APIサーバーはポート10250でkubeletと通信します。ファイアウォールルールの変更、誤設定されたセキュリティグループ(クラウドインフラ更新後によく発生)、またはノードがオフラインになることで、同じ NotReady の症状が現れます。
# コントロールプレーンのノードから直接テストする
curl -k https://<node-ip>:10250/healthz
# ノード上の iptables を確認する
iptables -L -n | grep 10250
# firewalld の場合
firewall-cmd --list-all
ノードが復帰したことを確認する
# ノードが Ready になっているか確認する
kubectl get nodes
# 期待される出力:
# NAME STATUS ROLES AGE VERSION
# node-1 Ready <none> 5d v1.28.0
# コンディションがクリアか確認する
kubectl describe node node-1 | grep -A 10 Conditions
# スケジューリングのクイックテストを実行する
kubectl run test-pod --image=nginx --restart=Never
kubectl get pod test-pod -o wide
# クリーンアップ
kubectl delete pod test-pod
インシデント中にノードがcordonされていませんでしたか?このステップを忘れずに:
kubectl uncordon node-1
メンテナンス前にcordonとdrainを行う
カーネルのアップグレードやディスクのクリーンアップを行う場合は、必ず先にノードをdrainしてください — リクエストの途中でポッドが強制終了されるより、適切に退避させる方がよいです。
# 新しいポッドがここに配置されないようにする
kubectl cordon node-1
# 既存のポッドを安全に退避させる
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
# メンテナンス作業を行う...
# ノードをローテーションに戻す
kubectl uncordon node-1
次回に向けて改善すること
- ディスクのアラートは85%ではなく70%に設定する。 kubelet が85%で退避をトリガーする頃には、既に悪い状況です。70%のアラートなら、本番インシデントなしに片付けるための15ポイントの余裕があります。DiskPressure は他のどの原因よりも多くの人を驚かせます。
- 最初にAPIサーバーではなくkubeletのログを確認する。 ジャーナルはほぼ必ず最初の10行以内に正確な障害を示します。まずそこを見てください。
- クロック同期は任意ではない。 クラウド環境ではVMがライブマイグレーション後にずれることがあります — 数分ずれることも。chronyまたはntpdを実行し、あれば嬉しい機能ではなくインフラとして扱ってください。
- node-problem-detectorをデプロイする。 node-problem-detector DaemonSet は、カーネルパニックやランタイム障害が NotReady にエスカレートする前に、ノードコンディションとして表面化します。
helm install node-problem-detector deliveryhero/node-problem-detectorで2分でインストールできます。 - 証明書の期限をopsカレンダーで管理する。 kubeadmの証明書は固定日から1年後に期限切れになります。11ヶ月時点で更新リマインダーを設定してください — サプライズとして扱うのは避けられます。

