找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5354

积分

0

好友

739

主题
发表于 5 小时前 | 查看: 2| 回复: 0

一个动态的黄色箭头指示图标,表示加载或状态切换

凌晨三点被刺耳的告警叫醒,Kubernetes 集群某个节点状态突然变成了 NotReady,上面的 Pod 被驱逐,服务开始抖动——这种场景相信每个 K8s 运维都经历过。节点 NotReady 是 Kubernetes 运维中最常见也最令人头疼的问题之一,原因五花八门:可能是 kubelet 挂了,可能是 Docker/containerd 卡死了,可能是网络插件出问题了,也可能是节点资源耗尽了。

这篇文章将我们团队遇到过的各种 NotReady 场景整理成一份系统化的自查手册,旨在帮助你在 15 分钟内定位问题根因。建议收藏,下次出问题直接按清单排查。

技术特点

  • 系统化排查:按照优先级从高到低的顺序排查,避免遗漏关键点。
  • 快速定位:每个检查点都给出具体命令和预期输出,一看就知道问题在哪。
  • 覆盖全面:涵盖 kubelet、容器运行时、网络、存储、资源等各种可能原因。
  • 实战验证:所有内容均从实际生产故障中总结而来,绝非纸上谈兵。

适用场景

  • 场景一:节点突然 NotReady,需要快速定位原因。
  • 场景二:节点频繁在 Ready 和 NotReady 之间切换(Flapping)。
  • 场景三:新节点加入集群后无法变成 Ready 状态。
  • 场景四:集群升级或节点维护后,节点无法恢复正常。

环境要求

组件 版本要求 说明
Kubernetes 1.20+ 本文以 1.28 为例
容器运行时 containerd 1.6+ 或 Docker 20+ 推荐使用 containerd
操作系统 Ubuntu 20.04+ / CentOS 7+ 内核版本 4.15+
网络插件 Calico/Flannel/Cilium 根据实际环境选择

二、详细排查步骤

2.1 准备工作

◆ 2.1.1 收集基本信息

首先,我们需要确认问题的范围和节点的基本信息。

# 首先确认哪些节点 NotReady
kubectl get nodes

# 示例输出:
# NAME        STATUS     ROLES           AGE   VERSION
# node-01     Ready      control-plane   90d   v1.28.2
# node-02     NotReady   <none>          90d   v1.28.2
# node-03     Ready      <none>          90d   v1.28.2

# 查看 NotReady 节点的详细信息
kubectl describe node node-02

