RDMA资源说没就没,这种事我一年里能碰上两三次。一开始总怀疑网卡、驱动、固件哪个环节出了问题,后来定位定位着才发现,根子往往不在硬件,而在Kubernetes的调度门禁上——节点被打上了污点,而设备插件、CSI存储组件这些平台服务没有对应的容忍度,调度器直接把它们全拦在了门外。节点上的RDMA资源看起来是"消失"了,CSI挂载也跟着失败,实际上它们踩的是同一个坑。
这里涉及的三个技术点都不算新:RDMA是高性能网络里绕开内核协议栈、靠网卡卸载实现低延迟通信的路线,DPDK、XDP是它旁边的兄弟方案;Kubernetes里的RDMA资源需要靠Device Plugin以Extended Resource的形式上报;而存储插件CSI要拿到节点上的RDMA设备,又通常部署成DaemonSet。这篇文章就从一次真实排障出发,把"RDMA资源消失"和"CSI挂载失败"之间那条隐藏的调度链路拆开讲清楚,包括排查命令、修复方式和生产环境里的避坑经验。适合正在维护GPU/RDMA节点池或者跟CSI存储问题死磕的工程师。
1. 现象还原:节点上的RDMA资源怎么就没了
1.1 第一现场:Allocatable里的RDMA条目消失
事情开始于一次例行的集群巡检。我习惯性地用kubectl describe node查看一批RDMA节点的资源,结果发现某个节点的资源表少了一行。正常情况下,节点上应该同时显示CPU、内存、GPU数量和RDMA设备数量,大致长这样:
Capacity: cpu: 96 memory: 786396Mi nvidia.com/gpu: 8 rdma/hca: 8 Allocatable: cpu: 96 memory: 786396Mi nvidia.com/gpu: 8 rdma/hca: 8但现场只有GPU,没有rdma/hca。第一反应是网卡掉线了。跑到服务器上敲ibstat、ibv_devinfo,硬件状态都是健康,再用rdma link show看网卡链路,也是ACTIVE状态。硬件和驱动都没问题,集群却认为节点没有RDMA能力,这个矛盾很关键。
接着去看设备插件的运行状态,一下就发现了缺口:RDMA设备插件的DaemonSet在该节点上的Pod是Pending,始终没有调度上去。Kubernetes里的扩展资源和内置资源不一样,节点上有没有rdma/hca,完全取决于设备插件有没有在节点上正常运行并向kubelet上报。插件没跑起来,节点资源表里对应条目自然消失。到这里,问题的性质已经从硬件故障变成了调度问题。
1.2 连带故障:CSI卷挂载开始卡死
如果故障只停留在RDMA资源消失,影响范围还算可控,最多是那些声明了resources.limits["rdma/hca"]的业务Pod无法调度。但这次真正引爆问题的,是一批与RDMA无关的Pod开始报存储挂载失败。
节点上大量Pod的事件里反复出现FailedMount、MountVolume.MountDevice failed、volume attachment is being deleted这类字样,kubelet日志里也能看到调用CSI的NodePublishVolume超时记录。排查后发现,存储后端的CSI插件同样部署成了DaemonSet,它需要在每个节点上都运行一个Node组件,才能在节点本机执行挂载动作。而在这个故障节点上,CSI Node组件也是Pending状态。
两条故障线同时出现,表面看一个属于RDMA资源,一个属于存储插件,八竿子打不着。但只要看一眼调度器的事件,就会明白两者踩中同一个机制:节点上有污点没有被匹配掉,而这两个平台组件的Pod模板里都没有对应容忍度,调度器不允许它们落在节点上。资源消失、挂载失败,都是这个根因的两个出口。
1.3 我第一次踩这个坑时走的弯路
第一次遇到同类问题时,我排查得相当狼狈。先怀疑驱动,重装了rdma-core,又把网卡固件刷了一版,故障纹丝不动。接着去看设备插件日志,发现根本没有日志可看——Pod压根没被创建,自然没有进程在跑。正当我准备去存储厂商那边对接CSI问题的时候,运维同事发来一条变更记录,说前一天晚上为了给这批节点做维护,执行了一条命令:
kubectl taint nodes <node-name> dedicated=rdma:NoSchedule这条记录当时就把整条逻辑串通了。运维的本意只是"临时维护,让新Pod别来",但没意识到设备插件和CSI组件这些平台服务同样会被挡在外面。这个坑我踩过一次之后,后来再看到任何DaemonSet长期Pending,都会先看节点污点,再看容忍度,这个习惯帮我省了很多时间。
2. 根因拆解:污点、容忍、扩展资源的三角关系
2.1 30秒复习污点与容忍的调度语义
Taint是打在节点上的标记,格式是key=value:effect,effect有三种:
NoSchedule:新的Pod不能调度上来,已经运行的Pod不受影响。NoExecute:不仅新Pod进不来,已经在节点上的不匹配Pod也会被驱逐。Pod可以通过tolerationSeconds延期,但到期后仍会被清理。PreferNoSchedule:软约束,调度器尽量不往这个节点放Pod,但不绝对禁止。
Toleration是加在Pod上的豁免声明。调度器看到节点有污点,会回头检查Pod是否声明了能匹配的容忍:要么精确匹配污点的key、value、effect,要么用operator: Exists放宽匹配条件。生活化一点,污点就是园区门口挂着"施工区域谢绝入内"的牌子,容忍就是施工人员手里的门禁卡。没有门禁卡的人,保安绝不会放你进去。
关键在这里:容忍度不是Pod的默认属性。绝大多数Pod模板里如果不写tolerations,那就是完全没有豁免权,节点上任何污点都会把它挡在外面。这个规则对DaemonSet同样生效。设备插件、CSI Node组件、日志采集、监控采集这类面向全节点部署的平台组件,最容易在这个机制上翻车。
2.2 RDMA扩展资源的生命周期绑定设备插件
CPU和内存是Kubernetes的内置资源,节点天然具备,调度器不用问就知道。RDMA网卡这类硬件则完全不同,调度器默认不认识。要让调度器理解"这台机器上有8张RDMA网卡",必须通过Device Plugin机制:节点上的kubelet向设备插件实例发起ListAndWatch调用,插件把设备列表上报,kubelet再把结果写进节点的Capacity和Allocatable。集群里常见的扩展资源名包括nvidia.com/gpu、rdma/hca等。
这意味着一个很容易被忽略的事实:节点有没有RDMA资源,不是静态硬件状态,而是"设备插件有没有在该节点正常运行"的动态结果。插件没跑起来,节点在调度器眼里就等同于没有这个设备。应用声明要2个rdma/hca,节点资源表里没这个条目,那这个节点根本进不了候选列表。这就是RDMA资源"消失"的底层机制。
设备插件本身通常以DaemonSet方式部署,这就回到了2.1的规则里。节点一打上污点,设备插件Pod先阵亡,节点资源表里RDMA条目随即消失,上层所有依赖RDMA资源的业务开始连锁反应。如果不理解这层绑定关系,很容易在硬件排查上浪费好几个小时。
2.3 CSI节点组件为何也躲不开污点
CSI的架构很清晰:Controller组件负责卷的创建、绑定、挂接等控制面操作,Node组件负责在节点本机执行挂载。为了让集群里所有节点都能挂卷,Node组件基本都以DaemonSet方式发布,它的存在假设就是"每个节点上都有我"。
但这个假设在污点面前会直接破碎。节点被标记污点后,如果CSI Node组件没有对应容忍度,调度器就不会把它放到这个节点上。后续只要有Pod需要在这个节点挂载存储卷,kubelet就找不到本机的CSI插件去执行NodePublishVolume,报错信息就是挂载超时。注意,这里并不要求业务Pod本身使用RDMA——只要节点上的存储数据面依赖RDMA通道,或者单纯因为这个节点还需要CSI插件来挂卷,故障都会发生。
我碰到的实际情况里,很多业务Pod的PVC一直报FailedMount,但RDMA设备、驱动、网络地址全部正常,最后查下来就是CSI Node组件的DaemonSet没有调度上来。如果连CSI Controller也带有资源亲和性,同样会卡在Pending状态。这类问题如果不懂调度机制,很容易在存储协议和网络层反复排查,完全走偏。
2.4 一条完整的故障链
把所有环节串起来,这次故障的完整因果链是这样的:
- 运维为了节点维护,给节点打上
dedicated=rdma:NoSchedule污点。 - RDMA设备插件和CSI Node组件都没有对应容忍度,全部无法调度,Pod卡在Pending。
- 设备插件不运行,节点Allocatable里的
rdma/hca消失。 - 依赖RDMA资源的新Pod无法再调度到该节点,或者尝试申请资源时报不足。
- 节点上已存在的Pod如果需要挂载卷,因为CSI Node组件不可用而挂载失败,报FailedMount。
- 如果污点用的是
NoExecute,节点上已有的不匹配Pod还会被驱逐,故障范围进一步扩大。
这条链里每一环都有可能单独断开。设备插件有容忍而CSI没有,RDMA资源还在,但存储挂载照样断;反过来,只有CSI有容忍而设备插件没有,资源照样消失。所以排查这种故障,绝不能只盯着某一个组件,必须把节点上的所有平台组件都过一遍。
3. 定位链路:一份可以直接照做的排查清单
3.1 确认"资源消失":是硬件故障还是插件没上报
拿到"RDMA资源消失"的反馈,先做两个操作,把节点资源状态完整拉出来:
kubectl describe node <node-name> | head -n 40 kubectl get node <node-name> -o json | jq '.status.capacity, .status.allocatable'重点看rdma/hca或类似的扩展资源是否还在。如果不在了,立刻转去看设备插件:
kubectl get pods -n kube-system -o wide | grep rdma kubectl describe pod -n kube-system <rdma-device-plugin-pod> | tail -n 30如果设备插件Pod是Pending,Events里通常会直接出现had untolerated taint字样。这个信号是整个排查链路里最重要的分水岭:一旦看到taint相关描述,就不用再去底层翻驱动了,直接进入调度问题排查阶段。顺手也要确认硬件状态,避免被其他因素带偏,RDMA网卡的实操工具两个够用:ibstat和rdma link show。
3.2 从挂载失败反查CSI组件调度
另一个方向是从挂载失败反查。找到挂载失败的业务Pod,看事件:
kubectl describe pod <business-pod> -n <namespace> | grep -A 20 Events然后看目标节点上CSI Node组件的状态:
kubectl get pods -n <storage-namespace> -o wide | grep csi-node如果CSI Node组件没有调度到目标节点,Pod列表里该节点对应的行会一直显示Pending,或者干脆不存在。再进一步确认kubelet日志里的挂载动作超时:
journalctl -u kubelet -f | grep MountVolume journalctl -u kubelet -f | grep NodePublishVolume我建议不要跳过这个反查动作。它能帮你快速判断故障是单一调度问题,还是多组件故障并发。生产环境里RDMA设备插件和CSI组件同时挂是常见情况,只看一边容易得出"另一个组件也刚好坏了"的错误结论。两边同时确认,才能直接锁定"缺容忍"这个根因。
3.3 核对节点污点与DaemonSet容忍
查完了组件状态,接下来就是核对机制本身。
# 直接看节点污点 kubectl get node <node-name> -o json | jq '.spec.taints' kubectl describe node <node-name> | grep -A 10 Taints # 检查DaemonSet模板里的容忍配置 kubectl get daemonset rdma-device-plugin -n kube-system -o yaml | grep -A 20 tolerations kubectl get daemonset csi-node -n <storage-namespace> -o yaml | grep -A 20 tolerations节点上有污点而组件的tolerations字段为空,基本就可以定案。要注意,很多平台组件在Helm Chart或Operator部署时会有默认容忍,但只会覆盖常用污点,比如node-role.kubernetes.io/control-plane、node.kubernetes.io/not-ready。自定义污点比如dedicated=rdma,通常不在默认列表里,这正是盲区所在。
3.4 最小修复三步走
如果污点只是临时维护用的,最干净的做法是把污点移除:
kubectl taint nodes <node-name> dedicated=rdma:NoSchedule-注意:命令末尾的减号不是笔误,它表示删除该污点,而不是添加。移除后调度器会自动把Pending的DaemonSet Pod调度上来,节点资源也会逐步回归。
如果污点是长期规划的一部分,比如这批节点就是专供RDMA业务使用的专用节点池,那就需要给所有必须跑在上面的平台组件补上容忍度。以DaemonSet为例,在模板中增加:
tolerations: - key: "dedicated" operator: "Equal" value: "rdma" effect: "NoSchedule"改完后执行滚动更新并确认状态:
kubectl rollout restart daemonset rdma-device-plugin -n kube-system kubectl rollout restart daemonset csi-node -n <storage-namespace> kubectl rollout status daemonset rdma-device-plugin -n kube-system kubectl rollout status daemonset csi-node -n <storage-namespace>验证顺序建议是:先看节点Capacity里的rdma/hca是否回归,再看CSI Node Pod是否Running,最后验证业务PVC是否挂载成功。不要跳过前两步直接刷业务Pod状态,否则一旦恢复失败,排查又得从头再来。
4. 生产实践:我踩过的坑和沉淀下来的避坑清单
4.1 一张速查表对应常见现象与根因
| 现象 | 直接原因 | 排查看点 |
|---|---|---|
| 节点Allocatable里RDMA资源消失 | 设备插件Pod未调度或未上报 | kubectl describe node+ RDMA设备插件Pod状态 |
| CSI卷一直FailedMount | CSI Node组件未运行在目标节点 | 业务Pod的Events + csi-node Pod列表 |
| DaemonSet Pod长期Pending | 节点污点与Pod容忍不匹配 | 事件里的had untolerated taint |
| 节点Ready但业务Pod无法调度 | 节点有自定义污点或资源不足 | .spec.taints+.status.allocatable |
| 故障集中在某一批特定节点 | 专用节点池污点被平台组件漏配 | 对比同池节点污点与DaemonSet容忍 |
| 业务能跑但RDMA通信异常 | 设备没有真正映射给容器 | 设备插件分配日志、容器设备挂载 |
这张表的用法是帮助你快速把"看起来不相关的故障"映射到同一类机制上。先做方向判断,再进具体命令验证,比直接从底层日志往上翻效率高很多。
4.2 平台组件容忍配置的三条经验
第一,全节点型平台组件建议默认带上对通用运维污点的容忍,但不要全容忍。operator: Exists不加key的写法会把节点上所有污点都认了,如果某个节点被打了一个含义完全不同的污点,组件也可能跑上去,反而造成资源抢占甚至安全隐患。尽量按key和effect精确匹配。
第二,不要把调度设计全押在污点上。生产环境里我习惯用标签、污点、亲和性三件套组合:标签表达节点身份,比如rdma=true;污点表达"默认不要来";亲和性表达"哪些组件需要来"。平台组件可以在tolerations里精确放行dedicated=rdma,同时用nodeSelector或nodeAffinity进一步收敛目标节点,既灵活又安全。
第三,控制面节点的污点不要随意容忍。node-role.kubernetes.io/control-plane这类污点如果被宽松的operator: Exists覆盖,资源型组件可能被调度到控制面节点,挤占管理面资源。更稳妥的做法是给不同组件单独配置,而不是图省事一刀切。
4.3 打污点之前先做一次组件覆盖检查
我现在做任何节点污点变更前,都会先跑一遍全平台组件检查,确认哪些DaemonSet没有对应容忍。
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do kubectl get daemonset -n $ns -o json 2>/dev/null | \ jq -r '.items[] | [.metadata.namespace, .metadata.name, (.spec.template.spec.tolerations // [])] | @tsv' done这个脚本很粗糙,但能在一分钟内把"哪个DaemonSet没有容忍"列出来,再手动核对是否需要补。核心是养成习惯:任何平台变更,先评估调度影响面。我有一次就是因为跳过这一步,给RDMA节点打污点后设备插件和CSI组件同时Pending,该节点上几十个业务Pod的存储挂载全部报错,恢复花了近两个小时。如果当时提前三分钟跑一次覆盖检查,这个事故完全可以避免。
4.4 沉淀到平台组件发版清单里的调度体检项
经过几次折腾,我把平台组件的调度健康检查沉淀成了固定清单,每次发版前都会过一遍:
- 该组件是否需要在所有节点上运行?如果是,默认的容忍覆盖是否齐全?
- 节点的污点集合有哪些?组件模板里是否显式声明了对应容忍?
- 组件是否与某个扩展资源强绑定?设备插件挂了,上层组件能否独立存活?
- 组件之间是否存在调度依赖?比如CSI Node组件是否依赖设备插件先运行?
- 监控告警是否覆盖了"DaemonSet期望副本数与实际Running数不匹配"这个指标?
这套检查项后来帮我避掉了至少三起同类事故。Kubernetes的调度机制本身并不算难,难的是把它纳入日常变更管理的视野。RDMA、CSI这些技术名词看起来各不相同,但在污点与容忍这道调度关卡下,它们共用同一套规则。
最后说一句实在话:RDMA资源消失这类故障,放到Kubernetes的机制里看,往往不是资源真的丢了,而是承载资源的平台组件进不去。下次再遇到CSI挂载失败,先别看存储配置,先看节点污点,再数一下设备插件和CSI组件的Pod数量,大概率能省下半天时间。