1. 第一现场RDMA 资源消失与 CSI 挂载失败的双重故障1.1 故障 ARDMA 资源为什么悄无声息地“蒸发”了先说 RDMA 资源消失这件事。某个高性能计算集群里我们有一批通过 RDMA 网卡做高速互通的节点工作负载会被调度到这些节点上申请形如rdma/hca的扩展资源。这类资源不像 CPU 和内存那样天然存在而是靠节点上的设备插件定期向 kubelet 上报数量再由 kubelet 写进节点状态里的。某天早上例行巡检时我习惯性看了一眼资源池发现其中一台 RDMA 节点上rdma/hca的调度容量变成了 0而节点本身网络、内存、磁盘一切正常。更诡异的是这台节点上的所有 Pod 都被清理掉了像是被人为做过一次大扫除。第一反应是查设备插件是否还在运行。RDMA 设备插件是以 DaemonSet 形式部署在kube-system命名空间的理论上每个节点都有一个 Pod。我kubectl get pod -n kube-system -o wide | grep rdma一看故障节点上对应的设备插件 Pod 确实处于 Pending 状态调度器给出的原因很清楚节点上有污点而 Pod 没有容忍。这台节点不知道什么时候被打了maintenance:NoSchedule的污点业务 Pod 进不来倒正常但设备插件这种“平台组件”也被一并挡在了门外接下来就引发了一连串连锁反应。设备插件下线kubelet 长时间得不到资源汇报节点状态里登记的扩展资源数量就会慢慢失效最终在调度用户面前表现为“RDMA 资源消失”。这个消失不是物理层面的网卡损坏也不是资源被抢占纯粹是“没人上报系统就不认账”。如果不理解这层机制很容易白费力气去查硬件链路查驱动甚至去换网卡。1.2 故障 BCSI 挂载失败同一时间引爆的另一个坑第二个故障是 CSI 挂载失败。同一个维护窗口内有人尝试在这个节点上创建一个需要本地 RDMA 设备直通的 PVC再挂载到工作负载里。结果 Pod 创建以后一直卡在 ContainerCreating事件里反复出现 volume mount failed 或者类似“failed to mount volume”的报错。控制器的日志指向 CSI 存储插件但不管怎么检查存储后端、卷配置都找不到逻辑问题。如果你对 CSI 的架构有印象就会知道挂载动作不是控制平面直接做的而是由节点上的 CSI Node Driver 负责执行的。容器编排平台在调度 Pod 之前会先要求节点上的 CSI 插件把远端卷“Stage”到本地的中间目录再做 Bind Mount 到容器路径。这个 CSI Node Driver 同样是以 DaemonSet 部署的同样没有容忍节点上的污点名字出现在了故障节点的待调度队列里。设备没准备、驱动没响应、挂载路径不存在Pod 自然卡死。RDMA 资源消失和 CSI 挂载失败看起来分属网络和存储两个不同领域排查起来很容易各查各的。但实际上它们共享同一个根因平台组件没有针对节点污点配置容忍策略。这个标题叫“平台组件的污点与容忍”说的就是这一类问题。1.3 初步排查先梳理状态而不是急着动刀双重故障发生后常规排查路径是先确认硬件、再看驱动、接着怀疑存储后端但这三条路我们都走了一遍全部没有收获。网卡链路正常驱动加载正常存储服务端也确认没有任何告警。回过头来一查节点信息才发现在这些技术问题之外还有一个看似无关的状态变化节点上的 Taint 列表里躺着一条自定义污点。而且这个污点加上去的时间正好和故障开始的时间吻合。这里有个容易忽略的点污点不一定会让节点上的现有组件立刻消失要看 Effect 的等级。如果当初打的 Effect 是NoExecutekubelet 会直接驱逐节点上的存量 Pod设备插件当时就被杀掉了如果是NoSchedule则只影响后续调度但当时如果恰好设备插件因为重启、升级、驱逐等原因重建过同样会创建失败。无论哪种情况最后的结果都是平台组件缺失资源上报和存储挂载链路双双断掉。2. 根因解析污点、容忍与平台组件的调度困境2.1 污点与容忍机制的本质Kubernetes 的调度器在决定 Pod 放到哪个节点时会检查节点上是否有 Taint以及 Pod 是否有匹配的 Toleration。两者刚好配对调度器才允许 Pod 进入否则业务 Pod 会被排斥在外。Taint 有三种 EffectNoSchedule表示不调度新 PodPreferNoSchedule表示尽量不调度但资源紧张时也可能放进去NoExecute最严厉不仅不调度新 Pod还会驱逐节点上已有的、没有匹配容忍的 Pod。用生活里的类比来说污点就是业主在门口挂的“本栋不让进”的牌子容忍就是租户手里持有的门禁授权。没有授权的人压根进不来有授权但类型不对也进不来想进来必须挂牌子和留钥匙的人完全匹配。平台组件和设备插件就是“常住租户”她们必须常驻在每一个需要工作的节点上却常常没跟物业提前签好“容忍协议”。系统级组件一般都会自带常用的系统容忍比如node-role.kubernetes.io/master等。但业务里自定义的污点比如运维团队为了维护节点时打的maintenance、dedicated、gpu-only系统不会自动处理必须显式地在平台组件上配置 Toleration。这是最容易出疏漏的地方因为打污点的人可能是运维配置容忍的可能是平台研发前后两拨人如果没有对齐埋雷就埋下了。2.2 平台组件最容易被“无容忍”坑到的原因设备插件、CSI Node Driver、日志采集、监控 Agent 这类组件全部是 DaemonSet 形态。DaemonSet 的设计初衷是“每个符合条件的节点上都跑一个 Pod”但是注意它仍然要遵循调度规则。节点上的污点照样会拦住 DaemonSet 的 Pod除非 DaemonSet 的 Pod 模板里明确写了tolerations。为什么偏偏是平台组件容易踩坑先说时机。集群平时一切正常没人会去特意给正在使用的节点打污点。但一旦进入维护场景比如节点要重启、换硬件、调整内核参数运维会先cordon标记不可调度再打污点防止新业务进来。问题就在这个操作可能持续一小时、一天甚至更久。如果维护人员忘了在恢复后清除污点或者想用污点承接“容器化重启节点”这种高级操作平台组件的网络和存储基础服务就会持续处于瘫痪状态。再说设计盲区。很多人下意识认为 DaemonSet 就是“必然在节点上运行”的忽略了调度器这层关卡。写 YAML 时只配置了hostNetwork: true、privileged: true、volumeMounts这类权限项没有认真思考生产环境下节点可能被自定义污点标记。等到异常发生组件静默消失服务又没有告警问题会被拖到用户真正开始跑业务时才暴露出来。2.3 资源消失与挂载失败为什么会连锁发生把时间线拉直你会发现两个故障是同一根链条上的两个结。第一步节点被打了污点。第二步设备插件和 CSI Node Driver 都无法在节点上运行。第三步RDMA 设备插件下线导致 kubelet 停止上报扩展资源节点容量里rdma/hca变成 0依赖 RDMA 的新任务无法被调度进来。第四步CSI Node Driver 下线导致挂载操作没人执行PVC 虽然可以在控制面创建成功但节点侧无法完成设备准备和挂载Pod 起不来。这种连锁反应有一个鲜明特征表面现象分别指向网络和存储单看哪一边都不像调度问题。RDMA 资源消失更像硬件故障CSI 挂载失败更像存储配置错误。如果排障的人没有“平台组件调度”这根弦很容易在错误方向上消耗大量时间。我们在踩过这个坑之后把“检查节点污点、检查 DaemonSet 容忍”列为了这类故障的前置排查项以后每次都能大幅缩短定位时间。3. 实操复盘从现象到根因的完整排障链路3.1 刺探节点状态资源、污点、Pod 分布三查排障第一步永远是看清节点现状我通常一次执行三条命令分别看资源、污点、Pod 分布。查资源用kubectl describe node看 Allocatable 里的扩展资源查污点用下面这条kubectl get node rdma-node-01 -o jsonpath{.spec.taints}{\n} # 期望输出是 [] 或者空如果出现 [{key:maintenance value: effect:NoSchedule}] 就要注意了查 Pod 分布要看关键平台组件在故障节点上的实际状态。设备插件所在命名空间的 Pod应该有一一对应的 Running 状态如果某个节点对应位置空白或者 Pending基本可以断定这个组件没有成功上节点。CSI Node Driver 同理在kube-system或者存储专用命名空间里过滤csi关键字。三条信息合起来看往往问题就已经水落石出。如果资源数为 0、节点有自定义污点、关键 DaemonSet Pod 不在该节点上那主因大概率就是污点与容忍不匹配。这时候不需要再继续深挖网络和存储链路可以直接进入修复环节。3.2 追踪 RDMA 设备插件的“失踪记录”RDMA 资源消失的完整链路需要确认设备插件到底为何没有上报。先找到设备插件的 DaemonSet 定义看模板里是否包含容错配置kubectl get daemonset -n kube-system rdma-device-plugin -o yaml | grep -A 10 tolerations如果输出为空说明设备插件完全没有容忍任何自定义污点。然后看这个 DaemonSet 的 Pod 状态确认它是 Pending、Evicted 还是 CrashLoopBackOff。在我遇到的情况里Pod 处于 Pending调度事件里的提示是0/2 nodes are available: 2 node(s) had untolerated taint。这就是根因了。设备插件想回节点干活但是调度器不让进。它跟工作负载本身没有区别都是普通 Pod不会因为是“系统组件”就有免检特权。如果设备插件本来就在节点上运行而打污点的 Effect 是NoExecutePod 会立刻被驱逐如果是NoSchedulePod 也会因为任何形式的自动重建比如镜像更新、节点重启伴随的重新创建而卡住。理解了这一点就知道为什么资源消失是“静默”的因为没有一个环节会主动报错只是资源数据不再更新而已。3.3 追踪 CSI Node Driver 与挂载路径对于 CSI 挂载失败排查路径要去 CSI 的控制平面服务和节点服务两侧看。先在控制平面侧查看 PVC 事件和卷操作kubectl describe pvc rdma-pvc-01 kubectl describe pod rdma-workload-pod事件里如果出现FailedAttachVolume、FailedMount之类再去看 CSI 控制器提供的日志确认ControllerPublishVolume是否已经完成。完成之后节点侧应该由 CSI Node Driver 接手做NodeStageVolume但此时如果没有 Node Driver 在监听、响应控制面发出的节点操作请求长时间无人处理最终在 Pod 事件里表现为挂载超时或失败。接下来看 CSI Node Driver 的 Podkubectl get pod -n kube-system -l appcsi-node-driver -o wide | grep rdma-node-01结果跟设备插件一样对应节点位置的 Pod 不存在或 Pending。到这里基本确认根因一致问题不在存储后端而在存储插件压根没有上节点。虽然这俩故障一个在 RDMA一个在 CSI但它们共享同一个根因修复方案也共享同一个核心动作给平台组件补上容忍。3.4 修复方案给平台组件正确配置容忍修复的核心是给设备插件和 CSI Node Driver 的 DaemonSet 模板中加上对应的 Toleration。这里要注意两个细节一是要适配实际存在的污点 key 和 effect二是要考虑是否需要加宽泛容忍。以维护节点常用的污点为例tolerations: - key: maintenance operator: Exists effect: NoSchedule - key: node.kubernetes.io/unreachable operator: Exists effect: NoExecute tolerationSeconds: 300第一条容忍解决了NoSchedule拦截第二条容忍用于应对节点暂时不可达时的驱逐行为。平台组件作为集群基础设施最好容忍常见的基础故障类污点比如not-ready、unreachable、disk-pressure、memory-pressure否则节点出现资源压力或临时故障时平台组件同样会被驱逐导致故障面扩大。修改方式很简单kubectl edit daemonset -n kube-system rdma-device-plugin # 在 spec.template.spec 下添加 tolerations 字段 kubectl edit daemonset -n kube-system csi-node-driver编辑保存后DaemonSet 会滚动重建在之前被污点拦截的节点上补出新 Pod。建议先改一个组件观察效果再改另一个避免因为 YAML 写错导致平台组件的其他节点也受影响。3.5 验证与回归测试修复完成后做验证时我一般按三步走。第一步看资源是否恢复kubectl describe node rdma-node-01 | grep -A 5 Allocatable扩展资源rdma/hca应该重新出现并且数量与节点实际网卡数量一致。第二步看 CSI 挂载能力是否恢复重新创建一个测试 Pod挂载那块之前失败的 PVC观察 Pod 能否从 ContainerCreating 进入 Running。第三步看两个 DaemonSet 是否在每个目标节点上都处于 Running 状态kubectl get pod -n kube-system -l appcsi-node-driver -o wide kubectl get pod -n kube-system -l apprdma-device-plugin -o wide如果三查都通过说明射频链路和存储链路都已经恢复正常。这里提醒一句验证过程不要只跑一遍建议隔 5 分钟再查一次资源数确认 kubelet 的设备插件上报是持续稳定的而不是临时抖动一下。4. 常见问题与排查技巧实录4.1 常见问题速查表问题现象可能原因快速排查命令解决办法节点扩展资源为 0设备插件未运行kubectl get pod -n kube-system -l apprdma-device-plugin -o wide给设备插件加容忍或清除节点污点Pod 调度失败提示 node(s) had untolerated taint节点污点与 Pod 容忍不匹配kubectl get node node -o jsonpath{.spec.taints}给 Pod 加对应容忍或删除多余污点PVC 挂载失败事件里没有节点侧响应CSI Node Driver 未运行kubectl get pod -n kube-system -l appcsi-node-driver -o wide给 CSI Node Driver 加容忍滚动重建DaemonSet Pod 处于 PendingPod 模板缺少 tolerantkubectl get daemonset -o yamlgrep -A 10 tolerations节点维护结束后业务仍不可用维护用污点未清理kubectl describe node node | grep Taintskubectl taint node node key-删除污点这张表列的是最常见的几种表现实际生产里现象可能进一步组合。比如设备插件没有容忍、CSI Driver 有容忍那只会出现 RDMA 资源消失挂载正常再比如 CSI Driver 没有容忍但 PVC 用的是静态卷、不需要节点侧准备设备那也不会挂载失败。要结合具体架构逐层判断不能拿现成的答案生搬硬套。4.2 排障经验怎么快速判断是不是污点问题我在这个案例里踩过弯路一开始只盯着 RDMA 网卡驱动和 CSI 存储后端查两个小时过去了毫无进展。后来总结出一个规律当故障节点上多个不同领域的平台组件同时失效时优先怀疑节点级因素。比如这台机器不仅 RDMA 扩展资源没了CSI 节点插件也不在了那就已经不是在闹“单一组件”的问题而是基站出了问题。具体的快速判断动作我推荐就做一件事kubectl get node查 Taints。节点级问题里污点是最常见的人为因素。它可能来自运维的维护动作、自动化巡检脚本的标记、云厂商托管节点的自动维护流程甚至可能是某个自动化工具误打了“维护中”的标签。一旦确定节点有问题立刻把kubectl get node node -o jsonpath{.spec.taints}{\n}的结果和 DaemonSet 的tolerations配置做对比基本一眼就能锁定。4.3 排障经验避免“修好一个坏一个”的连锁反应只给设备插件加容忍不给 CSI 加可能修完 RDMA 发现 CSI 又开始报错。这种“修好一个坏一个”的情况在大型集群里很常见因为基础组件之间的服务是互相依赖的设备在哪里存储就要跟到哪里网络要在挂载点才有效。所以我建议在修改前先把集群里所有关键 DaemonSet 的容忍配置拉一个清单统一梳理一次性补齐而不是只补触发报警的那个。清单内容很简单组件名、所在命名空间、宿主要求(比如是否需要固定节点)、当前 tolerations 列表、期望 tolerations 列表。拿这份清单去和应用维护方以及存储方案方对齐把标准确认好再批量修改。特别是 CSI 这类改动一条 YAML 就影响全部节点的组件一定要先在一个临时节点做小范围灰度确认没问题后再做全量滚动避免一次改错直接废掉一个可用域。还有一个细节容易忽略有些组件通过 Helm Chart 部署直接kubectl edit daemonset修改下回 Helm 升级时会被旧模板覆盖回去。正确做法是改 Helm Chart 的 values 里对tolerations的定义或者找到渲染模板的源文件从源头修复。否则今天修的配置明天一升级又丢了到时候再来一次线上变更白白增加风险。5. 长远建议平台组件的高可用与调度规范5.1 统一治理为所有基础组件定义标准容忍策略经历过这次故障之后我们把平台组件的调度策略做了一次统一治理。原则很简单基础组件应当承载集群的“地基”角色不能被普通业务调度限制随便排挤掉。因此对设备插件、CSI 驱动、日志采集、监控 Agent 这类 DaemonSet统一在模板中配置一套基础容忍覆盖主流的维护和故障类污点。这套标准不是越多越好。容忍过宽比如直接operator: Exists 不限定 key 和 effect会导致组件被调度到所有节点包括那些本不该有该组件的专用节点造成资源浪费甚至冲突。建议按需收敛NoSchedule类污点尽量容忍NoExecute类污点配合tolerationSeconds设置合理的暂存窗口。设备插件和 CSI 驱动这类节点级基础服务原则上不应当被轻易驱逐所以tolerationSeconds可以设置得短一些甚至不设置。同时要在配置评审阶段强制检查“新接入的 DaemonSet 必须带基础容忍”这一条。代码评审时如果发现模板里没有 tolerations至少给一个 review 阻断把问题拦截在上线之前而不是等生产故障来提醒你。5.2 监控与告警别让资源静默消失RDMA 资源消失这类问题最让人头疼的一点就是“静默”。kubelet 不会主动告警说“设备插件下线了”调度器也不会在节点没有资源时替你通知“这个节点废了”。因此必须有主动监控。比较实用的监控指标是按节点统计扩展资源的allocatable数值变化一旦出现从“正常值”直接降到 0就触发告警。这里的正常值指节点实际硬件数量比如 8 张网卡就应该是 8。另有一个指标是 DaemonSet 的desiredNumberScheduled和currentNumberScheduled对比理想情况下两者相等如果有任何 gap 就代表有节点没有跑上对应的平台组件这个 gap 必须变成告警而不是日志。存储侧同理CSI Node Driver 也是 DaemonSet同样用currentNumberScheduled对比全部目标节点数差异即告警。把这些监控项做成模板化的告警规则能让后续所有平台组件的状态缺口都暴露出来而不是等用户报障。5.3 变更管理从根源避免“污点遗忘”运维在维护节点时打污点是必要操作但打完污点一定要有“归还”机制。我在实际工作中推荐两条第一所有节点污点操作必须走变更平台记录操作人、变更单号、预期恢复时间第二维护动作结束后的恢复步骤里必须包含“清除污点”这一项把它写进标准操作流程。好的流程应该是这样的节点维护前在变更单里明确目标节点、开始时间和结束时间维护开始时执行cordon加taint维护结束后先移除污点再执行uncordon让调度器恢复。这里有一个原则性问题taint和cordon是两件事cordon只让新 Pod 不调度上去taint会连带影响平台组件。维护任务场景下很多团队习惯直接打污点觉得这样就“不会有人来打扰了”其实用cordon就足够了。这样既保护维护中的节点也不会把平台组件一起带走。如果确需打污点尤其是NoExecute类型的驱逐级污点务必先想清楚它会驱逐哪些东西。可以先用下面的命令查看节点上的 Pod 数量评估影响面再做操作kubectl get pod --all-namespaces --field-selector spec.nodeNamerdma-node-015.4 后续扩展污点自动治理与调度策略进阶这次讲的是“平台组件被污点挡住”的修复再往后可以做更自动化的治理。一种常见做法是引入准入控制器在创建和更新 DaemonSet 资源时自动注入基础容忍配置这样任何团队新上的平台组件只要不显式覆盖就会自动获得一组合理默认值。这样既不用依赖人工 review也能保证基本盘不丢。另一个方向是把调度策略精细化。比如用 NodeAffinity 加上requiredDuringSchedulingIgnoredDuringExecution限定平台组件只在特定角色节点上运行再配合容忍让它们能突破顽固污点区。这套组合优势在于既维持了“节点不可用于普通业务”的隔离语义又确保平台组件仍然有权限进去干活。比如 GPU 专属节点上既要跑 GPU 设备插件又需要 GPU 存储卷的 CSI 驱动这类节点通常带着dedicatedgpu的污点工作负载靠容忍进去平台组件也要靠容忍进去。这些操作一旦成体系就不会再出现“资源消失但没人知道”的情况。平台组件的存在应该是“隐形但可靠”用户看不见它们但它们的缺失一定会被系统提示出来。6. 一些个人体会这次排障下来我最深的感受是Kubernetes 里很多“高级故障”根子往往在不被注意的基础语义上。RDMA 资源消失和 CSI 挂载失败表面上分属网络和存储两个专业方向最后却被同一条kubectl taint命令解释清楚了。工具本身并不复杂复杂的是在使用工具时有没有对调度机制保持敬畏。如果只能带走一条经验我想说任何新增的节点污点都要在变更系统里留下记录同时检查全集群所有 DaemonSet 的容忍配置任何平台组件在修改调度策略时都要先问一句“这个组件需要跑在哪些可能带污点的节点上”。设备插件和 CSI 驱动这类组件不是可有可无的辅助角色它们是集群正常运作的底座。底座丢掉的时候上层业务发出的任何请求都不会有结果而且大多数时候还没有人主动告诉你是底座不见了。最后分享一个小技巧给 DaemonSet 配完容忍后可以用下面这条命令快速对比所有节点的覆盖情况kubectl get daemonset -A | grep -v NAMESPACE | awk {print $1, $2, $3, $4}如果某个 DaemonSet 的 DESIRED 数少于节点总数立刻顺藤摸瓜查一下是哪些节点没覆盖到然后对照它们的 Taints 确认是否被污点拦截。这套操作我已经固化成日常巡检的一部分几次提前于用户发现过“维护污染的漏网之鱼”赶在故障扩大前就处理掉了。