跳转至

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
kubectl describe node
  • 观测点:查看输出尾部的 Events。如果出现 dial unix /var/run/containerd/containerd.sock: connect: no such file or directory,说明 Kubelet 尝试连接 Containerd 的 socket 文件时,发现物理文件不存在。

步骤二:登录故障节点检查底层服务

明确是连接运行时失败后,进入故障节点检查 Containerd 进程状态:

# 检查 containerd 服务状态
systemctl status containerd
  • 观测点:如果服务状态显示 Active: active (running),说明进程正常运行。此时大概率是服务监听的 Socket 路径配置有误。

步骤三:核对双方配置文件中的 Socket 路径

检查 Containerd 实际监听的 Socket 名称:

# 查看 containerd 配置文件的头部内容
head -n 20 /etc/containerd/config.toml

  • 比对逻辑:重点核对 [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
回到 Master 节点验证,确认节点恢复为 Ready 状态:
kubectl get nodes

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}"