返回资讯中心
外部精选
软件工程
#开发者实时流

Kubernetes 中 RDMA Pod 的 sysctl 配置实践

来源标题:Sysctl settings for RDMA pods in K8s

在 Kubernetes 中运行多 RoCE 网卡的 RDMA Pod 时,sysctl 配置的归属位置决定其是否生效。作者基于 A100、B300 等节点测试,梳理了 Pod 获取 RDMA 接口的多种方式及各自网络命名空间归属。在 16 条 RoCE 链路同子网场景下,Linux 默认 ARP 行为会导致对端学到错误网卡的 MAC 地址,进而丢包。

Hacker News 最新asaiacai2026/11/3 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

一台带有 16 条 RoCE rail 的 GPU 节点有 16 个网络接口,通常位于同一个子网。Linux 认为 IP 地址属于主机,而不是属于某个接口。对于单张网卡来说,这个默认行为没有问题。但在 16 条 rail 的情况下,它会以看似 RDMA 缺陷的方式破坏 RDMA。

解决办法始终是调整几个 sysctl。在 Kubernetes 上,难点在于这些设置位于哪里。Pod 可以通过多种方式获得 RDMA 接口。每种方式决定了接口最终进入哪个网络命名空间,以及由谁拥有它的设置。

本文逐一介绍每种方式。结果来自我在 2026 年 6 月至 10 月间运行的测试。大部分测试在 OKE 上成对的 A100 节点(BM.GPU.B4.8 和 BM.GPU4.8)上运行,每个节点有 8 个 GPU 和 16 条 RoCE rail。大部分 IPv6 相关部分在 B300 和 GB300 节点上运行。

以下是这里关键的设置。其含义来自内核的 ip-sysctl 文档。

在默认设置下,节点会在任意 rail 上应答其任意地址的 ARP。它还会选择任意本地地址作为 ARP 源地址。当一个子网中有 16 条 rail 时,对端可能会学到错误 rail 的 MAC 地址。随后 RoCE 帧会被发往该 rail 上不存在的 MAC 地址,fabric 会将其丢弃。

每个 IPv4 设置都存在于 conf/all、conf/default 和 conf/<interface> 中。对于 arp_ignore、arp_announce 和 rp_filter,内核使用 conf/all 和 conf/<interface> 中较高的值。arp_filter 只要两者之一开启就开启。因此,在 all 上设置某个值可以提升每个接口的值,但永远无法降低某个自身值更高的接口的值。

conf/default 则不同。它是稍后出现的接口的起始值。对于 IPv4,写入它不会改变 rail。一旦某个接口有了 IPv4 地址,内核就会认为它的所有设置都已设定。之后再写入 conf/default 会跳过它。

net.ipv4.conf 和 net.ipv6.conf 下的 sysctl 属于某个网络命名空间。hostNetwork Pod 使用节点的命名空间。其他每个 Pod 都有自己的命名空间。

新的命名空间并非从空开始。内核的 devconf_inherit_init_net 设置控制这一点,其默认值为 0。当值为 0 时,新命名空间会复制节点的 IPv4 all 和 default 值。IPv6 值则从内核默认值开始。我没有单独测试这条规则。它确实与我下面看到的情况相符。

移入命名空间的接口不会保留其设置。它会从其新命名空间的 conf/default 重新开始。我通过将一条未使用的 rail 移入测试命名空间再移回来验证了这一点。各列依次为 rp_filter、arp_ignore、arp_announce、accept_local 和 arp_filter:

第二行和第三行中的 rp_filter=2 是节点自身的 conf/default 值。内核默认值是 0,但这些节点设置为 2,测试命名空间复制了该值。

主机值之所以恢复,是因为移回操作是一个 udev add 事件。在这些节点上,udev 会为新接口运行 systemd-sysctl,从而重新应用节点的按接口配置文件。Pod 内部不会运行类似的东西。该 rail 还丢失了其策略路由表中的路由。

因此,按接口的值必须在接口到达后,在它最终所在的命名空间内设置。

hostNetwork Pod 按原样使用节点的 rail。节点配置拥有所有值。节点的 host agent 在启动时设置了上表第一行中的五个值。下面的 Pod 测试表明,其中只有两个值重要:arp_ignore=1 和 arp_announce=2。

节点上的两个组件修改了相互依赖的 IPv6 设置,而没有任何机制对它们进行排序:

if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) && (!idev || idev->cnf.accept_ra != 2) && fib6_info_hold_safe(rt)) { rcu_read_unlock(); ip6_del_rt(net, rt, false); goto restart;

我在一个节点首次启动期间对其进行了轮询。每一行显示时间、uptime、IPv6 转发状态、有地址的 rail 数量以及有默认路由的 rail 数量:

03:01:56 up=205s fwd=0 addr=2/16 defrt=3/16 03:02:25 up=234s fwd=1 addr=5/16 defrt=0/16

转发开启时,这些 rail 仍在获取地址。那一刻所有默认路由都消失了,而没有地址的 rail 也从未获得地址。在首次启动时,16 条 rail 中有 3 到 6 条有地址,且没有一条有默认路由。NCCL 会像这样失败:

还有一种情况需要小心。NIC 重置会移除 rail 接口并重新创建它们。新接口从 conf/default 开始,因此这些 rail 恢复时 accept_ra=1。在 Ubuntu 24.04 上,带有接口模式的 sysctl.d 文件可以解决这个问题,因为 udev 会将其应用到每个新接口:

/etc/sysctl.d/90-oke-rdma-ipv6.conf

net.ipv6.conf.rdma*.accept_ra = 2

我在一个 A100 节点上测试了它。没有该文件时,重置后的 NIC 的两个端口恢复时 accept_ra=1。有该文件时,它们恢复为 2。

使用 Multus 时,Pod 会通过 CNI 链获得额外接口。接口可以是 SR-IOV VF,由 SR-IOV CNI 移入 Pod。它也可以是 rail 的 IPvlan 子接口,由 IPvlan CNI 在 Pod 中创建。无论哪种方式,接口都从 Pod 命名空间的 conf/default 开始。

tuning CNI 在同一链中设置 sysctl。它用正在配置的接口名称替换 IFNAME,因此可以设置按接口的值。以下是 SR-IOV Network Operator 根据我的 SriovNetwork 生成的 NetworkAttachmentDefinition 中的 tuning 条目:

net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.IFNAME.arp_announce=2 net.ipv4.conf.IFNAME.arp_filter=1 net.ipv4.conf.IFNAME.arp_ignore=1 net.ipv4.conf.IFNAME.rp_filter=0 net.ipv4.conf.IFNAME.accept_local=1

我比较了一个只有 Pod 网络的 Pod 和一个带有一个 VF 及这些条目的 Pod:

只有 VF 发生了变化。Pod 的 all 值和 eth0 保持原样。每个节点 16 个 VF 上的 NCCL 在 4 GiB 时达到 190.21 GB/s 总线带宽,这是该次运行中最大的尺寸。

all.rp_filter=0 没有改变 VF。每个 VF 起始时 rp_filter=2,而内核使用较高的值。该链也在 IFNAME 上设置了 rp_filter,但在下面的 DRANET 测试中,rp_filter=2 没有导致任何失败。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →