Node 排障
Node 是 Pod 运行的基础底层,Node 不可用会直接影响其上所有 Pod。
排查口诀: Node NotReady 不要慌,Conditions 先看看;kubelet 日志是王道,磁盘空间占比高;容器运行时不能忘,cgroup 一致很重要;证书过期要检查,网络连通少不了。
Kubelet 节点状态机制
kubelet 在每个 nodeStatusReportFrequency 周期(默认 10 秒)内向 API Server 更新节点状态。判断逻辑在 kube-controller-manager 的 nodecontroller 中:
- kubelet 每 10 秒上报一次节点状态(可通过
--node-status-update-frequency修改) - nodecontroller 在节点连续 40 秒未更新时将状态置为 Unknown
- 节点处于 Unknown 超过 5 分钟(
pod-eviction-timeout)后,触发 Pod 驱逐 - 如果超过
termination-duration(默认 5 分钟)仍未恢复,节点被彻底标记为 NotReady
kubelet 报告的 Ready 条件是一个组合条件,任何一个检查失败都会导致 NotReady:
Conditions:
Type Status Reason
MemoryPressure False KubeletHasSufficientMemory
DiskPressure True KubeletHasDiskPressure ← 磁盘压力
PIDPressure False KubeletHasSufficientPID
NetworkUnavailable False RouteCreated
Ready False KubeletNotReady ← 综合结果
排查路径总览
- 确认 NotReady 现象和受影响范围(
kubectl describe node) - 检查 kubelet 进程(
systemctl status kubelet/journalctl) - 检查容器运行时(containerd/docker / crictl)
- 检查节点资源(
df -h/free -m/uptime) - 检查网络连通性(ping / curl / CNI)
- 检查证书是否过期(
kubeadm certs check-expiration) - 常见根因场景与修复
- 验证修复结果
- 预防措施
Step 1 — 确认 NotReady 现象和受影响范围
# 查看所有节点状态
kubectl get nodes -o wide
# 过滤 NotReady 节点
kubectl get nodes | grep -v Ready
# 查看节点详情(重点关注 Conditions 段最后几行)
kubectl describe node <node-name>
Conditions 含义速查:
| Condition | True 含义 | 常见根因 |
|---|---|---|
| MemoryPressure | 内存不足 | 节点内存耗尽,OOM risk |
| DiskPressure | 磁盘不足 | 日志/镜像占满磁盘,最常⻅ |
| PIDPressure | 进程数超限 | 进程数接近 pid_max |
| NetworkUnavailable | 网络不可用 | CNI 配置错误 |
| Ready=False | kubelet 未就绪 | 以上任一或多个原因的组合 |
Step 2 — 检查 kubelet 进程
kubelet 是节点上最核心的 K8s 组件。进程挂了 → 节点立即 NotReady。
查看 kubelet 状态
sudo systemctl status kubelet
# Active: active (running) → 正常
# Active: failed (Result: start-limit-hit) → 启动失败,立刻查看日志
查看 kubelet 日志
# 最近 300 行
sudo journalctl -u kubelet -n 300 --no-pager
# 按故障时间过滤
sudo journalctl -u kubelet --since "2026-04-29 09:00:00" --no-pager
# 搜索错误
sudo journalctl -u kubelet -b --no-pager | grep -i error
常见日志错误
| 错误类型 | 典型日志 | 排查方向 |
|---|---|---|
| 容器运行时连接失败 | failed to load Kubelet config file / context deadline exceeded |
检查 kubelet 的 --container-runtime-endpoint 和 containerd 配置是否一致 |
| 证书相关 | unable to load client CA file |
证书文件缺失或过期,见 Step 6 |
| ETCD 连接超时 | request canceled |
etcd 健康状态和网络连通性 |
| OOM kill | 见 dmesg 或 journalctl 中 OOM 记录 | 节点内存不足,见 Step 4 |
⚠️ 重启 kubelet
重启 kubelet 会导致该节点上的 Pod 被驱逐和重新调度。生产环境执行前必须确认副本数足够。
# 先评估影响
kubectl get pods -o wide --all-namespaces | grep <node-name>
# 重启
sudo systemctl restart kubelet
sleep 30
kubectl get node <node-name>
Step 3 — 检查容器运行时
containerd 排查
# 查看 containerd 服务状态
sudo systemctl status containerd
sudo journalctl -u containerd -n 200 --no-pager
# 使用 crictl 检查运行时
sudo crictl info
# 报错 "runtime endpoint not connected" → containerd 异常
# 检查 containerd socket 文件
ls -la /run/containerd/containerd.sock
# 查看容器和镜像
sudo crictl ps -a
sudo crictl images
dockerd 排查(如果使用 Docker 运行时)
sudo systemctl status docker
sudo journalctl -u docker -n 200 --no-pager
sudo docker ps -a
sudo docker info
# docker info 中重点关注 Cgroup Driver:
# docker 和 kubelet 的 cgroup driver 必须一致
cgroup driver 一致性检查
不一致会导致资源管理异常,节点出现莫名的内存压力或 CPU 限制失效:
grep SystemdCgroup /etc/containerd/config.toml
# 或
grep cgroupDriver /var/lib/kubelet/config.yaml
Step 4 — 检查节点资源
磁盘空间(最常见原因)
kubelet 的 DiskPressure 检测默认阈值:磁盘空间低于 10% 触发。
# 查看所有挂载点使用率,重点关注 / 和 /var/lib/containerd
df -h
# 查看 /var/lib 目录大小分布
sudo du -sh /var/lib/*
# 清理步骤(注意顺序):
# 1. 清理未使用的镜像(不要直接删 overlay2 目录!)
sudo ctr -n k8s.io images prune -f
# 2. 清理旧的 journal 日志
sudo journalctl --vacuum-time=3d
# 3. 清理已停止的容器
sudo crictl rm -f $(sudo crictl ps -a --state EXITED -q)
# 4. 清理大日志文件
sudo find /var/log -name "*.log" -size +100M -exec ls -lh {} \;
⚠️ 不要盲目删除
/var/lib/containerd/overlay2下的镜像文件,可能导致正在运行的容器异常。使用ctr -n k8s.io images prune清理。
内存压力(MemoryPressure)
free -m
# available 列低于总内存 10% → 内存压力
# 按内存排序查看进程
ps aux --sort=-%mem | head -20
# 查看 kubelet 内存使用
ps -p $(pgrep kubelet) -o pid,vsz,rss,comm
Pod 驱逐顺序: BestEffort → Burstable → Guaranteed。
CPU 负载
uptime
# load average 持续高于 CPU 核心数 → CPU 资源紧张
# CPU 不足一般不会直接导致 NotReady,除非导致 kubelet 心跳超时
PID 压力(PIDPressure)
cat /proc/sys/kernel/pid_max
ps aux | wc -l
# 进程数接近 pid_max 时触发
综合诊断脚本
#!/bin/bash
# node_diag.sh
echo "===== CPU 核心数和负载 ====="
nproc; uptime
echo "===== 内存使用 ====="; free -m
echo "===== 磁盘使用 ====="; df -h | grep -v tmpfs
echo "===== 进程 Top 10 ====="; ps aux --sort=-%cpu | awk 'NR==1{print} NR>1&&NR<=12'
echo "===== kubelet 状态 ====="; systemctl is-active kubelet
echo "===== containerd 状态 ====="; systemctl is-active containerd 2>/dev/null
echo "===== kubelet 最新错误 ====="; journalctl -u kubelet -n 20 --no-pager | grep -i error
Step 5 — 检查网络连通性
# 1. 测试到 API Server 的连通性
APISERVER=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}')
curl -sk --max-time 5 ${APISERVER}/healthz
# 正常返回: {"status":"ok"}
# 2. 测试 DNS 解析
ping -c 3 kubernetes.default.svc
# 3. 检查网络接口和路由
ip addr
ip route
# 4. 检查 CNI 插件
# Flannel:
ip addr | grep flannel
kubectl logs -n kube-system -l app=flannel --tail=50
# Calico:
kubectl get pods -n kube-system -l k8s-app=calico-node
calicoctl node status
calicoctl get ippool -o wide
# 5. 检查 NetworkPolicy(罕见但需排除)
kubectl get networkpolicy -A
Step 6 — 检查证书是否过期
kubelet 证书默认有效期 1 年(kubeadm 部署的集群)。集群运行超过 1 年后首次出现,或手动调整过系统时间后出现。
# SSH 到目标节点(节点 SSH 安全配置参见 [[ssh-brute-force-protection-guide]])
# 检查控制面证书
sudo kubeadm certs check-expiration --cert-dir /etc/kubernetes/pki
# 检查 kubelet 自身证书(存放在不同位置)
sudo openssl x509 -in /var/lib/kubelet/pki/cert.crt -noout -dates
date # 对比当前时间
# 续期(推荐方式)
sudo cp -r /etc/kubernetes/pki /etc/kubernetes/pki.bak.$(date +%Y%m%d)
sudo kubeadm alpha certs renew kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf
# 紧急方式:删旧证书让 kubelet 自动重新申请
sudo systemctl stop kubelet
sudo rm -rf /var/lib/kubelet/pki/*
sudo systemctl start kubelet
Step 7 — 常见根因场景与修复
场景一:磁盘空间不足(占比最高)
诊断: df -h 磁盘使用率 > 90%,kubectl describe node 显示 DiskPressure=True
修复:
sudo ctr -n k8s.io images prune -f # 清理未使用镜像
sudo journalctl --vacuum-size=500M # 清理 journal
sleep 120 && kubectl get node <node> # 等待自动恢复
场景二:kubelet 进程 OOM(占比第二)
诊断:
sudo dmesg | grep -i "out of memory" | grep -i "kubelet"
sudo journalctl -xb | grep -i "oom" | tail -20
修复: 设置 kubelet 的 OOM Score Adj 为负数(更不容易被 kill)
cat /proc/$(pgrep kubelet)/oom_score_adj # 当前值
# 编辑 /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
# 添加: Environment="KUBELET_OPTS=--oom-score-adj=-999"
根本解决: 增加节点内存或减少 Pod 数量。
场景三:容器运行时配置不一致
诊断: containerd 和 kubelet 的 cgroup driver 不匹配。
修复: 修改 /etc/containerd/config.toml → sudo systemctl restart containerd → sudo systemctl restart kubelet
⚠️ 重启 containerd 会导致节点上所有容器停止,对有状态应用有严重影响。
场景四:内核问题
诊断: uname -r 查看版本;sudo dmesg -T | grep -iE "error|warn|fail|timeout"
修复: 升级内核或重装节点。
场景五:kubelet 配置文件错误
诊断: ps aux | grep kubelet 查看启动命令;手动执行 kubelet 看报错
常见错误: cgroupDriver 不一致、evictionHard 格式错误、serializeImagePulls 值类型错误。
场景六:etcd 连接超时(影响所有节点)
诊断:
kubectl exec -n kube-system <etcd-pod> -- etcdctl endpoint health \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--key=/etc/kubernetes/pki/etcd/server.key
修复: 恢复 etcd 集群。etcd 恢复后节点自动恢复。
Step 8 — 验证修复结果
# 1. 节点状态
kubectl get nodes -o wide
kubectl describe node <node-name> | grep -A 20 "Conditions"
# 2. Pod 调度
kubectl get pods -o wide --all-namespaces | grep <node-name>
kubectl get pods --all-namespaces | grep -v Running | grep -v Completed
# 3. 业务功能
kubectl get deployment -A
# 4. 节点资源
ssh <node> "df -h / && free -m && uptime"
Step 9 — 预防措施
9.1 节点健康巡检脚本
#!/bin/bash
# k8s_node_health_check.sh(建议 cron 每日执行)
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
for NODE in $NODES; do
STATUS=$(kubectl get node $NODE -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
[ "$STATUS" != "True" ] && echo "[ALERT] NotReady: $NODE"
DISK=$(ssh $NODE "df -h / | tail -1 | awk '{print \$5}' | sed 's/%//'")
[ "$DISK" -gt 85 ] && echo "[ALERT] Disk $DISK%: $NODE"
done
9.2 配置 eviction threshold
# /var/lib/kubelet/config.yaml
evictionHard:
memory.available: "100Mi"
nodefs.available: "5%"
imagefs.available: "10%"
evictionSoft:
memory.available: "200Mi"
nodefs.available: "10%"
imagefs.available: "15%"
evictionSoftGracePeriod:
memory.available: "2m"
nodefs.available: "2m"
imagefs.available: "2m"
evictionPressureTransitionPeriod: "2m"
9.3 证书自动续期
kubeadm 部署的集群 kubelet 会自动续期。手动管理证书时建立 crontab:
0 3 1 * * /usr/bin/kubeadm certs renew all --kubeconfig=/etc/kubernetes/admin.conf
9.4 Prometheus 告警示例
- alert: NodeDiskPressure
expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.15
for: 5m
labels:
severity: warning
快速定位表
| 现象 | 第一步排查命令 | 常见根因 |
|---|---|---|
| DiskPressure=True | df -h |
磁盘占满(日志/镜像) |
| MemoryPressure=True | free -m / kubectl top pod |
节点内存耗尽 |
| kubelet 进程挂 | systemctl status kubelet |
OOM、配置错误 |
| 容器运行时异常 | systemctl status containerd |
cgroup 不一致、socket 丢失 |
| 网络不可达 | curl -sk <apiserver>/healthz |
CNI 异常、防火墙 |
| 证书过期 | openssl x509 -in ... -noout -dates |
超过 1 年未续期 |
关联页面
| 页面 | 关联点 |
|---|---|
| k8s-service-access-troubleshooting | 服务访问排查(Pod 排障前置条件) |
| pod-troubleshooting | Pod 排障(节点 NotReady 后 Pod 被驱逐的后续排查) |
| service-troubleshooting | Service 排障(节点恢复后的网络排查) |
| k8s-production-incident-case-studies | K8s 生产 10 大故障复盘(节点 NotReady/etcd 磁盘等实战案例) |
| k8s-troubleshooting-principles | 通用排障原则 |
| network-troubleshooting-order | 服务器网络排障(节点网络排查参考) |
| server-performance-four-dimensions | 服务器四维排障(资源深度排查) |
| linux-load-average-guide | Load Average 解读(CPU 异常分析) |
| resource-rbac-scheduling-troubleshooting | 资源配额与调度排障 |