Kubernetes服务器NotReady怎么办?状态、网络与资源排查

早上到公司打开监控面板,发现集群里两个节点变成 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节点健康,可以接受 Podkubelet 停止、证书过期、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 -q 
systemctl 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 expiredkubelet 客户端证书过期kubeadm certs renew all 后重启 kubelet
Pod 容器全部正常,节点却 NotReady节点到 APIServer 网络中断或 DNS 解析失败curl 探测 6443 端口
节点曾重启,之后一直 NotReadyswap 被重新挂载、iptables/FORWARD 策略重置swapon --show、检查 ip_forward
新节点加入后 NotReady,Describe 只有 NetworkUnavailableCNI 未安装或配置文件缺失ls /etc/cni/net.d/,重应用 CNI 清单
空间未满但报 DiskPressureinode 耗尽df -i,清理小文件堆积目录
节点经常性短暂 NotReady 后自愈NTP 时钟跳变、负载过高导致心跳超时chronyc tracking、检查 node lease

六、标准化排查步骤清单

  1. kubectl get nodes 与 kubectl describe node,读出五种 Condition 的状态。
  2. kubectl get events -A --sort-by=.metadata.creationTimestamp,找 KubeletNotReady、NodeNotReady、驱逐类事件。
  3. 登录节点,systemctl status kubelet + journalctl -u kubelet -f,看 kubelet 是否存活、是否有报错。
  4. 验证容器运行时:systemctl status containerd、crictl ps,必要时用 crictl 绕过 kubelet 直连。
  5. 检查网络连通:curl -k https:///healthz,确认节点能访问 APIServer。
  6. 检查证书有效期:openssl x509 -noout -dates 与 kubeadm certs check-expiration。
  7. 检查四种资源压力:free -h、df -h、df -i、线程数/PID 上限。
  8. 若 NetworkUnavailable:检查 /etc/cni/net.d/、/opt/cni/bin/、kube-system 网络插件 Pod 与日志。
  9. 确认基础环境:swapoff -a、sysctl net.ipv4.ip_forward=1、时间同步、br_netfilter 模块已加载。
  10. 修复后重启 kubelet 验证:systemctl restart kubelet,等待 kubectl get nodes 恢复 Ready。
  11. 确认节点无需封锁后恢复调度: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 事件、容器退出码与健康检查上了。