公司动态
Kubernetes节点异常排查:从NodeCondition到驱逐策略的完整指南
在 Kubernetes 集群运维中“注意重要节点出现”并不是一句凭空出现的口号而是一条随时可能出现在监控告警里的真实信号。这里的“节点”指的是集群里的工作节点Node它是承载 Pod、容器、存储和网络流量的物理机或虚拟机当节点从 Ready 翻转为 NotReady或者出现 MemoryPressure、DiskPressure、PIDPressure 时集群的可用容量和调度结果会立刻改变运行在节点上的工作负载随时可能被驱逐。这篇内容以“节点状态异常”为主线从 NodeCondition 机制、kubelet 上报链路、典型故障现象、底层日志排查、驱逐策略和生产监控方案六个层面展开目标是让你遇到节点告警时能按链路定位而不是靠“重启一下试试”。1. 节点状态不是“玄学”先看清 NodeCondition 和 Ready 的判定逻辑很多人在听到“节点 NotReady”后第一反应是登录到机器上重启 kubelet。这样做的成功率并不高因为节点状态是 kubelet、apiserver、控制器和资源使用情况共同作用的结果。先搞清楚节点状态是怎么产生的后面排查才有方向。1.1 节点在 Kubernetes 中承担什么角色Kubernetes 集群在逻辑上分为控制平面和工作节点。控制平面上的 kube-apiserver、kube-scheduler、kube-controller-manager、etcd 负责管理集群状态工作节点上的 kubelet、容器运行时、kube-proxy、CNI 插件负责真正运行 Pod。节点是工作负载的“地基”。调度器在创建 Pod 时只会在 Ready 状态的节点上放置新 Pod。如果节点不健康调度器会暂时跳过它如果节点长时间处于 NotReadykube-controller-manager 里的 NodeLifecycleController 还会触发 Pod 清理把原本运行在故障节点上的 Pod 标记为删除并在其他可用节点上重建。这里有一个容易误解的点节点状态不只是“健康/不健康”的二元判断。Kubernetes 把节点状态拆成一组 NodeCondition每个条件都有各自的阈值、判定逻辑和传播方式。只看kubectl get nodes的第一列会丢失大量信息。1.2 NodeCondition节点状况是一组可编程状态每个节点的状态信息存放在 Node 对象的status.conditions数组中。一个 Condition 通常包含 Type、Status、Reason、Message、LastHeartbeatTime、LastTransitionTime 等字段。Type 表示条件类型Status 通常是 True、False 或 Unknown。最常见的 NodeCondition 类型如下Condition 类型含义异常表现Ready节点是否准备好接收新 PodFalse 或 UnknownMemoryPressure节点内存是否接近耗尽TrueDiskPressure根分区或镜像分区是否磁盘不足TruePIDPressure节点可创建的进程数量是否接近上限TrueNetworkUnavailable节点网络是否配置完成TrueReady 是维护人员最关注的条件但它并不是单一来源的结论。kubelet 会周期性检查容器运行时、网络探针、本地磁盘等状态然后汇总成 Ready 条件。如果 kubelet 无法启动或者容器运行时接口调用失败Ready 会被置为 FalseReason 通常是 KubeletNotReady。ReadyFalse 和 ReadyUnknown 的含义并不一样。ReadyFalse 说明 kubelet 明确上报了本地异常ReadyUnknown 则可能是长时间没有收到 kubelet 心跳控制平面无法确认节点状态。两者要按不同路径排查。1.3 从 kubelet 到控制平面的状态传播链路节点状态不是实时同步的而是周期性上报和监控的结果。整体链路可以概括为kubelet 启动后持续采集节点上的 CPU、内存、磁盘、PID、容器运行时、网络插件等状态。kubelet 按--node-status-update-frequency配置的间隔把 Node 对象的status提交给 kube-apiserver。kube-apiserver 将 NodeStatus 写入 etcd。NodeLifecycleController 定期检查节点的心跳时间。一旦发现节点超过宽限期没有上报会把 Ready 条件置为 Unknown。调度器读取节点状态决定是否继续向该节点调度 Pod。因此当你看到节点刚刚变成 NotReady实际上故障可能已经发生了一小段时间只是正在等待上报或者宽限期判定。恢复时也一样kubelet 先恢复健康再重新上报状态节点不会立刻变回 Ready。理解这条链路后排查时就不会纠结“为什么刚重启完还是 NotReady”。2. 实验环境准备观察“重要节点出现”的最小前提要真正理解节点异常建议准备一套可以随意折腾的实验环境。下面从环境要求、基础命令和告警观察工具三个方面说明。2.1 环境要求与版本确认Kubernetes 版本迭代比较快这里不把版本号写死。下面命令基于 kubeadm 部署的主流版本落地前先确认自己的 Kubernetes 版本和容器运行时版本。角色建议配置关键组件控制平面节点2 核 4G 以上kube-apiserver、etcd、kube-controller-manager、kube-scheduler工作节点2 核 4G 以上kubelet、containerd、CNI 插件操作系统Ubuntu 22.04 / CentOS Stream 9 等systemd、内核网络模块容器运行时containerd 1.7.x 或对应版本CRI 接口、crictl 工具宿主机可以选择虚拟机也可以直接用云厂商的云主机。实验环境最核心的要求是至少两台节点一台控制平面加一台工作节点。这样才能模拟节点网络中断、资源耗尽、kubelet 异常退出等场景。如果使用 containerd 作为容器运行时要保证 kubelet 的 cgroup driver 和 containerd 的 cgroup driver 一致。市面上很多节点异常案例根因就是这里不一致。Kubernetes 1.24 之后默认不再内置支持 Docker 作为直接运行时因此新环境建议直接使用 containerd 或 CRI-O。2.2 检查节点状态的基础命令集群部署完成后先记住一组基础命令。节点出现异常时下面四条命令能快速判断问题范围。kubectl get nodes -o wide kubectl describe node node01 kubectl get nodes --show-labels kubectl top nodekubectl get nodes -o wide可以查看节点的内部 IP、外部 IP、Kubernetes 版本、运行时版本。kubectl describe node node01是最重要的命令它会输出节点的内存、CPU 分配情况、NodeCondition 明细和最近事件。kubectl top node依赖 metrics-server如果未安装会报错但它能直接显示节点当前资源使用率。用 JSONPath 可以快速把集群里所有节点的 Ready 状态拉出来。这个命令适合写进巡检脚本kubectl get nodes -o jsonpath{range .items[*]}{.metadata.name}{\t}{.status.conditions[?(.typeReady)].status}{\n}{end}输出的第二列是 True、False 或 Unknown。任何一个不是 True都要进入排查流程。2.3 准备观察告警的命令行工具节点状态异常往往伴随着事件。Kubernetes 的事件默认保留时间有限但排障时要第一时间看。下面几个命令比较常用kubectl get events --all-namespaces --sort-by.lastTimestamp kubectl get events -n kube-system --field-selector typeWarning查看资源水位时可以配合watch命令持续观察watch -n 2 kubectl get nodes watch -n 5 kubectl top node实验环境中可以使用 SSH 登录节点手动停掉 kubelet 或塞满磁盘来模拟故障。例如停掉 kubelet 后过几十秒再执行kubectl get nodes会看到节点状态从 Ready 变为 NotReady。这个实验能直观理解心跳超时的判定过程。3. 典型故障现象NotReady、压力状态与抖动节点状态异常并不会只有一种表现。不同根因在 NodeCondition、Events 和日志里留下的线索完全不同。下面按三类高频现象展开。3.1 节点进入 NotReady 时的标准确认方式先用最直观的方式看状态kubectl get nodes输出中 STATUS 列会出现 NotReadyNAME STATUS ROLES AGE VERSION master Ready control-plane 45d v1.28.7 node01 NotReady none 32d v1.28.7继续执行kubectl describe node node01重点看 Conditions 段Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- NetworkUnavailable False ... ... CalicoIsUp ... MemoryPressure False ... ... KubeletHasSufficientMemory ... DiskPressure False ... ... KubeletHasNoDiskPressure ... PIDPressure False ... ... KubeletHasSufficientPID ... Ready False ... ... KubeletNotReady ...这里最关键的是 Ready 的 Reason 和 Message。如果 Message 是 KubeletNotReady说明故障发生在节点侧的 kubelet、容器运行时或系统资源上如果 Ready 是 Unknown则要优先检查控制平面和网络链路。有时kubectl describe node的 Events 段会出现驱逐记录、镜像拉取失败、节点文件系统只读等线索。这些信息比猜测根因可靠得多。3.2 压力类状态MemoryPressure、DiskPressure 与 PIDPressure节点可能仍然处于 Ready但已经出现压力条件。例如MemoryPressure True ... KubeletHasInsufficientMemory ... DiskPressure True ... KubeletHasDiskPressure ...MemoryPressure 表示节点可用内存不足。kubelet 通过 cgroup 读取memory.available当可用内存低于阈值时会触发 Pod 驱逐。常见原因包括业务 Pod 内存设置过大、没有设置 limits、节点上存在内存泄漏、系统页缓存被大量占用。DiskPressure 表示节点磁盘或镜像文件系统空间不足。常见原因包括容器日志未轮转、镜像堆积、本地 PV 数据增长、操作系统日志撑满根分区。DiskPressure 影响的不只是新 Pod 调度已经运行的容器也可能被 kubelet 驱逐。PIDPressure 表示节点 PID 数量接近 limit。常见原因包括某个 Pod 内进程数量失控、系统出现僵尸进程、容器运行时泄漏文件描述符。这个条件在日常排障中容易被忽略但表现非常明显节点上无法再创建新进程容器启动失败kubelet 也可能因此无法上报状态。3.3 抖动问题节点在 Ready 和 NotReady 之间反复有些场景下节点状态不是彻底 NotReady而是周期性 Ready、NotReady 反复切换。比如每十几分钟 alert 一次过几分钟又自动恢复。这类抖动通常和以下因素有关kubelet 心跳上报超时根因是节点负载过高或磁盘 I/O 抖动。apiserver 或 etcd 响应变慢导致大量节点同时出现 Unknown。容器运行时响应缓慢kubelet 调用 CRI 接口超时。DNS 或节点到 apiserver 的网络不稳定。排查时先看事件时间点。如果多个节点在同一时间窗口抖动问题大概率在控制平面如果只有单个节点异常重点检查该节点本身的资源使用和 kubelet 存活情况。4. 底层系统与组件排查链路从现象到根因节点异常的核心矛盾通常是 kubelet 和它依赖的下游组件出了问题。下面按排查优先级给出具体路径。4.1 第一步看 kubelet 是否真的活着登录节点后先检查 systemd 状态systemctl status kubelet sudo journalctl -u kubelet -n 200 --no-pager常见的 kubelet 侧问题包括kubelet 服务处于 failed 状态。证书过期日志里出现证书相关错误。kubelet 与容器运行时 cgroup driver 不一致。/var/lib/kubelet目录权限异常或磁盘只读。日志里如果出现failed to run Kubelet: misconfiguration通常需要检查/var/lib/kubelet/config.yaml和容器运行时的配置。出现failed to ensure that the daemon has enough rights to access /var/lib/kubelet需要检查目录权限和 SELinux。注意不要用systemctl restart kubelet来“掩盖”问题。如果 kubelet 是被磁盘占满或配置错误拖挂的重启后还会再次失败而且原来的日志会被覆盖排障难度更大。4.2 第二步看容器运行时的状态kubelet 必须通过 CRI 接口与容器运行时通信。containerd 异常时kubelet 也会报 KubeletNotReady。检查命令systemctl status containerd sudo crictl ps sudo crictl info如果crictl ps无法返回说明 containerd 的 gRPC 服务异常。可以继续看 containerd 日志sudo journalctl -u containerd -n 200 --no-pager常见原因包括磁盘空间不足导致 containerd 无法写数据、镜像存储目录损坏、containerd 版本和 kubelet 不兼容。4.3 第三步检查节点资源与文件系统即使 kubelet 和 containerd 都活着节点资源耗尽仍然会导致状态翻转。常用命令uptime free -m df -h df -h /var/lib/containerd dmesg | tail -n 100内存排查重点看free -m的 available 列而不是 total 和 used。磁盘排查要同时看根分区和容器数据目录。如果业务日志写在根分区而根分区没有独立挂载日志轮转配置不合理时很容易撑爆。dmesg里如果出现Out of memory或者进程被 kill 的记录说明节点或 cgroup 内发生过 OOM。这类信息在 Kubernetes Events 里不一定看得到必须登录节点。4.4 第四步检查网络组件与 apiserver 连通性节点上没有网络插件或者 CNI 组件异常新 Pod 无法获得 IP已有 Pod 也可能出现跨节点通信失败。查看集群内的网络插件状态kubectl get pods -n kube-system -o wide kubectl logs -n kube-system cni-pod -f同时在节点上检查 CNI 网卡ip a ip route ping apiserver-ip如果节点到 apiserver 的 6443 端口不通kubelet 无法上报状态节点也会被控制平面判定为 Unknown。可以通过 curl 验证 apiserver 健康curl -k https://apiserver-ip:6443/healthz4.5 第五步看控制平面尤其多个节点同时异常时如果多个节点同时进入 NotReady优先怀疑控制平面和网络而不是逐个节点排查。检查 kube-system 命名空间下的静态 Podkubectl get pods -n kube-system -o wide查看 apiserver 和 etcd 日志kubectl logs -n kube-system apiserver-pod --tail200 kubectl logs -n kube-system etcd-pod --tail200kubectl get componentstatuses在新版本里已经基本不可用不建议依赖。需要验证 etcd 健康时直接进入 etcd 容器执行 etcdctl或者使用 apiserver 的健康检查接口。常见控制平面问题有apiserver 压力过大请求超时。etcd 磁盘 fsync 变慢写入延迟升高。证书过期或节点被删除后重新加入出现认证失败。网络策略或防火墙把节点到 apiserver 的连接拦截。下表把排查链路整理成速查现象可能原因检查命令单节点 ReadyFalsekubelet 或容器运行时异常systemctl status kubelet、journalctl -u kubelet多节点 ReadyUnknown控制平面网络或 apiserver 过载kubectl get pods -n kube-system、curl apiserver /healthzMemoryPressureTrue业务 Pod 内存超限或节点内存不足kubectl top node、free -m、dmesgDiskPressureTrue日志或镜像占用磁盘df -h、df -h /var/lib/containerdPIDPressureTrue容器内进程泄漏或节点 PID 上限过低ps -eLfReady 状态抖动心跳超时、网络抖动、负载峰值kubectl describe node、事件时间对比5. 驱逐与 PDB节点异常时工作负载如何被保护节点状态异常之后真正影响业务的往往是 Pod 被驱逐和重建。理解驱逐机制才能判断哪些 Pod 会先消失、哪些 Pod 会被保留。5.1 kubelet 驱逐机制为什么节点压力会触发容器删除当节点出现 MemoryPressure 或 DiskPressure 时kubelet 会启动节点压力驱逐。驱逐的目的是保护节点本身不被完全拖垮但它牺牲的是节点上的非关键 Pod。Pod 被驱逐的顺序和 QoS 等级强相关。Kubernetes 将 Pod 划分为三类 QoS ClassQoS 等级条件被驱逐优先级Guaranteed所有容器都有 limits 且 request 等于 limit最低Burstable至少一个容器设置了 request 或 limit但不满足 Guaranteed中等BestEffort所有容器都没有设置 request 和 limit最高BestEffort Pod 会最先被驱逐Guaranteed Pod 会尽量保留。这里的取舍逻辑是没有设置资源上限的 Pod 往往是“吃资源”的大户优先牺牲它们可以更有效地恢复节点状态。5.2 驱逐阈值与宽限期kubelet 支持两种驱逐阈值硬阈值和软阈值。硬阈值达到后立即驱逐软阈值达到后等待宽限期结束或节点压力缓解后再驱逐。下面是一段 KubeletConfiguration 示例仅说明配置结构。apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration evictionHard: memory.available: 300Mi nodefs.available: 10% imagefs.available: 15% evictionSoft: memory.available: 500Mi evictionSoftGracePeriod: memory.available: 1m30sevictionHard.memory.available设置的是触发硬驱逐的内存阈值。nodefs.available和imagefs.available分别对应根文件系统和镜像文件系统。生产环境不要直接照抄应该根据节点规格和业务模型计算。阈值设置过小节点可能已经 OOM 才触发驱逐阈值设置过大正常负载波动也会引起不必要的驱逐。建议先基于监控数据观察节点资源水位再设置一个安全余量。注意资源型驱逐硬驱逐属于 kubelet 对节点的自我保护它不遵循 Pod 优雅终止流程也不受 PodDisruptionBudget 约束。PDB 只约束主动驱逐场景比如节点维护前执行的 drain。5.3 让驱逐更可控PodDisruptionBudget 的正确用法在业务上需要保证最小可用副本的服务应该配置 PodDisruptionBudgetPDB。例如某个应用希望至少保留两个副本apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-pdb spec: minAvailable: 2 selector: matchLabels: app: nginxPDB 的作用是在主动驱逐时阻止可用副本数降到minAvailable以下。比如这个 app 当前只有两个副本在运行执行kubectl drain时drain 会卡住直到副本在其他节点可用或手动忽略 PDB。但 PDB 不能防止硬件故障、不能防止 kubelet 硬驱逐、不能防止 Pod 所在的节点彻底宕机。它只能让主动维护更安全不能替代多副本和资源限制。5.4 观察驱逐事件如何判断 Pod 消失原因被驱逐的 Pod 会显示为 Evictedkubectl get pods -o wide | grep Evicted如果要看驱逐原因查看节点上的事件kubectl describe node node01 kubectl get events -A --field-selector reasonEvicted --sort-by.lastTimestamp事件里通常会写The node was low on resource: memory或imagefs等具体原因。看到 Evicted 后不要急着把 Pod 再调度回去先解决节点资源问题否则新 Pod 仍然会被再次驱逐。6. 预防与监控让“重要节点出现”前就能发现节点故障不可避免但可以通过监控、告警和标准处置流程缩短恢复时间。下面给出监控指标、告警规则和一组合格的预防清单。6.1 节点层面的监控指标生产环境通常使用 Prometheus 加 kube-prometheus 栈。节点侧重点看两类指标。第一类是节点系统指标来自 Node Exporternode_load1 node_memory_MemAvailable_bytes node_filesystem_avail_bytes node_processes_pids第二类是 Kubernetes 层面的节点状态指标来自 kube-state-metricskube_node_status_condition{conditionReady,statustrue} kube_node_status_condition{conditionMemoryPressure,statustrue} kube_node_status_condition{conditionDiskPressure,statustrue} kube_node_status_condition{conditionPIDPressure,statustrue}监控面板上应该同时展示资源使用率和 NodeCondition 状态。只看资源使用率看不到状态翻转的历史只看 NodeCondition又解释不了根因。6.2 告警规则示例节点不是一旦异常就立刻告警最好保留一个持续观察窗口避免偶发抖动造成误报。下面是一组 Prometheus 告警规则示例groups: - name: kubernetes-node rules: - alert: KubeNodeNotReady expr: kube_node_status_condition{conditionReady,statustrue} 0 for: 5m labels: severity: critical annotations: summary: Node {{ $labels.node }} is not ready - alert: KubeNodeMemoryPressure expr: kube_node_status_condition{conditionMemoryPressure,statustrue} 1 for: 10m labels: severity: warning annotations: summary: Node {{ $labels.node }} has memory pressure - alert: KubeNodeDiskPressure expr: kube_node_status_condition{conditionDiskPressure,statustrue} 1 for: 10m labels: severity: warning annotations: summary: Node {{ $labels.node }} has disk pressureNotReady 的for: 5m表示节点保持该状态 5 分钟才告警。如果节点只是瞬时网络抖动不会立刻打扰到值班人员。压力类告警使用for: 10m可以过滤掉短时资源尖峰。6.3 节点异常时的标准处置流程将下面的流程固化成团队 SOP比临时查文档更高效确认影响范围执行kubectl get nodes、kubectl get pods -A -o wide。确认节点状态细节执行kubectl describe node node。检查 kubelet登录节点执行systemctl status kubelet和journalctl -u kubelet -n 200。检查容器运行时执行systemctl status containerd和crictl ps。检查节点资源执行free -m、df -h、uptime、dmesg | tail。检查网络执行kubectl get pods -n kube-system -o wide确认 CNI 和 apiserver 是否健康。处理根因清理磁盘、调整资源限制、修复网络配置或重新部署 CNI 插件。恢复验证执行watch kubectl get nodes确认节点状态稳定在 Ready。复盘确认是否需要调整告警阈值、补充 PDB、扩容节点或修改 kubelet 驱逐配置。6.4 常见误区与规避方式第一不看日志直接重启节点。节点状态异常是结果不是根因。重启后日志会被覆盖问题可能在下次高峰期复发。正确做法是先收集 kubelet、containerd、dmesg 和事件日志再决定处理方式。第二只检查 kubelet不检查容器运行时。Kubernetes 1.24 之后containerd 等 CRI 运行时是 kubelet 的直接依赖。容器运行时无响应时kubelet 也会把节点标记为 NotReady。两者都要纳入巡检。第三磁盘压力只检查根分区。容器镜像和容器日志的存储目录通常是/var/lib/containerd或/var/lib/docker。这个目录如果单独挂载根分区还有空间也不会报警一旦镜像目录被写满容器运行时会失败。第四把驱逐阈值配置得过于激进。阈值太小节点已经 OOM 才驱逐会损伤磁盘和业务阈值太大正常业务波动也可能触发驱逐。建议结合监控数据设置并保留足够余量。第五节点维护前不执行 cordon 和 drain。直接重启节点或升级节点内核可能导致存量 Pod 被强制终止。维护前先执行kubectl cordon node01 kubectl drain node01 --ignore-daemonsets --delete-emptydir-data维护完成后执行kubectl uncordon node01第六没有配置 PDB 或多副本导致任何主动驱逐都变成业务中断。核心服务至少要配置两个以上副本并设置 PDB让节点维护允许的驱逐范围是可控的。节点健康不是一个监控面板上的颜色而是一整套从内核、容器运行时、kubelet 到 apiserver 的协作结果。真正有价值的不是记住某条命令而是建立标准排查链路先确认现象再检查 kubelet、运行时、资源和网络最后用监控和告警提前拦截风险。下一阶段可以继续从 NodeProblemDetector 入手把内核死锁、文件系统只读这类系统级问题自动化上报进一步缩短节点故障的恢复时间。