某天早上打开监控,发现集群里一台节点变成了 NotReady,上面的 Pod 开始被驱逐,业务实例数对不上。这种时候最忌讳的是直接 reboot 或者 systemctl restart kubelet 一顿操作,因为节点 NotReady 只是"症状",真正的原因可能是磁盘满了、证书过期、网络插件崩了或者内存压力触发。这篇文章给出一条从外到内的排查路径,每一步都给出真实可执行的命令,帮你快速定位根因,而不是靠重启碰运气。
一、先看现象:节点到底哪里不对
第一步永远是确认哪些节点异常、异常了多久。
# 查看所有节点状态,注意 STATUS 和 年龄列
kubectl get nodes
kubectl get nodes -o wide
# 如果节点很多,只看 NotReady 的
kubectl get nodes | grep -v ' Ready '
# 查看节点状态变化的时间,判断是不是刚刚发生
kubectl get events -A --sort-by=.lastTimestamp | tail -30
kubectl get nodes 的输出里,STATUS 是 Ready 表示正常,NotReady 表示节点不可调度新 Pod,SchedulingDisabled 表示被手动封锁(drain/cordon)过。旁边还会带一列 Roles 和 AGE,如果好多节点一起变 NotReady,优先怀疑是共享组件出问题(比如 apiserver、网络、证书),而不是某台机器硬件坏。
二、describe node:Conditions 与 Events 是关键
kubectl describe node 是排障第一现场,输出里有两块必须细看:Conditions 和 Events。
kubectl describe node
重点看 Conditions 部分,常见的几个状态含义如下。
| Condition 类型 | 含义 | 异常时的典型原因 |
|---|---|---|
| Ready | 节点是否健康可调度 | kubelet 挂了、网络插件没起来 |
| MemoryPressure | 内存是否吃紧 | 节点内存不足,频繁回收 |
| DiskPressure | 磁盘空间/IO 是否吃紧 | 镜像和日志占满磁盘 |
| PIDPressure | 进程数是否过多 | 进程泄漏、容器创建失控 |
| NetworkUnavailable | 节点网络是否就绪 | CNI 插件未安装或未就绪 |
如果 Ready 是 False,Conditions 里通常会带原因(Reason)和消息(Message)。比如 KubeletNotReady、NodeStatusUnknown、KubeletHasDiskPressure。这些字符串就是定位方向的钥匙。
判读 Conditions 时有一个实用技巧:不要只看 True 还是 False,要看它是"什么时候变成这样的"。Conditions 里带了 LastTransitionTime,如果这个时间就在几分钟前,说明故障是刚发生的,可以对应到你最近做过的变更;如果时间很久以前,说明问题一直存在只是没被注意到。另外,Ready 为 Unknown 和 Ready 为 False 含义不同:Unknown 通常表示 kubelet 失联、节点根本没上报状态;False 表示节点联系上了但自检不通过。搞清楚这两者的区别,能少走很多弯路。
Events 部分记录了最近发生的事情,比如 NodeNotReady、Starting kubelet、NodeHasSufficientMemory 之类。它的价值在于给出"时间线":你能看到节点是先出现磁盘压力、再驱逐 Pod、最后变 NotReady,还是 kubelet 突然停止心跳直接 Unknown。但要注意,Events 会保留一段时间(默认 1 小时),所以故障发生太久可能已经被清理。如果没有 events,就说明问题发生得早,或者集群 API 本身也有问题了,此时要换个思路排查。
# 只看 Conditions 段
kubectl describe node | sed -n '/Conditions:/,/Addresses:/p'
# 只看 Events 段
kubectl describe node | sed -n '/Events:/,$p'
# 用 jsonpath 直接取出 Ready 状态和原因
kubectl get node -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
三、登录节点,从 kubelet 查起
describe 只能看到"节点汇报给控制平面的信息",更细的原因要登录节点看 kubelet。
# 查看 kubelet 服务状态(几乎所有发行版都用 systemd 管理)
systemctl status kubelet
# 如果不在运行,看它为什么退出
systemctl status kubelet -l --no-pager | tail -40
# 实时查看 kubelet 日志(最有用的一条命令)
journalctl -u kubelet -f
# 查看最近 200 行日志
journalctl -u kubelet -n 200 --no-pager
# 只看错误和警告
journalctl -u kubelet --no-pager | grep -iE 'error|fail|unable' | tail -50
# 查看指定时间段的日志
journalctl -u kubelet --since "30 min ago" --no-pager
kubelet 日志里最常见的几类错误值得背下来。
第一类是容器运行时连不上,比如 failed to get container runtime status: ... connection refused。这说明 kubelet 和 containerd/docker 之间通信断了,要去看容器运行时(下一节)。
第二类是证书问题,比如 x509: certificate has expired or is not yet valid。Kubernetes 集群证书默认有效期常见是一年,到期后整个集群会陆续报错,是非常典型的"突然大规模 NotReady"原因。
第三类是资源压力,比如 The node was low on resource: ephemeral-storage 或 imagefs。这说明根分区或镜像分区快满了。
第四类是 PLEG 相关,比如 PLEG is not healthy。这通常意味着容器运行时卡住了,kubelet 无法持续获取容器状态,节点会被标记为 NotReady。
# 查看 kubelet 配置(不同安装方式路径不同)
cat /var/lib/kubelet/config.yaml
# 查看 kubelet 启动参数(若用 systemd 单元)
systemctl cat kubelet | grep -A5 ExecStart
# 查看证书有效期(kubeadm 安装的集群证书在 /etc/kubernetes/pki)
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
# 检查所有证书到期时间(kubeadm 环境)
kubeadm certs check-expiration
四、容器运行时(containerd / docker)状态
Kubernetes 通过 CRI 对接容器运行时。运行时挂了,kubelet 没法创建和查询容器,节点必然 NotReady。
# containerd(当前主流)
systemctl status containerd
journalctl -u containerd -n 200 --no-pager
# 用 crictl 检查运行时是否响应(需装 cri-tools)
crictl --runtime-endpoint unix:///run/containerd/containerd.sock info
crictl ps | wc -l
# 如果用的是 dockerd(较新 K8s 已不推荐直接用 Docker 运行时)
systemctl status docker
journalctl -u docker -n 200 --no-pager
crictl --runtime-endpoint unix:///var/run/dockershim.sock info # 旧版本
crictl info 如果能返回运行时的容量、状态等信息,说明运行时是活的;如果报连接失败,就是运行时或它的 socket 路径有问题。crictl ps 能列出容器,说明运行时在工作。常见故障包括:运行时配置被改坏、磁盘满导致运行时无法写、socket 文件权限不对、systemd 单元被误停。
# 确认 kubelet 配置的运行时端点
grep -r 'containerRuntimeEndpoint\|containerd' /var/lib/kubelet/config.yaml
# 查看 containerd 配置文件
cat /etc/containerd/config.toml | grep -v '^#' | grep -v '^$'
五、CNI 网络插件是否就绪
这是一个极容易被忽略的原因:节点上 kubelet 正常、容器运行时正常,但网络插件(CNI)没装好或没就绪,节点依然会 NotReady(Ready 条件里的 NetworkUnavailable 或 cni 未初始化)。很多人排查时把注意力全放在 kubelet 上,翻遍日志也没找到明显错误,最后才发现是网络插件那边的问题。
为什么 CNI 出问题会让节点 NotReady?因为 Kubernetes 要求节点具备完整的网络能力:能创建 Pod 的网络接口、能保证 Pod 之间互相通信、能让 Pod 访问 Service。这些都由 CNI 插件实现。如果节点上没有装 CNI 插件、配置损坏、或者 CNI 的 DaemonSet Pod 没起来,kubelet 初始化网络时会失败,节点自然无法进入 Ready。这也是为什么"新加了一台节点,其他服务都正常就它 NotReady",十有八九是 CNI 相关问题。
# 查看网络插件 Pod 是否正常运行(以 Calico 为例)
kubectl get pods -n kube-system | grep -iE 'calico|flannel|cilium|weave'
kubectl -n kube-system describe pod
# 查看节点上的 CNI 配置文件是否存在
ls -l /etc/cni/net.d/
# 查看 CNI 二进制是否存在
ls /opt/cni/bin/
常见问题:CNI Pod 处于 CrashLoopBackOff,通常是配置里的网段冲突或镜像拉取失败;/etc/cni/net.d/ 目录为空,说明 CNI 没正确安装;节点上新装的插件没重启 kubelet,导致它还没加载到新的 CNI 配置。
# 网段冲突检查:确认 Pod CIDR、Service CIDR、节点网段不重叠
kubectl cluster-info dump | grep -iE 'cluster-cidr|service-cluster-ip-range'
ip route
六、磁盘与内存压力
DiskPressure 和 MemoryPressure 是最常见的两类"软故障",很多人看节点明明还有空间,却被标记了压力,原因是压力判断基于的是 kubelet 的驱逐阈值,不是"完全满"。
# 查看节点磁盘使用(根分区和镜像分区)
df -h
df -i # inode 也用尽会触发磁盘压力
# 查看 Docker/containerd 占用的空间
du -sh /var/lib/containerd /var/lib/docker 2>/dev/null
# 清理无用镜像(谨慎,会删除未被使用的镜像)
crictl rmi --prune
# 查看内存压力信号
free -h
cat /proc/pressure/memory 2>/dev/null
# 查看 kubelet 驱逐阈值设置
grep -A10 eviction /var/lib/kubelet/config.yaml
默认的驱逐阈值大致是:节点可用内存低于一定量(如 100Mi 或按百分比)触发 MemoryPressure,可用磁盘低于一定比例(如 10% 或 15%)触发 DiskPressure。日志和镜像往往是"空间杀手",/var/log、/var/lib/kubelet、/var/lib/containerd 是重点检查目录。
七、Swap 是否被开启
Kubernetes 的调度依赖内存 requests 的准确性,而 swap 会让"内存实际用量"变得不可预测,所以传统上要求关闭 swap。如果节点上 swap 是开的,kubelet 可能启动失败或报错,节点无法进入 Ready。
# 检查 swap 状态
free -h
swapon --show
# 临时关闭(重启后失效)
swapoff -a
# 永久关闭:注释掉 /etc/fstab 里的 swap 行,然后重启或执行 swapoff -a
grep -n swap /etc/fstab
# 验证 kubelet 是否因 swap 报错
journalctl -u kubelet --no-pager | grep -i swap | tail -20
| 排查项 | 命令 | 异常信号 |
|---|---|---|
| 节点状态 | kubectl get nodes | NotReady / Unknown |
| 节点条件 | kubectl describe node | DiskPressure/MemoryPressure=True |
| kubelet | systemctl status kubelet | inactive / failed |
| kubelet 日志 | journalctl -u kubelet | 证书过期、CRI 连接失败 |
| 容器运行时 | systemctl status containerd | 未运行、socket 不可用 |
| CNI | kubectl get pods -n kube-system | CrashLoopBackOff |
| 磁盘 | df -h / df -i | 使用率过高、inode 耗尽 |
| Swap | swapon --show | 输出了 swap 设备 |
八、一套完整的排查顺序
把上面的内容整理成一个可复用的流程,遇到 NotReady 按顺序走。
第一步,kubectl get nodes 确认范围,是一台还是多台。多台同时出问题,优先怀疑集群级组件或证书。
第二步,kubectl describe node 看 Conditions 和 Events,拿到 Reason 字符串。
第三步,登录节点,systemctl status kubelet 看服务是否活着,journalctl -u kubelet 看具体报错。
第四步,按报错分类处理:CRI 报错查容器运行时;证书报错续期证书;压力报错清磁盘或查内存;PLEG 报错重启运行时;CNI 报错修网络插件。
第五步,处理完观察节点是否恢复,用 kubectl get nodes -w 持续观察状态变化。
第六步,恢复后复盘根因,补上监控告警(磁盘、内存、证书到期、kubelet 存活),避免同类问题再发生。
# 持续观察节点状态变化
kubectl get nodes -w
# 证书续期(kubeadm 环境,续期后重启相关组件)
kubeadm certs renew all
systemctl restart kubelet
# 之后重启控制平面静态 Pod 需按环境操作,通常是移动 /etc/kubernetes/manifests 下的文件
常见问题
Q:节点 NotReady 后,上面的 Pod 会怎样?
取决于 Pod 的控制器和容忍时间。默认情况下,节点失联一段时间(常见 5 分钟)后,控制器会认为 Pod 需要重建,在其他节点拉起新的副本。有状态服务或有本地存储的 Pod 可能受影响,要提前设计好。
Q:重启 kubelet 能解决 NotReady 吗?
有时能,因为它会重新读取配置、重新连接运行时。但这只是掩盖症状。如果根因是证书过期或磁盘满,重启后很快又会 NotReady。务必先看日志找根因。
Q:为什么磁盘没满还是 DiskPressure?
因为 kubelet 是按阈值驱逐的,不是等磁盘 100% 才报警。默认可能剩余 10% 到 15% 就触发。另外 inode 耗尽也会触发,df -i 能看到。
Q:集群里所有节点同时 NotReady 是什么原因?
高度怀疑三类问题:控制平面/etcd 出问题导致所有节点汇报不上去;证书集体过期;网络故障导致节点与 apiserver 失联。先查 apiserver 和 etcd 是否正常。
Q:Unschedulable(被 cordon)和 NotReady 一样吗?
不一样。Unschedulable 是人为封锁,节点本身健康,只是不接受新 Pod;NotReady 是节点自身不健康。前者用 kubectl uncordon 恢复,后者要修根因。
总结
节点 NotReady 是症状不是病因,排查核心是"从控制平面看到的信息,逐步深入到节点内部"。先用 kubectl get nodes 和 kubectl describe node 拿 Conditions 和 Events 定位方向,再登录节点用 systemctl status kubelet 和 journalctl -u kubelet 找具体报错。
高频根因就那么几类:容器运行时连接失败、证书过期、磁盘或内存压力、CNI 未就绪、swap 未关闭、PLEG 不健康。把这几类对应的命令练熟,绝大多数 NotReady 都能在几分钟内定位。
最容易造成"大规模同时 NotReady"的是证书过期和集群级组件故障,因为它们的表现是成片的,而单机问题通常只影响一台。看到多台同时异常,先往集群级方向查。
恢复节点只是第一步,真正的价值在于事后补监控:kubelet 存活、磁盘水位、内存水位、证书到期提醒。把故障变成告警,比事后救火划算得多。
企业QQ咨询




