Netpath
学习路径43 / 49 · K8s 网络看全程 →
实验预计 40 分钟

动手:K8s 里把 RDMA 交给 Pod

network-operator、rdma/hca 资源、hostNetwork 与 MacVLAN 两种模式的实际配法。

学完这节你能做到

  • 部署 network-operator 并用 NicClusterPolicy 暴露 rdma/hca 资源
  • 按 IB 或 RoCE 场景写对 Pod 的资源与网络配置
  • 在 Pod 内验证 RDMA 设备可用并跑通带宽测试

建议先学

跳过这几节会看不懂本节的部分推导

要解决的三个问题

裸机上 RDMA 通了,搬进 K8s 要额外解决三件事:

  1. 容器里看得到 RDMA 设备(/dev/infiniband/*)
  2. 调度器知道哪些节点有卡、并且能按数量分配
  3. 网络配置正确——IB 和 RoCE 在这一步要求不同

NVIDIA Network Operator 把前两件包了,第三件要你自己选。

依赖:先把驱动装好

NFD(Node Feature Discovery)    ← 打标签,告诉调度器哪些节点有 Mellanox 卡
DOCA OFED                        ← 驱动(MLNX OFED 已迁移到 DOCA OFED)
Network Operator                 ← 部署设备插件与网络组件

驱动有两种装法,各有代价:

方式优点代价
宿主机预装稳定、启动快、排障直接要自己维护版本一致性
Operator 容器化下发(ofedDriver)版本统一、可滚动升级升级要 drain 节点,首次编译慢
# 确认宿主机驱动可用
ofed_info -s
ibstat -l
lsmod | grep -E 'mlx5_core|mlx5_ib|ib_uverbs'

NicClusterPolicy:暴露 rdma/hca 资源

apiVersion: mellanox.com/v1alpha1
kind: NicClusterPolicy
metadata:
  name: nic-cluster-policy
  namespace: network-operator
spec:
  tolerations:
    - operator: Exists
      effect: NoSchedule
  # ofedDriver:                      # 宿主机已装驱动就注释掉
  #   image: doca-driver
  #   repository: nvcr.io/nvidia/mellanox
  #   version: doca3.3.0-26.01-1.0.0.0-0
  rdmaSharedDevicePlugin:
    image: k8s-rdma-shared-dev-plugin
    repository: nvcr.io/nvidia/mellanox
    version: network-operator-v26.1.0
    config: |
      {
        "configList": [
          {
            "resourceName": "hca",
            "rdmaHcaMax": 63,
            "selectors": { "vendors": ["15b3"] }
          }
        ]
      }
kubectl apply -f nicclusterpolicy.yaml
kubectl get pods -n network-operator -o wide

验证资源真的暴露出来了

kubectl describe node <gpu-node> | grep -A8 'Allocatable'
Allocatable:
  cpu:                 126
  nvidia.com/gpu:      8
  rdma/hca:            63          ← 这一行必须有
×Allocatable 里没有 rdma/hca 时的三个查法

这是最高频的第一道坎。按顺序排:

# 1. 设备插件 Pod 起来了吗,日志有没有报错
kubectl logs -n network-operator -l app=rdma-shared-dp | tail -30

# 2. 宿主机上有设备文件吗(没有 = 驱动问题,不是 K8s 问题)
ls -l /dev/infiniband/

# 3. selector 匹配到卡了吗(15b3 是 Mellanox 的 PCI vendor ID)
lspci -nn | grep 15b3

第 2 步是分水岭:/dev/infiniband/ 为空说明是驱动层问题, 再怎么调 K8s 也没用,回去装 OFED。

共享设备插件的含义

rdmaHcaMax: 63 不是"有 63 张卡",而是允许最多 63 个 Pod 共享这些 HCA。

插件类型语义适合
rdma-shared-device-plugin多个 Pod 共享同一批 HCA,rdma/hca: 1 表示"我要访问 RDMA",不独占AI 训练主流做法
sriov-device-plugin每个 Pod 独占一个 VF,硬件级隔离需要隔离或 QoS 的多租户

所以训练 Pod 写 rdma/hca: 1 就能访问全部 HCA(配合 NCCL_IB_HCA 决定实际用哪几张)—— 这一点新人常误解为"只给了一张卡"。

两条路线:hostNetwork 与 MacVLAN

第三个问题——网络配置。这里要按场景分叉。

路线一:IB 场景(最简单)

纯 InfiniBand 下,建链走 IB 自己的寻址(LID/GID),不依赖 Pod 的 IP:

spec:
  containers:
    - name: trainer
      resources:
        limits:
          nvidia.com/gpu: 8
          rdma/hca: 1
      securityContext:
        capabilities:
          add: ["IPC_LOCK"]         # 注册内存要锁内存

只申请资源、不动网络配置就够了。

路线二:RoCE + hostNetwork

RoCE 建链要用 IP 地址,而 Pod 默认那个 overlay IP 不在 RoCE 网段上。 最省事的解法是把宿主机网卡放进 Pod 的网络命名空间:

spec:
  hostNetwork: true              # 关键
  dnsPolicy: ClusterFirstWithHostNet
  containers:
    - name: trainer
      resources:
        limits:
          nvidia.com/gpu: 8
          rdma/hca: 1
      securityContext:
        capabilities:
          add: ["IPC_LOCK"]
# Pod 内应该能看到宿主机的 RoCE 网卡与地址
kubectl exec -it trainer-0 -- ip -brief addr | grep -E 'bond|ens'
!RoCE 用 hostNetwork 时必须显式设 true

这条规则值得单独记:hostNetwork: true 不是可选优化,是 RoCE hostnet 模式的前提。 不设的话 Pod 里看不到 RoCE 网段的地址,NCCL 会找不到可用的 IB 设备并静默回退 TCP—— 功能全对,带宽只剩几分之一。

代价是同节点跑不了第二个任务:NCCL 与 MPI 的 sshd 都要监听固定端口, 第二个 Pod 会端口撞车、CrashLoopBackOff。 详见 次级 CNI 那节的取舍表。

路线三:RoCE + MacVLAN(共享集群)

要在同节点跑多个任务,就给每个 Pod 一套独立 IP:

apiVersion: mellanox.com/v1alpha1
kind: MacvlanNetwork
metadata:
  name: hca
  namespace: network-operator
spec:
  networkNamespace: "default"
  master: "bond0"
  mode: "bridge"
  mtu: 1500
  ipam: |
    { "type": "nv-ipam", "poolName": "hca" }
---
apiVersion: nv-ipam.nvidia.com/v1alpha1
kind: IPPool
metadata:
  name: hca
  namespace: network-operator
spec:
  subnet: 192.168.0.0/20
  perNodeBlockSize: 8
  gateway: 192.168.0.1

Pod 侧用注解申请:

metadata:
  annotations:
    k8s.v1.cni.cncf.io/networks: network-operator/hca
spec:
  containers:
    - name: trainer
      resources:
        limits:
          nvidia.com/gpu: 8
          rdma/hca: 1
      securityContext:
        capabilities:
          add: ["IPC_LOCK"]

三条路线对照:

IBRoCE + hostNetworkRoCE + MacVLAN
额外组件无无Multus/Spiderpool + IP 池
Pod IP无关宿主机 IP独立 IP
同节点多任务✅❌ 端口冲突✅
配置复杂度低低中

在 Pod 里验证:四步

部署完不要直接跑训练,先用两个测试 Pod 对打。

kubectl apply -f tests.yaml        # 起两个带 rdma/hca 的 Pod
kubectl get pods -o wide           # 记下它们在哪两台节点上

第一步:设备可见

kubectl exec -it tests-rdma-hca-aaa -- ibv_devinfo | grep -E 'hca_id|state|link_layer'
kubectl exec -it tests-rdma-hca-aaa -- ibdev2netdev

第二步:拿 IP 与 GID

# hostNetwork 模式看 bond0;MacVLAN 模式看 eth0/net1
kubectl exec -it tests-rdma-hca-aaa -- ip addr show bond0
kubectl exec -it tests-rdma-hca-aaa -- show_gids | grep v2

第三步:服务端起测试

kubectl exec -it tests-rdma-hca-aaa -- \
  ib_send_bw -x 3 -d mlx5_bond_0 -F --report_gbits

第四步:客户端对打

kubectl exec -it tests-rdma-hca-bbb -- \
  ib_send_bw -x 3 -d mlx5_bond_0 -F --report_gbits <server_pod_ip>

合格线和裸机一样:理论线速的 90%。

kubectl delete -f tests.yaml       # 测完记得删,它占着 rdma/hca 资源
✓容器里测出来比裸机低怎么办

先分清是配置差异还是容器开销。RDMA 数据路径绕过内核, 容器化本身几乎不引入额外开销——所以差距一定来自配置:

差距查什么
完全连不上GID index 变了(MacVLAN 模式下 GID 表可能不同)、设备名写错
带宽只剩几分之一是不是回退了 TCP:ibv_devinfo 在 Pod 内是否真能看到设备
稳定偏低ulimit -l 与 IPC_LOCK;或者 Pod 被调度到了跨 NUMA 的 CPU 上
延迟高、抖动大CPU 亲和与 NUMA:nvidia-smi topo -m 看 GPU-NIC 是不是 PIX

注意 MacVLAN 模式下 Pod 内的 GID index 可能与宿主机不同, 每次都重新 show_gids 确认,别照抄宿主机的值。

GPU 亲和:调度器不会替你考虑

K8s 的调度器知道"这台机器有 8 张 GPU 和 63 个 rdma/hca 额度",但它不知道 哪张 GPU 该配哪张网卡。而 PCIe 那节 讲过, GPU 与网卡不在同一个 PCIe switch 下(不是 PIX)会明显掉性能。

kubectl exec -it trainer-0 -- nvidia-smi topo -m

实践上的处理:

  • 独占整机(nvidia.com/gpu: 8)——最简单,8 张卡 8 张网卡都归你,亲和性天然满足
  • 共享节点——要靠 GPU Operator 的拓扑感知调度或自定义调度器,否则可能拿到 跨 NUMA 的 GPU-NIC 组合
  • 验收时一定看一眼 topo -m,别假设它是对的

排障清单

# 1. 宿主机层:驱动与设备
ofed_info -s; ls /dev/infiniband/; ibstat -l

# 2. 集群层:资源是否暴露
kubectl describe node <node> | grep -A6 Allocatable
kubectl logs -n network-operator -l app=rdma-shared-dp | tail -20

# 3. Pod 层:设备是否可见
kubectl exec -it <pod> -- ibv_devinfo | head
kubectl exec -it <pod> -- ls -l /dev/infiniband/

# 4. 网络层:地址与 GID
kubectl exec -it <pod> -- ip -brief addr
kubectl exec -it <pod> -- show_gids | grep v2

# 5. 性能层:点对点基线
kubectl exec -it <pod> -- ib_send_bw -x 3 -d <dev> -F --report_gbits <peer>

# 6. 权限层
kubectl exec -it <pod> -- sh -c 'ulimit -l'
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].securityContext}'

从下往上排:宿主机 → 集群 → Pod → 网络 → 性能。跳层会浪费时间。

检查点

Checkpoint单选

节点的 Allocatable 里没有 rdma/hca。第一步该确认什么?

Checkpoint单选

RoCE 场景下忘记设 hostNetwork: true,最可能的后果是?

Checkpoint单选

rdmaHcaMax: 63 配合 Pod 里写 rdma/hca: 1,含义是?

这节课的落点

  • 搬进 K8s 要解决三件事:设备可见、资源可调度、网络配置正确
  • 依赖顺序:NFD → DOCA OFED → Network Operator;驱动可宿主机预装或 Operator 下发
  • NicClusterPolicy 暴露 rdma/hca;Allocatable 里有这一行才算成功
  • 排查资源没暴露:设备插件日志 → /dev/infiniband/ 是否为空(驱动分水岭) → vendor selector
  • rdma/hca: 1 是访问许可不是卡数,共享插件下可访问全部 HCA
  • 三条网络路线:IB 最简单(只申请资源)、RoCE+hostNetwork(必须显式 true)、 RoCE+MacVLAN(同节点多任务)
  • RDMA 容器化几乎无额外开销,测出来低一定是配置;MacVLAN 下 GID index 要重新确认
  • 调度器不管 GPU-NIC 亲和,独占整机最省事,共享节点要拓扑感知调度
  • 排障从下往上:宿主机 → 集群 → Pod → 网络 → 性能

延伸资料

  • k8s-in-actionnetwork/network-operator/README.md
  • k8s-in-actionnetwork/network-operator/macvlan/README.md