Kubernetes节点NotReady怎么排查

描述

问题背景

生产环境中 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   NotReady             30d    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 问题时,保持冷静,按顺序排查,不要急于重启节点。重启节点会清除现场,让很多根因信息丢失。先查日志、再查资源、最后再决定是否重启。现场保留得越完整,定位根因就越快,下次预防也就越有针对性。

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分