问题背景
生产环境中 Kubernetes 集群的节点突然变成 NotReady 是非常常见的故障场景。节点一旦进入 NotReady 状态,该节点上调度的 Pod 会持续处于 Pending 或者被驱逐的状态,如果集群规模较小或者副本数不足,会直接导致业务服务中断。NotReady 本身不是根因,只是一个症状表现——它背后的原因可能是kubelet 进程崩溃、容器运行时异常、网络不通、etcd 连接故障、磁盘空间不足、内核问题、证书过期等等。不同原因对应的处理方式完全不同,如果不做系统排查,上来就重启节点,很可能把现场破坏掉,导致问题复现困难。
这篇文章面向初中级 Kubernetes 运维工程师,以一个真实的 NotReady 故障为线索,完整走一遍从现象观察、层层排查、定位根因到修复验证的全过程。文章涉及的所有排查命令均在 Kubernetes 1.24 及以上版本验证通过,部分命令在更早版本中表现可能略有差异,文中会做说明。所有涉及破坏性操作(如删除 Pod、驱逐节点、重启服务)的命令均会标注风险级别和必要的确认步骤。
适用场景
集群中有节点显示 NotReady 状态超过 5 分钟仍未恢复
业务 Pod 被驱逐,副本数下降,监控告警触发
新增节点加入集群后始终无法 Ready
节点运行一段时间后自发变成 NotReady(而非升级或维护导致)
升级 K8s 版本后部分节点 NotReady
集群节点数较少(小于 3),单节点 NotReady 已影响核心业务
核心知识点:Kubelet 节点状态机制
理解节点 NotReady 的根因之前,必须先弄清楚 kubelet 是如何向 kube-apiserver 上报节点状态的。kubelet 会在每个 nodeStatusReportFrequency 周期(默认 10 秒)内向 API Server 更新节点状态。如果 kubelet 连续失败超过某个阈值,节点状态就会被标记为 Unknown 或 NotReady。
具体判断逻辑在 kube-controller-manager 的 nodecontroller 中:
kubelet 每 10 秒上报一次节点状态(可通过 --node-status-update-frequency 修改,但不要盲目调大)
nodecontroller 会在节点连续 40 秒未更新时将状态置为 Unknown
节点处于 Unknown 状态超过 5 分钟(pod-eviction-timeout,默认 5 分钟)后,RC/Deployment 的 replicas 控制器会触发 Pod 驱逐
如果 kubelet 能恢复,则节点状态恢复为 Ready;如果超过 termination-duration(默认 5 分钟)仍未恢复,节点会被彻底标记为 NotReady
需要注意的是,kubelet 报告的 Ready 条件是一个组合条件,由多个 Condition 组成:
Conditions: Type Status MemoryPressure False DiskPressure False PIDPressure False NetworkUnavailable False kubeletReady True <-- 这个综合状态由 kubelet 自己计算
kubeletReady 这个 Condition 如果不是 True,在 kubectl get node 输出中就会显示 NotReady。但实际上 kubelet 内部还会细分:它会检查容器运行时是否可用、根文件系统是否可写、节点网络是否配置正确等。任何一个检查失败都可能导致 kubeletReady 变为 False,从而导致节点 NotReady。
排查路径总览
当发现节点 NotReady 时,推荐按以下顺序排查,避免走弯路:
1. 确认 NotReady 现象和受影响范围 └── kubectl get node(看节点状态和年龄) └── kubectl describe node(看 Reason 和 Condition) 2. 检查 kubelet 是否在运行 └── systemctl status kubelet └── journalctl -u kubelet -n 200 --no-pager 3. 检查容器运行时是否正常 └── systemctl status containerd(或 docker) └── crictl info / docker info 4. 检查节点资源是否耗尽 └── df -h(磁盘) └── free -m(内存) └── top / uptime(CPU 负载) 5. 检查网络连通性 └── ping(api server) └── curl -k https:// /healthz └── hostname -i / ip addr 6. 检查证书是否过期 └── kubeadm certs check-expiration └── openssl x509 -in /var/lib/kubelet/pki/cert.crt -noout -dates 7. 定位根因并修复 8. 验证恢复 └── kubectl get node └── 在节点上跑一个测试 Pod
以上顺序不是绝对的,要根据现场实际情况灵活调整。比如磁盘空间不足是最容易快速确认的项目,优先检查;如果节点一开始是 Ready 后来突然 NotReady,则 kubelet 进程问题概率更大;如果新增节点始终无法 Ready,则网络配置或 kubelet 启动参数问题概率更大。
第一步:确认 NotReady 现象和受影响范围
用 kubectl 连接目标集群(如果本地无法连接集群,可以找一台有 kubectl 配置的节点):
# 查看所有节点状态,-o wide 看更多信息 kubectl get nodes -o wide # 输出示例: # NAME STATUS ROLES AGE VERSION # k8s-master Ready control-plane 45d v1.28.4 # k8s-node-1 NotReady30d v1.28.4 # k8s-node-2 Ready 30d v1.28.4
如果节点数量较多,可以用 grep 过滤:
kubectl get nodes | grep -v Ready
确认 NotReady 节点名称和数量后,用 describe 查看详细信息:
kubectl describe node k8s-node-1
describe 输出的最后几行 Conditions 是最关键的:
Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- MemoryPressure False 2026-04-29T1034Z 2026-04-29T0812Z KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure True 2026-04-29T1034Z 2026-04-29T1000Z KubeletHasDiskPressure kubelet has disk pressure; low disk space PIDPressure False 2026-04-29T1034Z 2026-04-29T0812Z KubeletHasSufficientPID kubelet has sufficient PID available NetworkUnavailable False 2026-04-29T1034Z 2026-04-29T0812Z RouteCreated Route created successfully kubeletReady False 2026-04-29T1034Z 2026-04-29T1000Z KubeletNotReady kubelet status is (matchNodeConditionMode)
从上面这个示例可以看到,DiskPressure 为 True 且 kubeletReady 为 False,Message 中明确写了"low disk space"。这就是根因线索——磁盘空间不足导致 kubelet 认为节点不健康。
但要注意,DiskPressure 和 kubeletReady 是两个独立的 Condition。kubeletReady 为 False 不一定是因为 DiskPressure,也可能是因为容器运行时无响应、容器网络配置错误等。需要综合判断。
再看 Nodes Conditions 中 NotReady 对应的 Reason 字段,常见的原因值如下:
| Reason | 含义 | 常见根因 |
|---|---|---|
| KubeletNotReady | kubelet 未就绪 | kubelet 进程崩溃、容器运行时异常、磁盘/内存压力 |
| NodeNotReady | 节点整体不健康 | kubelet 完全无响应、证书过期、网络不通 |
| KubeletHasNoDiskPressure | kubelet 认为磁盘正常 | 仅状态展示用,不表示问题 |
| MemoryPressure | 内存压力 | 节点内存耗尽 |
| NetworkUnavailable | 网络不可用 | CNI 配置错误、节点网络未正确配置 |
接下来按照不同场景分别讲解排查步骤。
第二步:检查 kubelet 进程是否正常运行
kubelet 是节点上最核心的 K8s 组件。如果 kubelet 进程挂了,节点状态立即就会变成 NotReady。因此排查的第一步就是确认 kubelet 进程状态。
2.1 查看 kubelet 服务状态
# 查看 kubelet 服务状态(systemd 管理方式) sudo systemctl status kubelet # 如果是 containerd 运行时,可能看到: # kubelet.service - kubelet: The Kubernetes Node Agent # Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled) # Active: active (running) since ... # 或者 # Active: failed (Result: start-limit-hit) since ...
如果看到 Active: failed,说明 kubelet 启动失败了。需要立即查看日志。
2.2 查看 kubelet 日志
# 查看最近 300 行日志,不分页直接输出 sudo journalctl -u kubelet -n 300 --no-pager # 从头查看(启动日志) sudo journalctl -u kubelet --no-pager | head -500 # 查看某个时间点之后的日志(结合故障时间) sudo journalctl -u kubelet --since "2026-04-29 0900" --no-pager # 查看 kubelet 启动失败的相关日志 sudo journalctl -u kubelet -b --no-pager | grep -i error
kubelet 日志中最常见的错误类型和处理方式如下。
错误类型一:容器运行时连接失败
Failed to run Kubelet: failed to load Kubelet config file "/var/lib/kubelet/config.yaml": error adding watch on /var/lib/kubelet/cpu_manager_state": context deadline exceeded
这类错误通常是 kubelet 启动参数和容器运行时配置不匹配。如果是 Docker 切换到 containerd,或者升级了容器运行时版本后出现,重点检查 /etc/containerd/config.toml 和 kubelet 的 --container-runtime-endpoint 参数是否一致。
错误类型二:证书相关
Failed to start kubelet: unable to load client CA file /etc/kubernetes/pki/kubelet-client-latest/current: open /etc/kubernetes/pki/kubelet-client-latest/current: no such file or directory
证书文件缺失或不完整,通常发生在手动修改了 /etc/kubernetes/ 目录之后,或者升级过程中证书没有正确续期。可以用以下命令检查证书状态:
# 检查 kubelet 证书是否过期(需要先 SSH 到目标节点) sudo kubeadm certs check-expiration --cert-dir /etc/kubernetes/pki # 手动检查某个具体证书 sudo openssl x509 -in /var/lib/kubelet/pki/cert.crt -noout -dates
错误类型三:ETCD 连接超时
Failed to start kubelet: Post "https://192.168.1.10:2379/vote": net/http: request canceled
说明 kubelet 无法连接到集群的 etcd。可能是网络问题,也可能是 etcd 本身响应慢。如果集群所有节点同时出现这个问题,则 etcd 故障的概率更高;如果只有某一个节点出现,则该节点的网络配置问题概率更大。
2.3 手动重启 kubelet
风险提醒:重启 kubelet 会导致该节点上的 Pod 被驱逐和重新调度,对业务有影响。生产环境中执行前必须:1)确认业务副本数足够撑过驱逐和重新调度;2)通知相关业务方;3)准备好回滚方案。
# 先查看节点上运行了哪些 Pod,评估影响 kubectl get pods -o wide --all-namespaces | grep k8s-node-1 # 确认无误后,重启 kubelet sudo systemctl restart kubelet # 查看重启后的状态 sudo systemctl status kubelet sleep 30 kubectl get node k8s-node-1
如果 kubelet 重启后能正常启动,节点恢复 Ready,则说明是临时故障(比如 OOM 杀掉了 kubelet)。但这只是治标,要找到根因,还需要结合后面的步骤分析日志,判断是什么导致的 kubelet 崩溃。
第三步:检查容器运行时是否正常
容器运行时(containerd 或 dockerd)是 kubelet 工作的前提。如果容器运行时异常,kubelet 虽然进程在,但无法创建/管理容器,对应的 Pod 状态会变成 ContainerCreating 或 Error,同时 kubelet 会认为节点不健康。
3.1 检查 containerd 服务状态
# 查看 containerd 服务状态 sudo systemctl status containerd # 查看 containerd 日志 sudo journalctl -u containerd -n 200 --no-pager # 检查 containerd 进程是否存活 ps aux | grep containerd | grep -v grep
3.2 使用 crictl 检查容器运行时
crictl 是 containerd 提供的命令行工具,用于直接和 containerd 通信,绕过 kubelet 排查问题。
# 检查 containerd 版本和信息 sudo crictl info # 如果报错 "runtime endpoint not connected",说明 containerd 未正常响应
正常情况下,crictl info 会输出 containerd 的配置信息,包括存储驱动、网络插件等。如果这一步失败,问题很可能在 containerd 本身。
# 查看 containerd 的 socket 文件是否存在 ls -la /run/containerd/containerd.sock ls -la /var/run/containerd/containerd.sock
socket 文件不存在通常意味着 containerd 进程没有正常创建 socket,或者 containerd 服务配置了非默认路径。
3.3 检查 dockerd 服务(如果使用 docker 作为运行时)
sudo systemctl status docker # 查看 dockerd 日志 sudo journalctl -u docker -n 200 --no-pager # 检查 docker 是否能列出容器(验证 daemon 响应) sudo docker ps -a # 检查 docker 网络驱动是否正常 sudo docker info
docker info 输出中重点关注以下几个字段:
Container runtime: containerd # 确认使用的运行时 Storage Driver: overlay2 # 确认存储驱动 Logging Driver: json-file Cgroup Driver: systemd # 注意:docker 和 kubelet 的 cgroup driver 必须一致
如果 Cgroup Driver 显示 cgroupfs 而 kubelet 使用的是 systemd,就会导致资源管理不一致,节点可能出现莫名的内存压力或 CPU 限制失效。
3.4 检查 crictl 可见的容器和镜像
# 列出所有容器(包括已停止的) sudo crictl ps -a # 列出所有镜像 sudo crictl images # 查看运行中的容器 sudo crictl ps
如果 crictl ps 能正常列出容器,但 kubelet 报告节点 NotReady,说明问题可能在 kubelet 和容器运行时通信的 endpoint 配置上。这种情况比较少见,但容易让人误判。
第四步:检查节点资源是否耗尽
节点资源(磁盘、内存、CPU)耗尽是导致 NotReady 的最常见原因。kubelet 内置了资源压力检测,当检测到节点资源不足时,会自动设置对应的 Condition 为 True,同时降低节点评分甚至拒绝调度新 Pod。
4.1 检查磁盘空间
# 查看所有挂载点的磁盘使用率,重点关注 / 和 /var/lib/containerd df -h # 输出示例: # Filesystem Size Used Avail Use% Mounted on # /dev/sda1 100G 95G 5G 95% / <-- 危险:超过 90% # /dev/sdb1 500G 200G 300G 40% /data # overlay 100G 95G 5G 95% /var/lib/containerd <-- 这个是 containerd 使用的overlay
磁盘使用率超过 90% 是非常危险的。kubelet 的 DiskPressure 检测默认阈值是:磁盘空间低于 10%(可通过 kubelet 配置 --eviction-hard 调整)。当磁盘空间不足时:
容器无法写入日志(写入 /var/log 或 stdout)
镜像无法解压加载
kubelet 自身写 cpu_manager_state、memory_manager_state 等文件失败
节点逐渐失控
风险提醒:不要盲目删除 /var/lib/containerd/overlay2 下的镜像文件来释放磁盘空间。容器镜像层存储在 overlay2 目录中,手动删除可能导致正在运行的容器异常。用 ctr -n k8s.io images prune 来清理未使用的镜像(ctr 是 containerd 自带的命令行工具),不要用 rm -rf 直接删 overlay2 目录。
清理磁盘空间的正确步骤如下:
# SSH 到目标节点后,先评估可以清理的空间
# 1. 查看 /var/log 下的日志文件大小
sudo du -sh /var/log/*
sudo find /var/log -name "*.log" -size +100M -exec ls -lh {} ;
# 2. 查看是否有过大的 core dump
sudo find /var/lib/systemd/coredump -type f -size +100M 2>/dev/null | xargs ls -lh
# 3. 查看 containerd 镜像缓存
sudo ctr -n k8s.io images list
# 4. 清理未使用的镜像(不删除正在使用的)
sudo ctr -n k8s.io images prune -f
# 5. 清理旧的 journal 日志(保留最近 3 天)
sudo journalctl --vacuum-time=3d
# 6. 清理已停止的容器
sudo crictl rm -f $(sudo crictl ps -a --state EXITED -q)
如果磁盘使用率已经超过 95%,上述清理手段可能不够,需要临时扩容或者迁移数据。要评估是哪些目录占用了最多空间:
# 查看 /var/lib 目录大小分布 sudo du -sh /var/lib/*
通常 /var/lib/containerd 和 /var/lib/kubelet 是最大的两个目录。containerd 存储镜像数据,kubelet 存储 Pod 沙盒数据。
4.2 检查内存使用
free -m # 输出示例: # total used free shared buff/cache available # Mem: 32000 28000 4000 2000 8000 2000 # Swap: 8192 0 8192
如果 available 列的值非常低(低于总内存的 10%),说明节点处于内存压力状态。Linux 会使用 buff/cache 作为可用内存的一部分,但如果 free 列持续为 0 且 available 也很低,说明物理内存确实不够用了。
查看具体是哪些进程消耗了最多内存:
# 按内存使用排序查看进程 ps aux --sort=-%mem | head -20 # 查看 kubelet 进程的内存使用 ps -p $(pgrep kubelet) -o pid,vsz,rss,comm
如果发现某个进程(不一定是 kubelet)内存占用异常高,比如 Elasticsearch、Java 应用等,可以进一步分析是该进程自身的内存泄漏还是配置不合理(比如 JVM 堆内存设置过大)。但无论如何,如果节点的 available 内存持续很低,即使找到根因,节点也可能在根因修复前就已经 OOM 了。
4.3 检查 CPU 负载
# 查看系统负载和 CPU 使用率 uptime # 输出示例: # 1032 up 45 days, 3:22, 2 users, load average: 8.52, 6.31, 5.12 # 这个节点有 8 个 CPU 核心,load average 8.52 意味着 CPU 队列长度为 8.52 # 接近核心数,说明 CPU 资源非常紧张 # 查看 CPU 使用详情 top -bn1 | head -30
load average 是 1分钟、5分钟、15分钟的系统平均负载。如果 load average 持续高于 CPU 核心数,说明 CPU 资源不足。但 CPU 不足一般不会直接导致 NotReady(除非导致 kubelet 响应超时),更多是间接影响。直接导致 NotReady 的是 kubelet 进程本身被 OOM kill 或者 CPU 饥饿导致无法正常心跳。
4.4 综合资源诊断脚本
以下脚本可以快速输出节点资源全景,发现明显异常:
#!/bin/bash
# save as: node_diag.sh
# 执行方式: sudo bash node_diag.sh
echo "===== 节点基本信息 ====="
uname -r
cat /etc/os-release | grep PRETTY_NAME
echo ""
echo "===== CPU 核心数和负载 ====="
nproc
uptime
echo ""
echo "===== 内存使用 ====="
free -m
echo ""
echo "===== 磁盘使用 ====="
df -h | grep -v tmpfs | grep -v devtmpfs
echo ""
echo "===== 主要进程 CPU/MEM 占用 Top 10 ====="
ps aux --sort=-%cpu | awk 'NR==1{print} NR>1 && NR<=12'
echo ""
echo "===== kubelet 进程状态 ====="
systemctl is-active kubelet
systemctl is-failed kubelet 2>/dev/null || echo "not failed"
echo ""
echo "===== containerd/docker 状态 ====="
systemctl is-active containerd 2>/dev/null || echo "containerd not active"
systemctl is-active docker 2>/dev/null || echo "docker not active"
echo ""
echo "===== kubelet 日志最新错误 ====="
journalctl -u kubelet -n 20 --no-pager | grep -i error
echo ""
echo "===== inode 使用情况(文件名耗尽也导致磁盘问题)====="
df -i | grep -v tmpfs | grep -v devtmpfs
第五步:检查网络连通性
网络问题也是导致 NotReady 的常见原因。kubelet 需要和 API Server 保持通信,如果网络不通,kubelet 的心跳就无法送达,节点就会被标记为 NotReady。
5.1 从节点上测试到 API Server 的连通性
# 查看 API Server 的 endpoint(需要先从kubectl配置中获取)
APISERVER=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}')
echo "API Server: $APISERVER"
# 测试 HTTPS 连通性(使用内置 CA 验证,不验证证书可以加 -k)
curl -sk --max-time 5 ${APISERVER}/healthz
# 正常返回: {"status":"OK"}
# 如果报错: Connection refused / Timeout,说明网络不通
5.2 测试 DNS 解析
# 查看 kubelet 的 --cluster-dns 配置
grep -r "clusterDNS" /var/lib/kubelet/config.yaml 2>/dev/null || echo "not in config.yaml"
grep "cluster-dns" /etc/kubernetes/kubelet.conf 2>/dev/null | head -2
# 测试集群 DNS 是否可达
ping -c 3 kubernetes.default.svc
ping -c 3 $(grep nameserver /etc/resolv.conf | awk '{print $2}' | head -1)
# 如果 DNS 不通,检查 CoreDNS 是否在运行
kubectl get pods -n kube-system -o wide | grep coredns
5.3 检查节点网络接口和路由
# 查看网络接口配置
ip addr
# 重点关注:
# 1. 是否有网卡未 UP
# 2. 是否有网卡配置了正确的 IP 和掩码
# 3. CNI 插件创建的网桥和 veth 设备是否存在(如 cni0, flannel.1, calico* 等)
# 查看路由表
ip route
# 确认是否有到 API Server IP 的路由
ip route | grep $(echo $APISERVER | awk -F/ '{print $3}' | awk -F: '{print $1}')
5.4 检查 CNI 插件状态
不同集群使用的 CNI 插件不同(Flannel、Calico、Cilium、Weave 等),检查方式也不同。
如果是 Flannel:
# 查看 flannel 网卡是否存在 ip addr | grep flannel # 查看 flannel.1 接口信息 ip addr show flannel.1 # 查看 kube-flannel 的 Pod 日志 kubectl logs -n kube-system -l app=flannel --tail=50
如果是 Calico:
# 查看 calico-node Pod 状态 kubectl get pods -n kube-system -l k8s-app=calico-node -o wide # 查看 calico-node 日志 kubectl logs -n kube-system -l k8s-app=calico-node --tail=50 # 查看 BGP 对等连接状态(需要 calicoctl) calicoctl node status # 查看 calico IP 池 calicoctl get ippool -o wide
通用排查方法——查看所有 kube-system Pod 的网络相关组件:
kubectl get pods -n kube-system -o wide | grep -v Running # 查看所有网络相关 Pod kubectl get pods -n kube-system | grep -E "cni|network|calico|flannel|cilium|weave"
5.5 检查节点是否被网络策略误伤
如果集群启用了 NetworkPolicy,而针对特定命名空间或 Pod 设置了过于严格的策略,可能导致 kubelet 和 API Server 通信被阻断。检查方法:
# 查看某个命名空间的 NetworkPolicy kubectl get networkpolicy -A # 查看具体的 NetworkPolicy 规则 kubectl get networkpolicy-n -o yaml
不过这种情况比较罕见,因为 kube-system 命名空间通常不受 NetworkPolicy 影响。更常见的是业务命名空间的 Policy 影响了业务 Pod 和 API Server 的通信,导致 Pod 启动失败,而不是节点 NotReady。
第六步:检查证书是否过期
kubelet 的证书默认有效期为 1 年(通过 kubeadm 部署的集群)。如果证书过期,kubelet 将无法和 API Server 通信,导致节点 NotReady。这种情况在集群运行超过 1 年后首次出现,或者在手动调整过系统时间后出现。
6.1 检查 kubelet 证书过期时间
在 NotReady 节点上执行:
# 用 kubeadm 检查证书过期时间 sudo kubeadm certs check-expiration --cert-dir /etc/kubernetes/pki # 如果提示 "certificates signed by unknown authority",说明证书链有问题 # 重点关注以下证书的过期时间: # - kubelet client certificate (/etc/kubernetes/pki/kubelet-client-latest/current) # - kubelet server certificate (/var/lib/kubelet/pki/cert.crt)
注意:kubeadm certs check-expiration 检查的是 /etc/kubernetes/pki/ 目录下的控制面证书,而不是 kubelet 自己的证书。kubelet 的证书存放在 /var/lib/kubelet/pki/。两个目录不要混淆。
6.2 手动检查 kubelet 证书
# 查看 kubelet 证书文件 sudo ls -la /var/lib/kubelet/pki/ # 检查证书过期时间 sudo openssl x509 -in /var/lib/kubelet/pki/cert.crt -noout -dates # 对比当前时间 date
如果证书已过期或者即将过期(剩余有效期不足 7 天),需要续期。
6.3 续期 kubelet 证书
风险提醒:续期证书需要重启 kubelet,会触发节点上的 Pod 重新调度。生产环境操作前必须确认业务副本数和影响范围。
# 方式一:通过 kubeadm 续期(推荐) # 先备份现有证书 sudo cp -r /etc/kubernetes/pki /etc/kubernetes/pki.bak.$(date +%Y%m%d) sudo cp -r /var/lib/kubelet/pki /var/lib/kubelet/pki.bak.$(date +%Y%m%d) # 续期 kubelet 证书(需要 API Server 可达) 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 rm -rf /etc/kubernetes/pki/kubelet-client-latest sudo systemctl start kubelet
方式二会让 kubelet 启动时自动向 API Server 申请新的证书,适合紧急情况。方式一更可控,但需要证书配置正确。
第七步:常见根因场景与修复方案
通过以上排查步骤,大多数 NotReady 问题可以定位到根因。下面整理常见根因对应的修复方案。
场景一:磁盘空间不足(最常见)
根因:节点磁盘被日志、镜像、临时文件填满,kubelet 检测到 DiskPressure 后将节点标记为 NotReady。
修复方案:
# 1. 确认根因
df -h
# 看到 /dev/sda1 使用率 95%+
# 2. 评估可以清理的内容
sudo du -sh /var/lib/containerd/* 2>/dev/null | sort -rh | head -10
sudo du -sh /var/log/* 2>/dev/null | sort -rh | head -10
# 3. 清理未使用镜像(先查看镜像列表,评估哪些可以删)
sudo crictl images
# 查看未被任何容器使用的镜像(ctr 命令)
sudo ctr -n k8s.io images list | awk '{print $1":"$2}' | head -20
# 确认无误后执行清理(ctr 是 containerd 自带的命令行工具)
# 清理所有未被任何容器使用的镜像
sudo ctr -n k8s.io images prune -f
# 4. 清理旧的 journal 日志
sudo journalctl --vacuum-size=500M
# 5. 验证磁盘空间恢复
df -h
# 6. 等待 kubelet 重新评估磁盘状态(通常 1-2 分钟内自动恢复)
sleep 120
kubectl get node k8s-node-1
如果磁盘使用率是因为容器镜像本身太大(比如拉取了过多大型镜像),需要从镜像管理入手:设置镜像配额、定期清理未使用的镜像、使用更小的基础镜像。
从根源上防止磁盘压力:
# 在 kubelet 配置中设置 eviction threshold # 编辑 /var/lib/kubelet/config.yaml 或通过 kubelet 启动参数 # 示例:设置更严格的磁盘空间 eviction 阈值(85% 开始告警,80% 开始驱逐) evictionHard: imagefs.available: "15%" memory.available: "100Mi" nodefs.available: "10%"
场景二:kubelet 进程 OOM 被 kill
根因:节点内存不足,Linux OOM Killer 杀掉了 kubelet 进程。kubelet 进程被 kill 后无法上报心跳,节点立即变为 NotReady。
诊断依据:
# 查看系统日志中的 OOM kill 记录 sudo dmesg | grep -i "out of memory" sudo dmesg | grep -i "kubelet" # 如果 dmesg 输出被清除(环形缓冲区大小有限),查看 systemd journal sudo journalctl -x | grep -i "memory" | tail -50 sudo journalctl -xb | grep -i "oom" | tail -20
如果看到类似如下记录:
[12345.678901] kubelet invoked oom-killer: gfp_mask=0x6200x2502ca, order=0, oom_score_adj=999 [12345.678902] Memory cgroup out of memory: Killed process 12345 (kubelet)
说明 kubelet 确实被 OOM Killer 杀掉了。
修复方案:
先恢复内存:清理内存占用高的进程(不重要的业务 Pod),腾出空间
设置 kubelet 的 OOM Score Adj 为负数,让 kubelet 更不容易被 kill
# 查看 kubelet 进程当前的 oom_score_adj cat /proc/$(pgrep kubelet)/oom_score_adj # 默认值通常是 0 或负数(如 -999) # 如果需要调整(在 systemd service 文件中添加) # 编辑 /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf # 在 [Service] 段添加: Environment="KUBELET_OPTS=--oom-score-adj=-999"
根本解决:增加节点内存或减少节点上部署的 Pod 数量。可以查看节点上当前的 Pod 数量和内存请求:
kubectl get pods -o json --all-namespaces | jq '[.items[].spec.containers[].resources.requests.memory // "0Mi" | gsub("Ki";"") | gsub("Mi";"") | tonumber] | add'
场景三:容器运行时配置变更后无法通信
根因:修改了 /etc/containerd/config.toml 后没有重启 containerd,或者 containerd 和 kubelet 的 cgroup driver 配置不一致。
诊断依据:
# 查看 kubelet 的 container runtime endpoint grep container-runtime /var/lib/kubelet/config.yaml # 查看 containerd 的 cgroup driver 配置 grep SystemdCgroup /etc/containerd/config.toml # 对比两者是否一致 # containerd config.toml 中: SystemdCgroup = true # kubelet config.yaml 中: cgroupDriver: systemd # 两者必须匹配,否则 kubelet 无法管理容器
修复方案:
# 1. 检查配置一致性 grep -i "SystemdCgroup" /etc/containerd/config.toml grep -i "cgroupDriver" /var/lib/kubelet/config.yaml # 2. 如果不一致,修改 containerd 配置 # 编辑 /etc/containerd/config.toml # 确保 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # 段中有: SystemdCgroup = true # 3. 重启 containerd sudo systemctl restart containerd # 4. 重启 kubelet sudo systemctl restart kubelet # 5. 等待恢复 sleep 60 kubectl get node k8s-node-1
风险提醒:重启 containerd 会导致节点上所有容器停止并重新创建。对有状态应用(MySQL、Redis)和敏感业务有严重影响,必须在业务方确认后执行。
场景四:节点内核问题导致网络或存储异常
根因:内核版本过旧或存在已知的 BUG,导致某些 K8s 功能不正常。例如内核 5.x 之前的版本对 overlay2 文件系统支持不完善,或者某些内核版本对 cgroup v2 支持有问题。
诊断依据:
# 查看内核版本 uname -r # 查看操作系统版本 cat /etc/os-release # 查看内核日志中是否有硬件或文件系统错误 sudo dmesg -T | grep -iE "error|warn|fail|timeout|offline" | tail -50 # 查看是否有文件系统只读的错误 sudo dmesg -T | grep -i "readonly"
修复方案:升级内核或重装节点。如果内核问题导致根文件系统变成只读,必须立即重启节点,但要做好 Pod 驱逐和业务切换的准备。
# 查看当前内核相关参数 sysctl -a | grep -i "bridge|netfilter|forward" | head -20 # 检查 br_netfilter 模块是否加载 lsmod | grep br_netfilter
场景五:kubelet 配置文件错误
根因:手动修改了 /var/lib/kubelet/config.yaml 或 kubelet 启动参数,导致 kubelet 启动失败。
诊断依据:
# 查看 kubelet 的完整启动命令 ps aux | grep kubelet | grep -v grep # 查看 kubelet 配置文件 cat /var/lib/kubelet/config.yaml # 尝试手动启动 kubelet 看报错 sudo /usr/bin/kubelet --config=/var/lib/kubelet/config.yaml --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf
常见配置文件错误:
cgroupDriver 值和容器运行时不一致
evictionHard 配置格式错误(缺少引号、格式不对)
containerLogMaxSize 和 containerLogMaxFiles 格式不对
serializeImagePulls 值写成了字符串而不是布尔值
场景六:etcd 连接超时(影响所有节点)
根因:etcd 是 K8s 的状态存储后端,如果 etcd 响应慢或不可达,所有节点的 kubelet 心跳都无法处理,节点会陆续变成 NotReady。
诊断依据:
# 在任意节点测试 etcd 连通性
ETCD_ENDPOINT=$(kubectl get endpoints kube-apiserver -n default -o jsonpath='{.subsets[0].addresses[0].targetRef.name}')
ETCD_POD_NAME=$(kubectl get pods -n kube-system -l component=etcd -o jsonpath='{.items[0].metadata.name}')
# 测试 etcd 健康状态
kubectl exec -n kube-system ${ETCD_POD_NAME} -- etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health
# 查看 etcd pod 是否 Running
kubectl get pods -n kube-system -l component=etcd -o wide
修复方案:如果 etcd 故障,需要优先恢复 etcd 集群。etcd 恢复是另一个大话题,这里不展开,但需要知道:etcd 故障导致的 NotReady,节点本身没问题,重启 etcd 后节点会自动恢复。
第八步:验证修复结果
修复完成后,必须验证节点确实恢复到 Ready 状态,并且业务恢复正常。
8.1 节点状态验证
# 查看节点状态,等待所有节点变为 Ready kubectl get nodes -o wide # 查看节点的 Conditions,确认所有状态为 False 或 True(如 DiskPressure=False) kubectl describe node k8s-node-1 | grep -A 20 "Conditions" # 如果节点状态是 Ready 但某些 Condition 仍为 True,说明 kubelet 已恢复但资源压力未完全解除
8.2 Pod 调度验证
# 查看 NotReady 节点上之前的 Pod 是否已经重新调度并运行 kubectl get pods -o wide --all-namespaces | grep k8s-node-1 # 查看是否有 Pod 处于 Pending、ContainerCreating、CrashLoopBackOff 等异常状态 kubectl get pods --all-namespaces | grep -v Running | grep -v Completed # 如果有异常 Pod,分析原因 kubectl describe pod-n kubectl logs -n --previous
8.3 业务功能验证
# 查看业务相关的 Deployment replicas 是否满足预期 kubectl get deployment -A # 查看 Services 是否正常 kubectl get svc -A # 如果有 Ingress,测试 Ingress 访问 curl -sk https:/// -w " %{http_code} " # 测试 DNS 解析 kubectl run dnsutils --image=tbuskey/dnsutils:1.0 --restart=Never -it --rm -- sh # 在容器内执行:nslookup kubernetes.default
8.4 节点资源验证
# 在节点上执行最终资源检查 ssh k8s-node-1 "df -h / && free -m && uptime" # 确认磁盘空间已释放到安全范围 # 确认内存使用恢复正常 # 确认负载回到正常水平
第九步:预防措施和日常巡检
故障修复后,更重要的是建立长效机制,避免同类问题反复发生。
9.1 建立节点健康巡检机制
推荐每日执行一次节点健康检查,可以通过 Ansible、Prometheus 告警或定时脚本来实现:
#!/bin/bash
# save as: k8s_node_health_check.sh
# 建议通过 cron 每日执行,结果写入日志文件
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
ALERT_MSG=""
for NODE in $NODES; do
# 检查节点状态
STATUS=$(kubectl get node $NODE -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
if [ "$STATUS" != "True" ]; then
ALERT_MSG="${ALERT_MSG}
[node NotReady] $NODE"
kubectl describe node $NODE | grep -A 5 "Conditions" >> /var/log/k8s-health.log
fi
# 检查磁盘使用率(阈值 85%)
DISK_USAGE=$(ssh $NODE "df -h / | tail -1 | awk '{print $5}' | sed 's/%//'")
if [ "$DISK_USAGE" -gt 85 ]; then
ALERT_MSG="${ALERT_MSG}
[disk pressure] $NODE: ${DISK_USAGE}%"
fi
# 检查内存使用率(阈值 90%)
MEM_AVAILABLE=$(ssh $NODE "free -m | tail -2 | head -1 | awk '{print $7}'")
MEM_TOTAL=$(ssh $NODE "free -m | tail -2 | head -1 | awk '{print $2}'")
MEM_USAGE=$(( (MEM_TOTAL - MEM_AVAILABLE) * 100 / MEM_TOTAL ))
if [ "$MEM_USAGE" -gt 90 ]; then
ALERT_MSG="${ALERT_MSG}
[memory pressure] $NODE: ${MEM_USAGE}%"
fi
# 检查 kubelet 和容器运行时状态
KUBELET_ACTIVE=$(ssh $NODE "systemctl is-active kubelet")
CONTAINERD_ACTIVE=$(ssh $NODE "systemctl is-active containerd 2>/dev/null" || echo "unknown")
if [ "$KUBELET_ACTIVE" != "active" ]; then
ALERT_MSG="${ALERT_MSG}
[kubelet down] $NODE: $KUBELET_ACTIVE"
fi
done
if [ -n "$ALERT_MSG" ]; then
echo -e "K8s Node Health Alert: $ALERT_MSG" | tee -a /var/log/k8s-health.log
# 这里可以接入告警系统(如钉钉、企业微信、飞书、邮件等)
else
echo "$(date): All nodes healthy" >> /var/log/k8s-health.log
fi
9.2 合理配置 kubelet eviction threshold
不要使用默认的 eviction threshold,因为默认值比较激进,可能在磁盘空间稍微不足时就触发 NotReady。建议根据实际业务负载调整:
# /var/lib/kubelet/config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration # evictionHard: 不够用时直接驱逐,不给缓冲 evictionHard: memory.available: "100Mi" # 内存低于 100Mi 时才触发 eviction nodefs.available: "5%" # 磁盘空间低于 5% 时触发(保守设置) imagefs.available: "10%" # 镜像存储低于 10% 时触发 # evictionSoft: 软阈值,触发告警但不立即驱逐,给运维人员响应时间 evictionSoft: memory.available: "200Mi" nodefs.available: "10%" imagefs.available: "15%" evictionSoftGracePeriod: memory.available: "2m" nodefs.available: "2m" imagefs.available: "2m" # evictionPressureTransitionPeriod: 进入压力状态后,等待这段时间再决定是否开始驱逐 evictionPressureTransitionPeriod: "2m"
9.3 部署节点资源监控告警
使用 Prometheus Node Exporter 配合 AlertManager,可以对节点资源问题做到提前发现:
# PrometheusRule 示例:节点磁盘空间告警
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: node-disk-alerts
namespace: monitoring
spec:
groups:
- name: node-resources
rules:
- alert: NodeDiskPressure
expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.15
for: 5m
labels:
severity: warning
annotations:
summary: "Node {{ $labels.instance }} has less than 15% disk space"
description: "Disk usage at {{ $value | humanizePercentage }}"
- alert: NodeDiskPressureCritical
expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Node {{ $labels.instance }} has less than 5% disk space - imminent"
9.4 证书自动续期
通过 kubeadm 部署的集群,kubelet 证书会在过期前自动续期(kubelet 使用的是 bootstrap 机制)。但如果手动管理证书,需要建立定时任务:
# 在所有 master 节点上创建 cronjob 每月检查一次证书 # 添加到 crontab: 0 3 1 * * /usr/bin/kubeadm certs renew all --kubeconfig=/etc/kubernetes/admin.conf
9.5 节点资源限制
在生产环境中,建议对每个节点的 Pod 数量和资源使用设置硬限制,防止单节点过载:
# 查看节点最大 Pod 数量配置(kubelet --max-pods 参数,默认 110) ps aux | grep kubelet | grep max-pods # 推荐在 /var/lib/kubelet/config.yaml 中设置: maxPods: 110
总结
Kubernetes 节点 NotReady 故障的排查,核心在于理解 kubelet 是如何向 API Server 报告节点状态的,以及哪些系统级问题会导致 kubelet 无法正常工作。
标准排查流程回顾:
kubectl describe node:看 Conditions,判断是哪种压力(磁盘、内存、网络、PID)
systemctl status kubelet:确认 kubelet 进程是否存活
journalctl -u kubelet:看 kubelet 日志,找启动失败或异常退出的原因
df -h / free -m / uptime:检查磁盘、内存、CPU 资源
crictl info / docker info:验证容器运行时是否正常
ping / curl api-server:验证节点网络和 API Server 连通性
kubeadm certs check-expiration / openssl x509:检查证书是否过期
crictl ps -a / kubectl get pods:看节点上的 Pod 状态,辅助判断
最常见的三个根因:
磁盘空间不足(占比最高):日志/镜像/临时文件占满磁盘,kubelet 检测到 DiskPressure
kubelet OOM 被 kill(占比第二):节点内存不足,OOM Killer 优先杀掉 kubelet
容器运行时配置不一致(升级后常见):containerd 和 kubelet 的 cgroup driver 配置不匹配
修复后必须验证:
节点状态恢复 Ready
原有 Pod 重新调度并 Running
业务功能正常
资源使用回到安全范围
长期预防:
建立每日节点健康巡检
合理配置 eviction threshold(不要全用默认)
部署 Prometheus 监控和告警,提前发现资源问题
证书到期前自动续期
控制单节点 Pod 数量和资源请求/限制
遇到 NotReady 问题时,保持冷静,按顺序排查,不要急于重启节点。重启节点会清除现场,让很多根因信息丢失。先查日志、再查资源、最后再决定是否重启。现场保留得越完整,定位根因就越快,下次预防也就越有针对性。
全部0条评论
快来发表一下你的评论吧 !