早上到公司打开监控面板,发现集群里两个节点变成 NotReady,上面的 Pod 开始被驱逐、业务响应变慢。kubectl get nodes 里 STATUS 显示为 NotReady,但你完全不知道是网络断了、磁盘满了、还是 kubelet 挂了。这是 K8s 运维里最有代表性的一类故障:症状单一、成因多样。本文给出一条标准化的排查链路——节点状态 → 事件 → kubelet 与容器运行时 → 网络插件 → 资源压力——并重点讲清楚 MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable 这四种 condition 的区分与针对性处理。
一、第一步:拿到节点的状态与事件
不要急着登录节点。kubectl 本身就能提供绝大部分信息,而且不会影响线上。
# 1. 看节点整体状态
kubectl get nodes
kubectl get nodes -o wide # 额外显示 IP、OS、内核、容器运行时
# 2. 核心命令:查看节点详细信息(Conditions、Allocated resources、Events 三段最重要)
kubectl describe node <节点名>
# 3. 只提取 Conditions 段落
kubectl get node <节点名> -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.message}{"\n"}{end}'
# 4. 查看集群近期事件,按时间排序
kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -50
# 5. 筛选与该节点相关的事件
kubectl get events --field-selector involvedObject.kind=Node,involvedObject.name=<节点名>
# 6. 节点租约是否正常(心跳机制,1.14+ 引入)
kubectl get lease -n kube-node-lease
# 7. 该节点上所有 Pod 的状态
kubectl get pods -A -o wide --field-selector spec.nodeName=<节点名>
节点 Conditions 有五种,每一种 NO 或 Unknown 都指向完全不同的故障方向:
| Condition | 含义 | 典型原因 | 直接影响 |
|---|---|---|---|
| Ready | 节点健康,可以接受 Pod | kubelet 停止、证书过期、apiserver 连不上 | Pod 被驱逐,新 Pod 不再调度 |
| MemoryPressure | 可用内存低于驱逐阈值 | 内存泄漏、requests 总量超分配、宿主机内存不足 | kubelet 驱逐 Pod,BestEffort 优先 |
| DiskPressure | 节点文件系统或 inode 不足 | 容器日志暴涨、镜像堆积、imagefs 满 | 驱逐 Pod,镜像回收失败 |
| PIDPressure | 可用 PID 数量不足 | 进程泄漏、Pod 内线程未回收 | 无法 fork 新进程 |
| NetworkUnavailable | 节点网络配置不正确 | CNI 插件未安装/未启动/配置丢失 | Pod 跨节点无法通信 |
二、第二步:查 kubelet 与容器运行时
NodeNotReady 最常见的原因就是 kubelet 挂了或无法与 apiserver 通信。登录到出问题的节点,先看 kubelet 服务本身。
# 1. kubelet 服务状态 systemctl status kubelet systemctl is-active kubelet # 2. kubelet 实时日志(信息量最大,第一时间看这里) journalctl -u kubelet -f # 3. 查看最近 10 分钟的日志并过滤错误 journalctl -u kubelet --since "10 minutes ago" --no-pager | grep -iE 'error|fail|unable|denied|expired|refused' # 4. 容器运行时状态(二选一) systemctl status containerd systemctl status docker # 5. 用 crictl 验证运行时可用性(比 kubectl 更底层,能绕过 kubelet) crictl version crictl info crictl pods crictl ps -a # crictl 报 endpoint 错误时指定 socket cat > /etc/crictl.yaml <<'EOF' runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF # containerd 日志 journalctl -u containerd --since "10 minutes ago" --no-pager # 6. kubelet 自身配置与 kubeconfig cat /var/lib/kubelet/config.yaml ls -l /var/lib/kubelet/kubeconfig /etc/kubernetes/kubelet.conf cat /var/lib/kubelet/kubeconfig | grep server
2.1 证书过期(高频幕后原因)
节点运行一年以上突然全部 NotReady,第一嫌疑就是 kubelet 客户端证书过期。journalctl -u kubelet 里会出现 x509: certificate has expired 字样。
# 查看 kubelet 客户端证书有效期 openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates # kubeadm 集群一次性检查所有证书有效期 kubeadm certs check-expiration # 检查是否有 Pending 的 CSR 未批准 kubectl get csr kubectl certificate approve# 证书轮换(kubeadm 集群) kubeadm certs renew all systemctl restart kubelet # 极端情况:kubelet 无法自愈时,重建 kubelet.conf kubeadm kubeconfig kubelet --client-name system:node:<节点名> > /etc/kubernetes/kubelet.conf systemctl restart kubelet
注意一个易混淆点:时间不同步也会导致"证书看起来过期"。节点时钟跳到未来再跳回,或者 NTP 服务异常,都会让 TLS 握手失败。执行 timedatectl 和 chronyc tracking 确认节点时间与 apiserver 节点一致。
timedatectl chronyc tracking ntpdate -qsystemctl status chronyd
三、第三步:DiskPressure 专项排查
DiskPressure 是生产环境出现频率第二高的 condition,几乎总是由日志或镜像引起。kubelet 的默认硬驱逐阈值如下:
| 信号 | 默认硬驱逐阈值 | 检查命令 |
|---|---|---|
| memory.available | < 100Mi> | free -h |
| nodefs.available | < 10> | df -h /var/lib/kubelet |
| nodefs.inodesFree | < 5> | df -i /var/lib/kubelet |
| imagefs.available | < 15> | df -h /var/lib/containerd |
nodefs 指节点主文件系统(kubelet 数据目录所在),imagefs 指容器镜像与可写层所在的磁盘。两者在同一个分区时只比较 nodefs。
# 1. 磁盘与 inode 双查(inode 耗尽而空间未满是经典陷阱) df -h df -i # 2. 定位 Docker/containerd 目录下最大的子目录 du -xh --max-depth=1 /var/lib/containerd 2>/dev/null | sort -h du -xh --max-depth=1 /var/lib/docker 2>/dev/null | sort -h # 3. 找出 nodefs 上的大目录 du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -15 # 4. 容器日志(Pod 日志路径) du -sh /var/log/pods/*/*/*.log 2>/dev/null | sort -h | tail -10 du -sh /var/log/containers/ 2>/dev/null # 5. 清理无用镜像与容器(containerd 环境) crictl images | grep '' crictl rmi --prune crictl rm -a -f # 删除所有停止的容器,谨慎 # Docker 环境 docker system df docker system prune -a -f # 6. 查看 kubelet 的驱逐配置与当前状态 cat /var/lib/kubelet/config.yaml | grep -A20 'eviction' kubectl describe node <节点名> | grep -A15 'Conditions' # 7. 紧急:清空某 Pod 的大日志(先确认业务允许) truncate -s 0 /var/log/pods/ _ _ /<容器>/0.log
长期措施:修改 kubelet 的 containerLogMaxSize(默认无限制)与 containerLogMaxFiles(默认 5),以及 imageGCHighThresholdPercent(默认 85%)、imageGCLowThresholdPercent(默认 80%),并重启 kubelet。
# 修改 /var/lib/kubelet/config.yaml # containerLogMaxSize: 50Mi # containerLogMaxFiles: 3 # imageGCHighThresholdPercent: 80 # imageGCLowThresholdPercent: 70 # evictionHard: # nodefs.available: "10%" # imagefs.available: "15%" # memory.available: "500Mi" systemctl daemon-reload systemctl restart kubelet journalctl -u kubelet -f | grep -i evict
四、第四步:MemoryPressure 与 PIDPressure
这两种压力来自资源本身不够或泄漏,排查重点是"谁在吃"。
# 1. 内存全景
free -h
cat /proc/meminfo | egrep 'MemTotal|MemAvailable|SwapTotal|SwapFree'
# 2. 按进程看内存(RSS 列)
ps -eo pid,ppid,comm,rss,pmem --sort=-rss | head -20
# 3. cgroup 层面的限制与用量
# cgroup v2
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
# cgroup v1
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/memory.usage_in_bytes
# 4. 看节点上的资源请求总量(需要 metrics-server)
kubectl top node
kubectl top pod -A --sort-by=memory | head -20
# 5. 查看节点已分配资源 vs 可分配资源
kubectl describe node <节点名> | grep -A12 'Allocated resources'
kubectl get node <节点名> -o jsonpath='{.status.allocatable}'
# 6. swap 必须关闭,否则 kubelet 无法正常计算可用内存
free -h | grep Swap
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
# 7. PID 压力排查
cat /proc/sys/kernel/pid_max
ps -eLf | wc -l # 当前系统线程总数
cat /sys/fs/cgroup/pids.current # cgroup v2
cat /sys/fs/cgroup/pids/pids.current
# 找出线程数最多的进程
ps -eo pid,nlwp,comm --sort=-nlwp | head -15
判断标准:MemAvailable(注意不是 MemFree,前者计入了可回收的 page cache)逼近 memory.available 阈值就会出现 MemoryPressure。kubelet 驱逐 Pod 的顺序是:先 BestEffort,再 Burstable 超过 requests 的部分最多者,Guaranteed 最后。给核心业务 Pod 设置合理的 requests/limits 并保证 QoS 为 Guaranteed,是避免被误驱逐的关键。
五、第五步:NetworkUnavailable 与 CNI 插件
NetworkUnavailable 基本等价于CNI 插件没工作。要么没装,要么配置文件丢失,要么 CNI 的 DaemonSet Pod 挂了。
# 1. 检查 CNI 配置目录(必须有且只有一个 .conf / .conflist) ls -l /etc/cni/net.d/ cat /etc/cni/net.d/10-flannel.conflist cat /etc/cni/net.d/10-calico.conflist # 2. 检查 CNI 二进制是否存在 ls -l /opt/cni/bin/ # 3. 检查 kube-system 下的网络插件 Pod kubectl get pods -n kube-system -o wide kubectl get pods -n kube-system -l k8s-app=calico-node kubectl get pods -n kube-system -l app=flannel # 4. 查看 CNI Pod 日志 kubectl -n kube-system logs -l k8s-app=calico-node --tail=100 kubectl -n kube-system logs -l app=flannel --tail=100 # 5. kube-proxy 是否正常(决定 Service 能否转发) kubectl get pods -n kube-system -l k8s-app=kube-proxy kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=50 # 6. 节点上验证网络接口与路由 ip link show ip route show iptables-save | head -40 # flannel 应有 flannel.1;calico 应有 cali* 网卡 # 7. 节点间 VXLAN/路由连通性测试 ping <其他节点的Pod网段网关> ip route get <目标Pod IP> # 8. 检查 CDN 网段的 bird/BGP 会话(calico) calicoctl node status
CNI 没装时的典型表现是新节点加入集群后一直 NotReady,且 kubectl describe node 的 Conditions 里只有 NetworkUnavailable=False 但 Ready 为 Unknown——因为 Ready 依赖 CNI 就绪。修复方式是重新部署网络插件:
# 重新应用 CNI(以 flannel 为例,文件应提前备份) kubectl apply -f kube-flannel.yml # calico kubectl apply -f calico.yaml # 等待所有 Pod 就绪 kubectl wait --for=condition=ready pod -l app=flannel -n kube-system --timeout=120s
6.1 故障原因速查表
为了在无告警、时间紧迫的故障现场快速定位,可以把下面这张表作为第一份对照材料。左侧是 describe node 或日志里最显眼的线索,右侧是对应的高频成因。
| 观察到的线索 | 最可能的成因 | 第一动作 |
|---|---|---|
| 多个节点同时 NotReady,日志含 x509 expired | kubelet 客户端证书过期 | kubeadm certs renew all 后重启 kubelet |
| Pod 容器全部正常,节点却 NotReady | 节点到 APIServer 网络中断或 DNS 解析失败 | curl 探测 6443 端口 |
| 节点曾重启,之后一直 NotReady | swap 被重新挂载、iptables/FORWARD 策略重置 | swapon --show、检查 ip_forward |
| 新节点加入后 NotReady,Describe 只有 NetworkUnavailable | CNI 未安装或配置文件缺失 | ls /etc/cni/net.d/,重应用 CNI 清单 |
| 空间未满但报 DiskPressure | inode 耗尽 | df -i,清理小文件堆积目录 |
| 节点经常性短暂 NotReady 后自愈 | NTP 时钟跳变、负载过高导致心跳超时 | chronyc tracking、检查 node lease |
六、标准化排查步骤清单
kubectl get nodes与kubectl describe node,读出五种 Condition 的状态。kubectl get events -A --sort-by=.metadata.creationTimestamp,找 KubeletNotReady、NodeNotReady、驱逐类事件。- 登录节点,
systemctl status kubelet+journalctl -u kubelet -f,看 kubelet 是否存活、是否有报错。 - 验证容器运行时:
systemctl status containerd、crictl ps,必要时用crictl绕过 kubelet 直连。 - 检查网络连通:
curl -k https://,确认节点能访问 APIServer。/healthz - 检查证书有效期:
openssl x509 -noout -dates与kubeadm certs check-expiration。 - 检查四种资源压力:
free -h、df -h、df -i、线程数/PID 上限。 - 若 NetworkUnavailable:检查
/etc/cni/net.d/、/opt/cni/bin/、kube-system 网络插件 Pod 与日志。 - 确认基础环境:
swapoff -a、sysctl net.ipv4.ip_forward=1、时间同步、br_netfilter模块已加载。 - 修复后重启 kubelet 验证:
systemctl restart kubelet,等待kubectl get nodes恢复 Ready。 - 确认节点无需封锁后恢复调度:
kubectl uncordon <节点名>(如果你之前执行过 cordon)。
# 修复期间的标准操作(防止新 Pod 继续调度上来) kubectl cordon <节点名> # 如需排空节点进行维修 kubectl drain <节点名> --ignore-daemonsets --delete-emptydir-data --force # 维护完成后恢复 kubectl uncordon <节点名> # 基础环境检查合集 sysctl net.bridge.bridge-nf-call-iptables net.ipv4.ip_forward lsmod | grep -E 'br_netfilter|overlay' swapon --show getenforce systemctl status firewalld # CNI 需要放行节点间 VXLAN/UDP 8472(flannel)、179(calico BGP)、6443、10250 端口
小结
Kubernetes 节点 NotReady 的排查,本质是沿着 Node Conditions → Events → kubelet 日志 → 容器运行时 → CNI → 资源 这条链路收敛。绝大多数情况能在前三步(describe + events + journalctl)就找到答案。四种压力状态各有专属方向:MemoryPressure 看 MemAvailable 和 cgroup 限制,DiskPressure 看 nodefs/imagefs 的容量与 inode,PIDPressure 看线程总数与 pid_max,NetworkUnavailable 则基本等同于 CNI 插件故障。
避坑建议:
- 别只看
MemFree,page cache 是可回收的,MemAvailable才是 kubelet 的判断依据。 - inode 耗尽比磁盘满更隐蔽,
df -h正常但df -i已 100%,排查时两个都要查。 - 批量节点同时 NotReady,优先怀疑证书过期和时间不同步,而不是逐个节点排查。
- 修 containerd/docker 前先能
crictl ps,它是独立于 kubelet 的验证通道。 - 不要直接删 CNI 配置文件而不重装,会导致节点永久 NotReady 且新 Pod 起不来。
- 核心业务设置 requests/limits 使其 QoS 为 Guaranteed,可以在资源紧张时最后被驱逐。
- 给节点配置巡检:磁盘 >80%、内存 >85%、负荷过高提前告警,比故障后再救火成本低得多。
延伸阅读:如果节点是 Ready 但上面的 Pod 反复重启(CrashLoopBackOff),排查重心就转移到 Pod 事件、容器退出码与健康检查上了。
企业QQ咨询




