Node NotReady
01 前置理论与核心机制¶
1. 节点状态汇报链路¶
- 汇报机制:K8s 的工作节点(Node)状态并非黑盒。Kubelet 是节点上的核心代理组件,它每隔一段时间(默认 10 秒)向 Master 节点(API Server)发送心跳,汇报自身的健康状况。
- NotReady 的本质:当 Master 节点连续一段时间(默认 40 秒)未收到某个 Kubelet 的心跳,或者 Kubelet 自身检测到关键组件(如容器运行时 Containerd、网络插件 CNI)瘫痪时,会将该节点标记为 NotReady。
2. Kubelet 与 Containerd 的协作¶
- CRI(容器运行时接口):Kubelet 并不直接拉起容器,而是通过 UNIX Socket(套接字文件)与底层的 Containerd 进行 gRPC 通信。
- 通信断裂的后果:如果双方约定的 Socket 路径不一致(如文件不存在或权限错误),Kubelet 无法连接 Containerd,进而无法管理 Pod 生命周期,节点状态会迅速变为 NotReady。
02 故障排查与恢复闭环 SOP¶
遇到节点突然变为 NotReady,不建议第一时间盲目重启 Kubelet 或服务器。按通信链路逐步排查,可快速定位根因。
步骤一:在 Master 节点提取报错事件¶
首先通过控制平面掌握的信息,判断 Kubelet 在失联前的异常日志:
- 观测点:查看输出尾部的 Events。如果出现 dial unix /var/run/containerd/containerd.sock: connect: no such file or directory,说明 Kubelet 尝试连接 Containerd 的 socket 文件时,发现物理文件不存在。
步骤二:登录故障节点检查底层服务¶
明确是连接运行时失败后,进入故障节点检查 Containerd 进程状态:
- 观测点:如果服务状态显示 Active: active (running),说明进程正常运行。此时大概率是服务监听的 Socket 路径配置有误。
步骤三:核对双方配置文件中的 Socket 路径¶
检查 Containerd 实际监听的 Socket 名称:
- 比对逻辑:重点核对 [grpc] 段落下的 address 字段。
- 发现异常:默认的 socket 名称应为 containerd.sock。如果配置中存在拼写错误(例如漏打字母 d,写成了 container.sock),Containerd 会监听错误的新名称,而客户端 Kubelet 仍按照默认路径请求,导致通信失败。
步骤四:修正配置并重启服务恢复¶
将配置修改为标准的默认路径,使双方重新建立连接:
# 修正错误的 socket 名称
sed -i 's/container\.sock/containerd\.sock/' /etc/containerd/config.toml
# 依次重启底层运行时与 Kubelet
systemctl restart containerd
systemctl restart kubelet
03 真实生产故障现象与案例库¶
案例 1:人为修改配置导致 Socket 路径错位¶
- 监控现象:运维执行批量配置变更后,某台工作节点突然变为 NotReady。
- 排查结果:kubectl describe node 提示找不到 containerd.sock。检查发现 Ansible 分发 config.toml 时文件内容存在拼写错误(缺少字母 d)。
- 根因与处置:Kubelet 与 Containerd 的握手路径不一致。修正配置文件中的 address 路径后,重启相关组件即恢复。
案例 2:PLEG 超时导致节点假死¶
- 监控现象:节点状态在 Ready 和 NotReady 之间反复切换,但机器 CPU 和系统负载处于低位。
- 排查结果:查看 /var/log/messages 或 Kubelet 日志,发现大量报错 PLEG is not healthy: pleg was last seen active 3m ago。
- 根因与处置:PLEG(Pod 生命周期事件生成器)是 Kubelet 内部监控容器状态的核心组件。当底层 Docker/Containerd 响应极度缓慢(如受到系统底层 IO 阻塞或 D 状态进程影响),导致 PLEG 轮询超时,Kubelet 会判断底层瘫痪并上报 NotReady。需使用 iotop 等工具排查节点是否存在严重的 IO 拥塞。
案例 3:系统资源耗尽触发驱逐与失联¶
- 监控现象:节点变为 NotReady,同时其上运行的 Pod 处于 Evicted(被驱逐)或 Unknown 状态。
- 排查结果:Kubelet 日志提示 System OOM encountered 或 DiskPressure。
- 根因与处置:节点物理内存或根磁盘空间耗尽。Kubelet 进程因无法申请内存或写入日志而陷入僵死状态,无法向 Master 汇报心跳。处置方案为登录节点清理磁盘空间,或终止异常消耗内存的进程,随后重启 Kubelet。
04 生产排障速查表(Node NotReady)¶
遇到节点失联请按序单向排查:顺序排查层级执行命令 / 观测点异常基线与定性处置动作01全局视角kubectl describe node <node>查看底部的 Events 报错信息提取关键报错(网络/存储/容器运行时)02状态排查systemctl status kubelet状态异常或持续重启执行 journalctl -u kubelet 查找崩溃原因03运行排查systemctl status containerd状态并非 running 或挂起重启 Containerd 或检查其错误日志04路径比对检查 /var/run/containerd/找不到对应的 .sock 文件核对 config.toml 中的 address 配置05资源排查free -h 和 df -h内存耗尽或根分区磁盘 100%清理磁盘残余文件或终止 OOM 进程06恢复验证kubectl get nodes状态切回 Ready确认业务 Pod 被重新正常调度
05 面试应答参考话术¶
面试官问:“K8s 集群突然有一个工作节点变成了 NotReady,你怎么排查?”
可以这样回答:遇到 Node NotReady 告警,不建议盲目回答“直接重启 Kubelet 或服务器”。排查的核心在于理清 Kubelet 与底层组件的协作链路,沿着该链路逐层排查,可快速定位阻塞点。第一步:在 Master 节点查看异常事件
首先执行 kubectl describe node 查看该节点的 Events。如果 Kubelet 明确抛出错误,例如提示无法连接 /var/run/containerd/containerd.sock 套接字文件,即可基本确定是 Kubelet 与底层容器运行时的通信发生了断裂。第二步:进入故障节点排查服务与配置
随后登录该故障节点,执行 systemctl status containerd 检查服务是否正常运行。如果进程处于 running 状态,说明进程本身并未崩溃,大概率是监听的 Socket 路径配置有误。 此时可直接检查 containerd 的配置文件 config.toml。例如生产中曾遇到过配置文件中的 socket 名称存在拼写错误(如少打一个字母,变为 container.sock)。这会导致 Containerd 监听新名称,而 Kubelet 依然按照默认路径发起连接,最终导致失联。第三步:修正配置并恢复节点
定位问题后,将该路径修正为标准的 containerd.sock,并依次执行 systemctl restart containerd 和 kubelet。返回 Master 节点验证,节点即可恢复 Ready 状态。 总结来说,排查 Node NotReady 需先通过 describe 提取现场报错,再到故障机器上顺着 Kubelet、Containerd 以及系统资源的顺序逐层比对。
06 面试高频追问与应答预案¶
追问 1:如果 systemctl status kubelet 看到服务是正常的,但节点依然 NotReady,可能是什么原因?¶
- 回答要点:
- PLEG 超时:Kubelet 进程存活,但底层的 Docker/Containerd 停止响应(通常由于底层 IO 卡死),导致 Kubelet 同步容器状态超时。
- 网络插件异常:CNI 插件(如 Calico/Flannel)崩溃或配置丢失,导致 Node 的 NetworkReady 条件未满足,Kubelet 会将自身标记为 NotReady。
- 资源压力触发限制:节点达到硬驱逐阈值(如 DiskPressure、MemoryPressure),Kubelet 主动阻断调度并改变自身状态。
追问 2:节点变成 NotReady 后,其上运行的 Pod 会受到什么影响?¶
- 回答要点:
- Pod 不会立刻被删除。K8s 控制平面设有宽限期(默认由 Kube-controller-manager 的 pod-eviction-timeout 参数控制,通常为 5 分钟)。
- 超过 5 分钟后,K8s 会将该节点上的 Pod 状态标记为 Terminating。如果 Pod 由 Deployment 管理,控制器会在其他健康节点上拉起新的 Pod 副本。
- 如果是 StatefulSet,出于数据一致性保护机制,K8s 通常不会强制在其他节点拉起新副本,除非人工确认节点宕机并强制删除原 Pod。
07 一键节点失联巡检脚本(check_node_notready.sh)¶
该脚本用于在故障节点本地一键排查 Kubelet、Containerd 运行状态及关键 Socket 连通性。
#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_node_notready.sh
# 功能描述: 节点 NotReady 本地链路一键排查 (纯只读)
# 使用方式: 在故障 Node 节点以 root 权限执行
# ==============================================================================
set -o pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'
echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE} Node NotReady 节点底层状态诊断 ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"
# 1. 检查核心组件运行状态
echo -e "\n${YELLOW}[1/4] 检查系统服务进程状态...${PLAIN}"
for svc in kubelet containerd docker; do
if systemctl list-unit-files | grep -q "^${svc}.service"; then
IS_ACTIVE=$(systemctl is-active "$svc" 2>/dev/null)
if [ "$IS_ACTIVE" == "active" ]; then
echo -e " -> ${GREEN}[OK] ${svc} 服务运行正常 (Active)。${PLAIN}"
else
echo -e " -> ${RED}[WARN] ${svc} 服务异常,当前状态: ${IS_ACTIVE}。请查阅 journalctl -u ${svc}。${PLAIN}"
fi
fi
done
# 2. 检查 Containerd Socket 通信路径
echo -e "\n${YELLOW}[2/4] 检查 Containerd Socket 配置与文件...${PLAIN}"
CONF_PATH="/etc/containerd/config.toml"
if [ -f "$CONF_PATH" ]; then
# 提取配置文件中配置的 address
SOCK_CONF=$(grep 'address' "$CONF_PATH" | grep '\.sock' | awk -F'"' '{print $2}' | head -n 1)
if [ -n "$SOCK_CONF" ]; then
echo -e " -> 配置文件中的 Socket 路径: ${SOCK_CONF}"
if [ -S "$SOCK_CONF" ]; then
echo -e " -> ${GREEN}[OK] Socket 文件实际存在,路径一致。${PLAIN}"
else
echo -e " -> ${RED}[WARN] 物理文件不存在!配置可能异常或服务未成功监听。${PLAIN}"
fi
else
echo -e " -> ${YELLOW}[INFO] 未在 config.toml 中提取到明文 socket address,可能使用了默认配置。${PLAIN}"
fi
else
echo -e " -> ${YELLOW}[INFO] 未找到 $CONF_PATH 文件。${PLAIN}"
fi
# 3. 检查常规默认套接字是否存在
echo -e "\n${YELLOW}[3/4] 检查默认通信通道物理连通性...${PLAIN}"
DEFAULT_SOCK="/var/run/containerd/containerd.sock"
if [ -S "$DEFAULT_SOCK" ]; then
echo -e " -> ${GREEN}[OK] 默认 Socket ($DEFAULT_SOCK) 存在并可读。${PLAIN}"
else
echo -e " -> ${RED}[WARN] 默认 Socket ($DEFAULT_SOCK) 丢失!如果是 Containerd 节点,Kubelet 将无法通信。${PLAIN}"
fi
# 4. 检查是否有引发失联的资源压力
echo -e "\n${YELLOW}[4/4] 检查系统资源硬限制 (Disk/Memory)...${PLAIN}"
MEM_FREE=$(free -m | awk 'NR==2{print $4}')
if [ "$MEM_FREE" -lt 200 ]; then
echo -e " -> ${RED}[WARN] 节点可用物理内存不足 200MB (${MEM_FREE}MB),易触发 Kubelet 僵死或 Eviction。${PLAIN}"
else
echo -e " -> ${GREEN}[OK] 内存容量相对安全 (可用 ${MEM_FREE}MB)。${PLAIN}"
fi
DISK_USE=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
if [ "$DISK_USE" -ge 90 ]; then
echo -e " -> ${RED}[WARN] 根分区使用率达到 ${DISK_USE}\%,易触发 DiskPressure 导致节点 NotReady。${PLAIN}"
else
echo -e " -> ${GREEN}[OK] 根分区使用率正常 (${DISK_USE}\%)。${PLAIN}"
fi
echo -e "\n${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}诊断结束。若 Socket 路径正常但仍无法连接,请检查是否在集群 Master 执行了 kubectl describe 提取事件。${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"