# 重点看 Conditions 部分
kubectl get node node-02 -o jsonpath='{.status.conditions
  • }' | jq .
  • ◆ 2.1.2 理解 Node Conditions

    节点的健康状态由几个核心的 Condition 决定,任何一个异常都可能导致节点不可用。

    Condition 正常值 含义
    Ready True 节点健康,可以接受 Pod
    MemoryPressure False 节点内存充足
    DiskPressure False 节点磁盘空间充足
    PIDPressure False 节点 PID 充足
    NetworkUnavailable False 网络插件正常配置

    2.2 核心排查流程(按优先级)

    ◆ 2.2.1 第一步:检查 kubelet 状态

    kubelet 是节点上最重要的组件,负责向 API Server 汇报节点状态。如果 kubelet 挂了,节点肯定会变成 NotReady。

    # SSH 到问题节点
    ssh node-02
    
    # 检查 kubelet 服务状态
    systemctl status kubelet
    
    # 如果 kubelet 未运行
    systemctl start kubelet
    systemctl enable kubelet
    
    # 查看 kubelet 日志,寻找错误原因
    journalctl -u kubelet -f --since "10 minutes ago"
    
    # 过滤常见错误关键字
    journalctl -u kubelet | grep -E "error|failed|unable" | tail -50

    说明:kubelet 日志是排查问题的第一手资料。常见的 kubelet 问题包括:证书过期、配置文件错误、无法连接 API Server 等。对于复杂的集群运维问题,系统化的日志分析是关键。

    ◆ 2.2.2 第二步:检查容器运行时

    如果 kubelet 正常,接下来需要检查容器运行时(如 containerd 或 Docker)是否健康。

    # containerd 环境
    systemctl status containerd
    crictl info
    crictl ps -a | head -20
    
    # Docker 环境
    systemctl status docker
    docker info
    docker ps -a | head -20
    
    # 检查运行时是否响应(containerd)
    crictl version
    
    # 如果 crictl 命令超时不响应,说明 containerd 可能卡住了
    # 需要尝试重启 containerd
    systemctl restart containerd

    容器运行时常见问题

    1. containerd/Docker 进程僵死,需要重启。
    2. 存储驱动问题导致容器无法创建。
    3. 容器数量过多导致运行时性能下降。
    # 检查容器数量
    crictl ps -a | wc -l
    
    # 如果有大量 Exited 状态的容器,可以清理一下
    crictl rm $(crictl ps -a -q --state exited)

    ◆ 2.2.3 第三步:检查网络插件

    节点需要正常的网络才能与集群其他部分通信。网络插件(CNI)故障是导致 NotReady 的常见原因。

    # 查看 CNI 插件 Pod 状态
    kubectl get pods -n kube-system -l k8s-app=calico-node  # Calico
    kubectl get pods -n kube-system -l app=flannel          # Flannel
    kubectl get pods -n kube-system -l k8s-app=cilium       # Cilium
    
    # 在节点上检查 CNI 配置文件
    ls -la /etc/cni/net.d/
    
    # 检查 CNI 二进制文件
    ls -la /opt/cni/bin/
    
    # 查看 CNI 插件日志
    kubectl logs -n kube-system calico-node-xxxxx -c calico-node --tail=100
    
    # 检查节点网络接口和路由
    ip addr show
    ip route show

    网络插件常见问题

    1. CNI 配置文件损坏或缺失。
    2. CNI Pod 异常退出。
    3. IPAM 地址池耗尽。
    4. 网络策略阻止了关键通信。
    # 检查节点是否能访问 API Server(在节点上执行)
    curl -k https://10.96.0.1:443/healthz
    
    # 检查 Pod CIDR 分配
    kubectl get node node-02 -o jsonpath='{.spec.podCIDR}'

    ◆ 2.2.4 第四步:检查系统资源

    资源耗尽(如磁盘、内存、PID)会直接导致节点压力状态异常,进而变为 NotReady。

    # 检查内存
    free -h
    # 如果 available 内存很低,可能是内存压力
    
    # 检查磁盘空间和 inode
    df -h
    df -i  # inode 使用情况也要看
    
    # 检查 CPU 负载
    top -bn1 | head -20
    uptime
    
    # 检查 PID 使用情况
    cat /proc/sys/kernel/pid_max
    ls /proc | grep -E '^[0-9]+$' | wc -l

    资源相关常见问题

    1. 磁盘空间不足(特别是 /var/lib/docker/var/lib/containerd)。
    2. inode 耗尽(通常是生成了大量小文件)。
    3. 内存不足触发 OOM(Out-Of-Memory Killer)。
    4. PID 耗尽。
    # 查看最近的 OOM 记录
    dmesg | grep -i "out of memory" | tail -10
    journalctl -k | grep -i "oom" | tail -10
    
    # 查看哪个目录占用空间最多
    du -sh /var/lib/* 2>/dev/null | sort -rh | head -10
    
    # 清理容器日志(临时解决方案)
    find /var/lib/docker/containers -name "*.log" -size +100M -exec truncate -s 0 {} \;

    ◆ 2.2.5 第五步:检查证书和时间

    证书过期或节点时间与集群不同步,会导致身份验证失败,从而使节点失联。

    # 检查 kubelet 客户端证书是否过期
    openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates
    
    # 检查系统时间是否正确
    date
    timedatectl status
    
    # 如果时间不同步,重启时间同步服务
    systemctl restart chronyd  # 或 systemd-timesyncd
    
    # 检查与 API Server 的时间差
    # 时间差太大会导致证书验证失败

    证书和时间常见问题

    1. kubelet 证书过期。
    2. 系统时间与集群控制平面不同步。
    3. CA 根证书变更后,节点未及时更新。

    2.3 快速恢复操作

    ◆ 2.3.1 常规重启流程

    如果通过上述排查找到了可疑组件,可以尝试按顺序重启。

    # 按顺序重启组件(建议先在业务低峰期操作)
    systemctl restart containerd  # 或 docker
    sleep 10
    systemctl restart kubelet
    
    # 等待并观察节点恢复状态
    watch kubectl get node node-02
    
    # 如果节点恢复后还是 NotReady,检查是否有异常的 Taint
    kubectl describe node node-02 | grep -A5 Taints

    ◆ 2.3.2 节点重新加入集群

    如果上述方法都无法解决问题,可以考虑让节点重新加入集群(这是最后的手段)。

    # 在控制平面节点上,先安全驱逐并删除节点
    kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data
    kubectl delete node node-02
    
    # 在 node-02 上重置 Kubernetes 组件
    kubeadm reset -f
    rm -rf /etc/cni/net.d/*
    rm -rf /var/lib/kubelet/*
    
    # 重新加入(需要 token,如果过期需要重新生成)
    # 在控制平面节点上生成新的 join 命令
    kubeadm token create --print-join-command
    
    # 在 node-02 上执行上一步生成的 join 命令
    kubeadm join ...

    三、示例代码与配置

    3.1 完整配置示例

    ◆ 3.1.1 自动化排查脚本

    将排查步骤脚本化,可以极大提高效率。以下是一个综合检查脚本示例:

    #!/bin/bash
    # 文件名:k8s-node-check.sh
    # 功能:Kubernetes 节点健康检查脚本
    
    set -e
    
    NODE_NAME=$1
    
    if [ -z "$NODE_NAME" ]; then
      echo "用法: $0 <node-name>"
      exit 1
    fi
    
    echo "=========================================="
    echo "Kubernetes 节点健康检查: $NODE_NAME"
    echo "=========================================="
    
    # 1. 基本状态
    echo ""
    echo "[1/8] 节点基本状态"
    echo "-------------------------------------------"
    kubectl get node $NODE_NAME -o wide
    
    # 2. Node Conditions
    echo ""
    echo "[2/8] Node Conditions"
    echo "-------------------------------------------"
    kubectl get node $NODE_NAME -o jsonpath='{range .status.conditions
  • }{.type}: {.status} ({.reason}) - {.message}{"\n"}{end}' # 3. 资源使用情况 echo "" echo "[3/8] 资源使用情况" echo "-------------------------------------------" kubectl top node $NODE_NAME 2>/dev/null || echo "metrics-server 未安装或不可用" # 4. 运行的 Pod 数量 echo "" echo "[4/8] 运行的 Pod 数量" echo "-------------------------------------------" kubectl get pods --all-namespaces --field-selector spec.nodeName=$NODE_NAME --no-headers | wc -l # 5. 系统 Pod 状态 echo "" echo "[5/8] 系统 Pod 状态" echo "-------------------------------------------" kubectl get pods -n kube-system --field-selector spec.nodeName=$NODE_NAME # 6. 节点事件 echo "" echo "[6/8] 最近节点事件" echo "-------------------------------------------" kubectl get events --field-selector involvedObject.name=$NODE_NAME --sort-by='.lastTimestamp' | tail -10 # 7. Taints echo "" echo "[7/8] 节点 Taints" echo "-------------------------------------------" kubectl get node $NODE_NAME -o jsonpath='{.spec.taints}' | jq . 2>/dev/null || echo "无 Taints" # 8. 容量和分配 echo "" echo "[8/8] 节点容量和可分配资源" echo "-------------------------------------------" echo "Capacity:" kubectl get node $NODE_NAME -o jsonpath='{.status.capacity}' | jq . echo "" echo "Allocatable:" kubectl get node $NODE_NAME -o jsonpath='{.status.allocatable}' | jq . echo "" echo "==========================================" echo "检查完成" echo "=========================================="
  • ◆ 3.1.2 节点监控告警规则(Prometheus)

    预防胜于治疗。配置监控告警可以在节点出现问题时第一时间通知你。

    # prometheus-node-alerts.yaml
    groups:
    - name: kubernetes-node
      rules:
      - alert: KubernetesNodeNotReady
        expr: kube_node_status_condition{condition="Ready",status="true"} == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Kubernetes 节点 NotReady"
          description: "节点 {{ $labels.node }} 已经 NotReady 超过 5 分钟"
    
      - alert: KubernetesNodeMemoryPressure
        expr: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "节点内存压力"
          description: "节点 {{ $labels.node }} 存在内存压力"
    
      - alert: KubernetesNodeDiskPressure
        expr: kube_node_status_condition{condition="DiskPressure",status="true"} == 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "节点磁盘压力"
          description: "节点 {{ $labels.node }} 存在磁盘压力"
    
      - alert: KubernetesNodePIDPressure
        expr: kube_node_status_condition{condition="PIDPressure",status="true"} == 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "节点 PID 压力"
          description: "节点 {{ $labels.node }} 存在 PID 压力"
    
      - alert: KubeletDown
        expr: up{job="kubelet"} == 0
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "Kubelet 不可达"
          description: "节点 {{ $labels.instance }} 的 kubelet 无法访问"

    3.2 实际应用案例

    ◆ 案例一:containerd 僵死导致 NotReady

    场景描述:某天早上发现一个节点 NotReady,上面的 Pod 全部被驱逐。登录节点后发现 kubelet 进程在运行,但日志里一直报 containerd 调用超时。

    排查过程

    # 查看 kubelet 日志
    journalctl -u kubelet --since "1 hour ago" | grep -i error
    
    # 发现大量这样的错误:
    # "failed to get container status" err="rpc error: code = DeadlineExceeded desc = context deadline exceeded"
    
    # 检查 containerd 状态
    systemctl status containerd
    # Active: active (running) 但实际上已经僵死
    
    # 尝试执行 crictl 命令,卡住不返回
    timeout 10 crictl ps
    # 超时
    
    # 检查 containerd 进程
    ps aux | grep containerd
    # 进程在但 CPU 使用为 0,说明僵死了

    解决方案

    # 强制停止 containerd 及相关进程
    systemctl stop containerd
    pkill -9 containerd-shim
    pkill -9 containerd
    
    # 清理可能的运行时残留文件
    rm -rf /run/containerd/*
    
    # 重启服务
    systemctl start containerd
    systemctl restart kubelet
    
    # 验证恢复
    crictl ps
    kubectl get node

    根因分析:事后分析发现,是某个容器挂载的 volume 出现问题,导致 containerd 在处理该容器时卡死。建议定期巡检并清理异常状态的容器。

    ◆ 案例二:磁盘空间不足导致 NotReady

    场景描述:节点 NotReady,kubectl describe node 显示 Condition 中 DiskPressureTrue

    排查过程

    # 查看磁盘使用
    df -h
    # /var/lib/docker  200G  195G  5G  98% /var/lib/docker
    
    # 找出大文件或目录
    du -sh /var/lib/docker/* | sort -rh | head
    # 100G /var/lib/docker/overlay2
    # 80G  /var/lib/docker/containers
    
    # 检查容器日志大小
    ls -lhS /var/lib/docker/containers/*/
    # 发现某个容器的日志文件达到了 50G

    解决方案

    # 临时方案:截断过大的日志文件
    truncate -s 0 /var/lib/docker/containers/xxx/*-json.log
    
    # 清理未使用的镜像、容器和卷
    docker system prune -a -f
    
    # 长期方案:配置 Docker 日志轮转
    # 编辑 /etc/docker/daemon.json
    {
      "log-driver": "json-file",
      "log-opts": {
        "max-size": "100m",
        "max-file": "3"
      }
    }
    
    # 重启 Docker 使配置生效
    systemctl restart docker

    实现步骤

    1. 为容器运行时配置日志轮转策略。
    2. 设置磁盘使用率监控告警(例如 80% 预警,90% 告警)。
    3. 建立定期清理无用镜像和容器的运维流程。

    四、最佳实践与注意事项

    4.1 最佳实践

    ◆ 4.1.1 预防性监控

    • 监控关键指标:利用 Prometheus 等工具监控节点健康度、资源使用率,提前发现问题。
    • 设置合理的资源预留:在 kubelet 配置中为系统和 Kubernetes 组件预留资源,避免它们被业务 Pod 挤占。
      # /var/lib/kubelet/config.yaml 片段
      kubeReserved:
        cpu: "500m"
        memory: "1Gi"
        ephemeral-storage: "10Gi"
      systemReserved:
        cpu: "500m"
        memory: "1Gi"
        ephemeral-storage: "10Gi"
      evictionHard:
        memory.available: "500Mi"
        nodefs.available: "10%"
        imagefs.available: "15%"
    • 部署节点问题检测器:使用 Node Problem Detector 自动检测硬件、内核和运行时问题。

    ◆ 4.1.2 安全与维护

    • 安全的节点维护流程:任何可能影响 Pod 的操作前,务必先隔离节点。
      kubectl cordon node-02           # 禁止调度新 Pod
      kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data  # 安全驱逐现有 Pod
      # ... 执行维护操作 ...
      kubectl uncordon node-02         # 恢复调度
    • 定期轮换证书:定期检查并更新集群证书,避免过期。
      kubeadm certs check-expiration
      kubeadm certs renew all

    4.2 注意事项与常见错误

    警告:切勿在没有执行 drain 操作的情况下,直接重启节点的 kubelet 或容器运行时,这可能导致运行中的 Pod 被意外终止。

    错误现象 原因分析 解决方案
    “failed to run kubelet: misconfiguration” kubelet 配置文件语法错误 检查 YAML 格式,使用 kubelet --config 验证配置
    “Unable to register node with API Server” 无法连接 API Server 检查网络连通性、防火墙规则和路由
    “certificate has expired” 证书过期 使用 kubeadm certs renew 更新证书
    “PLEG is not healthy” Pod 生命周期事件生成器异常 通常是容器运行时问题,尝试重启 containerd/docker
    “failed to garbage collect” 垃圾回收失败 检查磁盘空间,手动清理未使用的镜像和容器

    五、总结与进阶

    5.1 技术要点回顾

    • 排查顺序是关键:按照 kubelet → 容器运行时 → 网络插件 → 系统资源 → 证书/时间 的优先级进行排查,效率最高。
    • 日志是破案线索journalctl -u kubelet 和容器运行时日志是定位根因的第一现场。
    • 监控预防是上策:配置完善的监控告警,在用户感知前发现问题。
    • 维护操作要规范:操作节点前先 cordondrain,是对线上业务的基本尊重。

    5.2 进阶学习方向

    1. Node Problem Detector:深入学习如何自动检测节点层面的各类问题,实现初步自愈。
    2. Cluster Autoscaler:了解如何根据负载自动伸缩集群节点数量,应对流量波动。
    3. 深入 kubelet 原理:阅读 kubelet 源码或设计文档,理解其与容器运行时、CNI 的交互机制,从根本上提升运维深度。

    节点 NotReady 的排查是 Kubernetes 运维的基本功,希望这份手册能成为你工具箱中的得力助手。在云原生的复杂世界里,系统性的方法和持续的学习是应对挑战的最佳途径。如果你在实践中遇到了更棘手的问题或有独到的经验,欢迎在云栈社区与广大开发者交流分享。




    上一篇:我的 Obsidian 知识库是如何在三个月内悄然“腐烂”的:一次关于可信度维护的反思
    下一篇:KubeVirt 实战:2000台VMware虚拟机迁移至Kubernetes的完整方案与避坑指南
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-4-23 09:53 , Processed in 0.626479 second(s), 42 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表