MoreRSS

site iconLixueduan | 李学端修改

博客名:指月小筑。专注云原生,Go,坚持分享最佳实践、经验干货。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Lixueduan | 李学端的 RSS 预览

Kubernetes教程(五十)---K8s 1.35:In-Place Pod Resize 正式 GA,实现 Pod 资源零中断调整

2026-01-20 08:00:00

in-place-pod-resize-ga.jpeg 想象一下这样的场景:你的生产系统突然流量激增,某个 Pod 的 CPU 使用率已经飙升到 90%,传统做法是重建整个 Pod,导致服务中断 30 秒以上。而现在,只需一行命令,CPU 资源瞬间调整完毕,服务零中断!

这就是 Kubernetes 1.35 带来的重磅功能:原地 Pod 资源调整(In-Place Pod Resize)正式 GA!🎉

1. 什么是 In-Place Pod Resize?

原地 Pod 资源调整(In-Place Pod Resize) 是 Kubernetes 的一项革命性特性,允许直接修改正在运行的 Pod 的 CPU 和内存资源配置,而无需重建 Pod。

1.1 核心意义

In-Place Pod Resize 是一个基础构建块,可解锁无缝、垂直的自动缩放功能,并提高工作负载效率。

🎯 核心优势

  • ⚡ 零中断调整:CPU 调整无需重启,内存调整仅在必要时重启

    • 对延迟或重启敏感的工作负载可以在不停机或状态丢失的情况下就地修改其资源
  • ⚙️ 即时生效:调整立即见效,无需等待 Pod 重建

  • 🛡️ 高稳定性:告别重建导致的网络抖动和服务中断

  • 🎛️ 简化运维:资源调优从"重型手术"变为"日常微调"

🚀 解锁新能力

更强大的自动缩放功能:自动缩放器现在可以调整资源,影响更小。例如,垂直Pod自动缩放器(VPA)的 InPlaceOrRecreate 更新模式利用了这一特性,现已升级到 beta 版。这使得资源可以根据使用情况自动无缝地调整,并将中断降至最低。

解决临时资源需求:可以快速调整临时需要更多资源的工作负载。这将启用 CPU 启动加速(AEP-7862)等功能,应用程序可以在启动期间请求更多 CPU,然后自动缩减。

1.2 发展历程

这个功能可不是一蹴而就的!从 Kubernetes v1.27 开始作为 Alpha 版本引入,经过社区反复打磨:

  • v1.27: 🧪 Alpha 版本,基础功能验证
  • v1.33: 🏗️ Beta 版本,稳定性大幅提升
  • v1.35: 🎉 正式 GA,生产环境完全就绪!

经过多次迭代,现在你可以放心大胆地在生产环境中使用这个强大的功能了!

2. ⚠️ 重要限制和注意事项

2.1 💚 支持的资源类型

资源类型 支持程度 重启要求 说明
CPU ✅ 完全支持 🚫 无需重启 最常用,零中断调整
内存 ✅ 支持 ⚠️ 通常需要重启 大多数应用不支持热更新内存
存储 ❌ 不支持 - 技术限制,未来可能支持
GPU 等扩展资源 ❌ 不支持 - 仍在开发中

2.2 🏷️ QoS 类限制

Pod 的 QoS(服务质量)类调整有严格要求,调整后必须保持原有 QoS 类不变:

  • 🛡️ Guaranteedrequests = limits,最高优先级
  • ⚖️ Burstablerequests < limits,中等优先级
  • 🎲 BestEffort:无资源限制,最低优先级

⚠️ 重要提醒:无法通过资源调整改变 Pod 的 QoS 类!

2.3 🐳 容器类型支持情况

容器类型 支持程度 说明
普通容器 ✅ 完全支持 最常见的应用场景
Sidecar 容器 ✅ 支持调整 适用于服务网格等场景
Init 容器 ⚠️ 条件支持 仅支持调整尚未执行的Init容器
Ephemeral 容器 ❌ 不支持 临时调试容器不支持

2.4 🖥️ 节点和运行时限制

环境/配置 支持程度 影响范围
Windows Pod ❌ 不支持 整个 Windows 环境
静态 CPU 管理器 ❌ 不支持 CPU 亲和性场景
内存管理器 ❌ 部分不支持 特定内存策略
Swap 内存 ⚠️ 条件支持 仅特定配置下可用

2.5 🛡️ 安全和稳定性保证

🚨 重要风险提醒

  • 💥 内存减少风险:降低内存限制时,如果当前使用量超过新限制,可能会触发 OOM!
  • ✅ 调整验证:Kubelet 会严格验证调整请求的可行性,确保安全
  • 🔄 回滚保护:调整失败时自动保持原有配置,不会破坏运行状态

💡 最佳实践:调整内存前务必检查当前使用量,避免意外 OOM

3. 🔧 核心修改,一探究竟

3.1 📊 资源字段重构

In-Place Pod Resize 对 Kubernetes API 做了精妙的设计:

🎯 资源分为两层

  • 期望资源spec.containers[*].resources - 你想要的资源配置
  • 实际资源status.containerStatuses[*].resources - 容器真正获得的资源

✨ 关键变化spec.containers[*].resources 现在支持运行时修改!requestslimits 都可以动态调整。

3.2 📋 新增 Pod 状态

为了让调整过程透明可观测,Kubernetes 引入了三个新的 Pod 状态:

状态 含义 常见原因
PodResizePending ⏳ 调整请求待处理 资源不足、节点压力大等
PodResizeInProgress 🔄 正在执行调整 Kubelet 正在处理调整请求
PodResizeStatusInfeasible ❌ 无法调整(不符合策略) 违反QoS类限制、容器类型不支持等

通过 kubectl get podkubectl describe pod,你可以实时监控调整进度!

3.3 ⚙️ resizePolicy 配置

新增 resizePolicy 字段,让你精确控制每种资源的调整行为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
spec:
 containers:
 - name: app
 image: nginx
 resizePolicy: # 🎛️ 新增配置
 - resourceName: cpu  # 支持 cpu、memory
 restartPolicy: NotRequired  # CPU:无需重启(默认)
 - resourceName: memory
 restartPolicy: RestartContainer # 内存:需要重启容器
 resources:
 requests:
 cpu: 100m
 memory: 128Mi
 limits:
 cpu: 100m
 memory: 128Mi

🔧 restartPolicy 选项

  • NotRequired:调整无需重启(CPU 的最佳选择)
  • RestartContainer:调整需要重启容器(内存的默认选择)

💡 高级用法:如果你的应用支持热更新内存,可以将内存的 restartPolicy 设为 NotRequired,实现真正的零中断调整!

4. 🎮 实战演示,亲手试试!

4.1 🏗️ 创建测试 Pod

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat << 'EOF' > pod-resize.yaml
apiVersion: v1
kind: Pod
metadata:
 name: resize-demo
spec:
 containers:
 - name: pause
 image: registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0
 command: ["sleep", "3600"]
 resizePolicy:
 - resourceName: cpu
 restartPolicy: NotRequired # Default, but explicit here
 - resourceName: memory
 restartPolicy: RestartContainer
 resources:
 limits:
 memory: "200Mi"
 cpu: "700m"
 requests:
 memory: "200Mi"
 cpu: "700m"
EOF

✨ 注意这里的 resizePolicy 配置:

  • resourceName:指定要控制的资源类型(cpumemory
  • restartPolicy:决定调整时是否需要重启容器

🎯 配置解读

  • CPU 设置为 NotRequired:调整时零中断
  • 内存设置为 RestartContainer:调整时会重启容器(这是默认行为)

💡 为什么内存需要重启? 大多数应用程序和容器运行时不支持动态调整内存分配,所以需要重启来生效。

1
kubectl apply -f pod-resize.yaml

4.2 ⚡ CPU 调整:真正的零中断体验!

1
2
kubectl patch pod resize-demo --subresource resize --patch \
 '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"800m"}, "limits":{"cpu":"800m"}}}]}}'

🎯 操作目标:将 CPU 从 700m 提升到 800m,看看会发生什么?

验证:查看 Pod 状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
root@lixd-dev-2:~/k8s2# k get po resize-demo -oyaml|grep "resources:" -A 6
 resources:
 limits:
 cpu: 800m
 memory: 200Mi
 requests:
 cpu: 800m
 memory: 200Mi
--
 resources:
 limits:
 cpu: 800m
 memory: 200Mi
 requests:
 cpu: 800m
 memory: 200Mi
root@lixd-dev-2:~/k8s2# k get po
NAME READY STATUS RESTARTS AGE
resize-demo 1/1 Running 0 2m43s

🎉 结果惊艳

  • CPU 成功更新到 800m ✅
  • restart count 依然是 0(没有重启!)✅
  • In-Place Resize 完美生效! 🚀

验证:查看 Pod Event

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@lixd-dev-2:~/k8s2# k describe po resize-demo

Events:
 Type Reason Age From Message
 ---- ------ ---- ---- -------
 Normal Scheduled 57s default-scheduler Successfully assigned default/resize-demo to lixd-dev-2.taiko.local
 Normal Pulled 56s kubelet spec.containers{pause}: Container image "registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0" already present on machine and can be accessed by the pod
 Normal Created 56s kubelet spec.containers{pause}: Container created
 Normal Started 56s kubelet spec.containers{pause}: Container started
 Normal ResizeStarted 20s kubelet Pod resize started: {"containers":[{"name":"pause","resources":{"limits":{"cpu":"800m","memory":"200Mi"},"requests":{"cpu":"800m","memory":"200Mi"}}}],"generation":2}
 Normal ResizeCompleted 19s kubelet Pod resize completed: {"containers":[{"name":"pause","resources":{"limits":{"cpu":"800m","memory":"200Mi"},"requests":{"cpu":"800m","memory":"200Mi"}}}],"generation":2}

可以看到 Event 里面可以看到 ResizeStarted 和 ResizeCompleted 事件。

4.3 🧠 内存调整:需要重启但依然高效!

1
2
kubectl patch pod resize-demo --subresource resize --patch \
 '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"memory":"300Mi"}, "limits":{"memory":"300Mi"}}}]}}'

🎯 操作目标:将内存从 200Mi 提升到 300Mi,观察重启行为。

验证:查看 Pod 状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
root@lixd-dev-2:~/k8s2# k get po resize-demo -oyaml|grep "resources:" -A 6
 resources:
 limits:
 cpu: 700m
 memory: 300Mi
 requests:
 cpu: 700m
 memory: 300Mi
--
 resources:
 limits:
 cpu: 700m
 memory: 300Mi
 requests:
 cpu: 700m
 memory: 300Mi
root@lixd-dev-2:~/k8s2#
root@lixd-dev-2:~/k8s2# k get po
NAME READY STATUS RESTARTS AGE
resize-demo 1/1 Running 1 (83s ago) 118s

📊 结果分析

  • 内存成功更新到 300Mi ✅
  • Restart Count 变为 1(预期的重启)✅
  • 符合 RestartContainer 策略! 🎯

💡 进阶技巧:如果你的应用程序和运行时支持热更新内存,可以将 restartPolicy 设置为 NotRequired,实现内存调整的零中断!

验证:查看 Pod Event

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@lixd-dev-2:~/k8s2# k describe po resize-demo
Events:
 Type Reason Age From Message
 ---- ------ ---- ---- -------
 Normal Scheduled 2m53s default-scheduler Successfully assigned default/resize-demo to lixd-dev-2.taiko.local
 Normal ResizeStarted 2m30s kubelet Pod resize started: {"containers":[{"name":"pause","resources":{"limits":{"cpu":"700m","memory":"300Mi"},"requests":{"cpu":"700m","memory":"300Mi"}}}],"generation":2}
 Normal Killing 2m30s kubelet spec.containers{pause}: Container pause resize requires restart
 Normal Pulled 2m (x2 over 2m52s) kubelet spec.containers{pause}: Container image "registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0" already present on machine and can be accessed by the pod
 Normal Created 2m (x2 over 2m52s) kubelet spec.containers{pause}: Container created
 Normal Started 119s (x2 over 2m52s) kubelet spec.containers{pause}: Container started
 Normal ResizeCompleted 119s kubelet Pod resize completed: {"containers":[{"name":"pause","resources":{"limits":{"cpu":"700m","memory":"300Mi"},"requests":{"cpu":"700m","memory":"300Mi"}}}],"generation":2}

可以看到 ResizeStarted 之后有一个 Killing 事件,等重启后才 ResizeCompleted,这也符合内存调整的 RestartContainer 重启策略。

4.4 🚫 资源不足场景:调整请求被拒绝

1
2
kubectl patch pod resize-demo --subresource resize --patch \
 '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"1000"}, "limits":{"cpu":"1000"}}}]}}'

🎯 操作目标:故意申请超出节点容量的 CPU(1000 core),看看系统如何处理?

验证:查看 Pod 状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
root@lixd-dev-2:~/k8s2# k get po resize-demo -oyaml|grep "resources:" -A 6
 resources:
 limits:
 cpu: 1k
 memory: 200Mi
 requests:
 cpu: 1k
 memory: 200Mi
--
 resources:
 limits:
 cpu: 700m
 memory: 200Mi
 requests:
 cpu: 700m
 memory: 200Mi

🔍 现象分析

  • 期望资源(spec)CPU 更新为 1000 core ✅
  • 实际资源(status)仍为 700m(未生效)❌
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@lixd-dev-2:~/k8s2# k get po resize-demo -oyaml|grep "conditions:" -A 10
 conditions:
 - lastProbeTime: "2026-01-20T14:03:38Z"
 lastTransitionTime: "2026-01-20T14:03:38Z"
 message: 'Node didn''t have enough capacity: cpu, requested: 1000000, capacity:
 16000'
 observedGeneration: 2
 reason: Infeasible
 status: "True"
 type: PodResizePending

显示 PodResizePending 状态,错误信息清晰明了:“Node didn’t have enough capacity”!

有一个 PodResizePending 状态,因为资源不足,所以一直 pending。

验证:查看 Pod Event

1
2
3
4
5
6
7
8
9
root@lixd-dev-2:~/k8s2# k describe po resize-demo
Events:
 Type Reason Age From Message
 ---- ------ ---- ---- -------
 Normal Scheduled 3m29s default-scheduler Successfully assigned default/resize-demo to lixd-dev-2.taiko.local
 Normal Pulled 3m28s kubelet spec.containers{pause}: Container image "registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0" already present on machine and can be accessed by the pod
 Normal Created 3m28s kubelet spec.containers{pause}: Container created
 Normal Started 3m27s kubelet spec.containers{pause}: Container started
 Warning ResizeInfeasible 3m23s kubelet Pod resize Infeasible: {"containers":[{"name":"pause","resources":{"limits":{"cpu":"1k","memory":"200Mi"},"requests":{"cpu":"1k","memory":"200Mi"}}}],"generation":2,"error":"Node didn't have enough capacity: cpu, requested: 1000000, capacity: 16000"}

Event 里面也可以看到具体问题,ResizeInfeasible 事件。

5. 🎯 总结:资源管理的革命性突破

5.1 💎 功能价值,一目了然

In-Place Pod Resize GA 是 Kubernetes 资源管理领域的一次革命!

🚀 四大核心优势

  • ⚡ 零中断:CPU 调整无需重启,内存调整仅在必要时重启
  • ⚙️ 即时生效:调整立即见效,无需等待 Pod 重建
  • 🛡️ 高稳定性:告别重建导致的网络抖动和服务中断
  • 🎛️ 简化运维:资源调优从"重型手术"变为"日常微调"

🔄 技术革新

  • 破坏性重建非破坏性调整
  • 静态配置动态优化
  • 人工运维智能自动化

5.2 最佳实践建议

生产环境应用建议

  1. CPU 优先使用:优先使用 CPU 的原地调整能力,避免不必要的容器重启
  2. 内存调整策略:根据应用特性选择合适的 restartPolicy
    • 对延迟敏感的应用:评估是否支持热更新内存
    • 普通应用:使用默认的 RestartContainer 策略
  3. QoS 类保持:确保调整后 Pod 的 QoS 类不变,避免调度问题
  4. 资源监控:密切监控调整后的资源使用情况,及时发现和处理异常

配置建议

1
2
3
4
5
6
7
8
9
# 推荐的 resizePolicy 配置
spec:
 containers:
 - name: app
 resizePolicy:
 - resourceName: cpu
 restartPolicy: NotRequired  # CPU 调整无需重启
 - resourceName: memory
 restartPolicy: RestartContainer # 内存调整需要重启(除非应用支持热更新)

集成场景

  • VPA 集成:结合 Vertical Pod Autoscaler 实现智能资源调整
  • 自定义控制器:开发基于业务指标的自动扩缩容控制器
  • 多租户平台:为平台用户提供动态资源调整能力

In-Place Pod Resize 让 Kubernetes 的资源管理变得更加现代化和智能化。现在就开始体验这个强大的功能,让您的应用资源管理更上一层楼吧!

6. 参考

Kubernetes 1.35: In-Place Pod Resize Graduates to Stable

Configure Pod Container Resources In-Place

⚡ AI集群通信革命:GB200 MNNVL通过Kubernetes DRA实现跨节点800Gbps通信

2026-01-07 04:00:00

nvidia-dra-gpu.jpeg

NVIDIA GB200 NVL72 正在将 AI 基础设施推向新的极限,使大规模语言模型训练和低延迟推理工作负载成为可能。随着 Kubernetes 在部署和扩展这些工作负载中的核心作用日益增强,快速演进的 AI 工作负载、基础设施需求和新硬件架构为 Kubernetes 编排和资源管理带来了新的挑战。

在本文中,我们将深入探讨如何通过 Kubernetes DRA (Dynamic Resource Allocation) 和 NVIDIA DRA Driver 在 GB200 平台上启用 Multi-Node NVLink (MNNVL),实现跨节点的 GPU 到 GPU 高带宽通信。

核心概念

Kubernetes DRA (Dynamic Resource Allocation) 简介

DRA (Dynamic Resource Allocation,动态资源分配) 是 Kubernetes v1.30 引入的革命性功能,用于解决传统 Device Plugin 框架在处理复杂异构硬件时的局限性。

DRA 最初始的版本(KEP-3063),于 1.26 版本引入,因可用性问题,在 1.32 版本被撤回 当前的 DRA 是第二个版本(KEP-4381) 于 1.30 引入。

为什么需要 DRA?

传统 Device Plugin 将硬件资源抽象为简单整数计数器,无法表达:

  • 设备特定参数(如 GPU 显存、计算能力、拓扑连接)

    • 无论什么 GPU,都展示为 nvidia.com/gpu
  • 资源共享需求(设备无法在容器间共享)

  • 硬件拓扑关系(NVLink 连接、PCIe 亲和性)

DRA 核心设计

DRA 遵循 Kubernetes 声明式原则,将硬件资源特性作为一等公民纳入 API:

  • ResourceClass:定义硬件资源类型和特性(类似 StorageClass)
  • ResourceClaim:工作负载的声明式资源请求(类似 PVC)
  • ResourceClaimTemplate:创建多个相似 ResourceClaim 的模板
  • DRA Driver:由硬件厂商实现的资源分配逻辑

DRA 的优势

  • 声明式管理:通过标准 API 声明复杂资源需求
  • 参数化配置:支持设备特定参数和资源共享
  • 拓扑感知:原生支持硬件拓扑关系的调度决策
  • 跨节点分配:支持分布式资源分配场景

GB200 MNNVL (Multi-Node NVLink) 的价值

GB200 NVL72 通过引入 Multi-Node NVLink (MNNVL) 技术,将单机 GPU 性能限制扩展到整个机架层面,为分布式 AI 工作负载带来革命性改进。

传统的单节点 DGX 系统受限于单机物理限制,MNNVL 改变了这一局面:

  • 全 NVLink 带宽跨节点通信:通过 NVIDIA NVLink Switch 实现全 NVLink 带宽通信
  • 无缝扩展:将整个机架转换为统一的 GPU 架构
  • 性能倍增:实现超快分布式训练和推理

ComputeDomains:连接底层硬件与 Kubernetes

IMEX (Internode Memory Exchange)

NVIDIA Internode Memory Exchange Service (IMEX) 是 GPU 驱动层面的软件,允许 GPU 跨节点通信。IMEX 对每个单独的 GPU 内存导出/导入操作进行细粒度访问控制,并在称为 IMEX 域的节点组中运行。

ComputeDomains 核心概念

作为 NVIDIA GPUs 的 DRA 驱动程序的一部分提供的 ComputeDomains,将底层 GPU 构造(NVIDIA NVLink 和 NVIDIA IMEX)与现代 Kubernetes 原生调度概念(动态资源分配,简称 DRA)连接起来,为在现代 GPU 硬件上运行分布式多节点工作负载提供所需的基础支持。

如果没有 ComputeDomains,多节点 NVLink 设置将不得不手动定义并固定到位,这限制了 Kubernetes 旨在提供的灵活性,并以牺牲安全隔离、故障隔离和成本效率为代价。

ComputeDomains 通过以下方式工作:

  • 动态创建 IMEX 域:根据工作负载调度自动形成 IMEX 域
  • 安全隔离:每个工作负载获得专用的隔离通信环境
  • 自动清理:工作负载完成后自动释放资源
  • 拓扑感知:理解并优化 GPU 连接关系

通过 ComputeDomains,运行分布式训练或跨复杂 NVLink 连接 GPU 架构的推理变得像部署标准 Kubernetes 工作负载一样简单。

环境部署

版本要求

软件信息:

  • Kubernetes:1.32 及以上,推荐 1.34
  • Containerd:支持 DRA 的版本 1.7.x,推荐 1.7.29
  • GPU Operator:25.3.x 及以上,推荐 25.10.0
  • DRA Driver:推荐部署最新的 25.8.0
  • NVIDIA GPU Driver:需要 565 或更新版本
    • 如果使用 DRA Driver 25.8.0,则需要驱动版本 >= 570.158.1

硬件信息:

  • 系统:GB200 NVL72(一柜系统中的 2 节点子集)
  • 节点数量:2 个节点
  • 每个节点配置
    • GPU:4 个 GB200 GPU(本次测试共 8 个 GPU)
    • GPU 显存:每个 GPU 192GB(189471 MiB)
    • CPU:2 个 Grace CPU

GPU 基本信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
root@GB200-POD2-F06-Node05:~/lixd/nccl-demo# nvidia-smi
Wed Dec 10 10:24:44 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05 Driver Version: 580.95.05 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GB200 On | 00000008:01:00.0 Off | 0 |
| N/A 37C P0 169W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA GB200 On | 00000009:01:00.0 Off | 0 |
| N/A 37C P0 157W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA GB200 On | 00000018:01:00.0 Off | 0 |
| N/A 38C P0 158W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA GB200 On | 00000019:01:00.0 Off | 0 |
| N/A 37C P0 166W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+

GPU 拓扑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root@GB200-POD2-F06-Node05:~/lixd/nccl-demo# nvidia-smi topo -m
	GPU0	GPU1	GPU2	GPU3	NIC0	NIC1	NIC2	NIC3	NIC4	NIC5	NIC6	NIC7	CPU Affinity	NUMA Affinity	GPU NUMA ID
GPU0	 X 	NV18	NV18	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	0-71	0,2-17		N/A
GPU1	NV18	 X 	NV18	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	0-71	0,2-17		N/A
GPU2	NV18	NV18	 X 	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	72-143	1,18-33		N/A
GPU3	NV18	NV18	NV18	 X 	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	72-143	1,18-33		N/A
NIC0	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS	SYS	SYS	SYS	SYS
NIC1	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS	SYS	SYS	SYS
NIC2	SYS	SYS	SYS	SYS	SYS	SYS	 X 	PIX	SYS	SYS	SYS	SYS
NIC3	SYS	SYS	SYS	SYS	SYS	SYS	PIX	 X 	SYS	SYS	SYS	SYS
NIC4	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS
NIC5	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS
NIC6	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	PIX
NIC7	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	PIX	 X

Legend:

 X = Self
 SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
 NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
 PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
 PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
 PIX = Connection traversing at most a single PCIe bridge
 NV# = Connection traversing a bonded set of # NVLinks

NIC Legend:

 NIC0: mlx5_0
 NIC1: mlx5_1
 NIC2: mlx5_2
 NIC3: mlx5_3
 NIC4: mlx5_4
 NIC5: mlx5_5
 NIC6: mlx5_6
 NIC7: mlx5_7

环境准备

GPU Operator

部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 添加 NVIDIA Helm 仓库
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update

# 部署 GPU Operator
helm install --wait gpu-operator \
 -n gpu-operator --create-namespace \
 nvidia/gpu-operator \
 --version=v25.10.0 \
 --set driver.enabled=true \
 --set dcgmExporter.serviceMonitor.enabled=true \
 --set dcgm.enabled=true

提示:部署过程可能需要 5-10 分钟,请耐心等待。可以通过 kubectl get pods -n gpu-operator 监控部署进度。

验证

如果部署成功,那么节点上可以看到 nvidia.com/gpu 资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@GB200-POD2-F06-Node05:~/lixd# kubectl describe node gb200-pod2-f06-node05|grep Capacity -C 7 Addresses:
 InternalIP: 10.0.6.41
 Hostname: gb200-pod2-f06-node05
Capacity:
 cpu: 144
 ephemeral-storage: 1840577300Ki
 hugepages-16Gi: 0
 hugepages-2Mi: 0
 hugepages-512Mi: 0
 memory: 1002717120Ki
 nvidia.com/gpu: 4

NVIDIA DRA Driver

部署

1
2
3
4
5
6
7
# 部署 NVIDIA DRA Driver
helm upgrade --install nvidia-dra-driver-gpu nvidia/nvidia-dra-driver-gpu \
 --version="25.8.0" \
 --create-namespace \
 --namespace nvidia-dra-driver-gpu \
 --set resources.gpus.enabled=false \
 --set nvidiaDriverRoot=/run/nvidia/driver

重要参数说明

  • resources.gpus.enabled=false:禁用默认 GPU 资源管理,由 GPU Operator 处理
  • nvidiaDriverRoot=/run/nvidia/driver:指定 NVIDIA 驱动路径

验证

正常情况下,可以看到节点间的 nvidia.com/gpu.clique label。

1
2
3
4
5
6
root@GB200-POD2-F06-Node05:~/lixd# (echo -e "NODE\tLABEL\tCLIQUE"; kubectl get nodes -o json | \
 /usr/bin/jq -r '.items[] | [.metadata.name, "nvidia.com/gpu.clique", .metadata.labels["nvidia.com/gpu.clique"]] | @tsv') | \
 column -t
NODE LABEL CLIQUE
gb200-pod2-f06-node05 nvidia.com/gpu.clique 69a19a31-f41c-45a5-8245-579b6bce5bdd.32766
gb200-pod2-f06-node06 nvidia.com/gpu.clique 69a19a31-f41c-45a5-8245-579b6bce5bdd.32766

创建 IMEX 负载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cat <<EOF > imex-channel-injection.yaml
---
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
 name: imex-channel-injection
spec:
 numNodes: 1
 channel:
 resourceClaimTemplate:
 name: imex-channel-0
---
apiVersion: v1
kind: Pod
metadata:
 name: imex-channel-injection
spec:
 affinity:
 nodeAffinity:
 requiredDuringSchedulingIgnoredDuringExecution:
 nodeSelectorTerms:
 - matchExpressions:
 - key: nvidia.com/gpu.clique
 operator: Exists
 containers:
 - name: ctr
 image: ubuntu:22.04
 command: ["bash", "-c"]
 args: ["ls -la /dev/nvidia-caps-imex-channels; trap 'exit 0' TERM; sleep 9999 & wait"]
 resources:
 claims:
 - name: imex-channel-0
 resourceClaims:
 - name: imex-channel-0
 resourceClaimTemplateName: imex-channel-0
EOF

查看日志,正常能看到注入的 imex channel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@GB200-POD2-F06-Node05:~/lixd/demo# kubectl apply -f imex-channel-injection.yaml
computedomain.resource.nvidia.com/imex-channel-injection created
pod/imex-channel-injection created
root@GB200-POD2-F06-Node05:~/lixd/demo# kubectl get pods
NAME READY STATUS RESTARTS AGE
imex-channel-injection 1/1 Running 0 5s
root@GB200-POD2-F06-Node05:~/lixd/demo# kubectl logs imex-channel-injection
total 0
drwxr-xr-x 2 root root 60 Jan 5 08:31 .
drwxr-xr-x 6 root root 380 Jan 5 08:31 ..
crw-rw-rw- 1 root root 501, 0 Jan 5 08:31 channel0

至此,说明 nvidia-dra-driver 部署成功。

验证测试

安装 MPI Operator

首先安装 MPI Operator,用于运行多节点 MPI 作业:

1
kubectl create -f https://github.com/kubeflow/mpi-operator/releases/download/v0.6.0/mpi-operator.yaml

nvbandwidth 测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
cat <<EOF > nvbandwidth-test-job.yaml
---
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
 name: nvbandwidth-test-compute-domain
spec:
 numNodes: 2
 channel:
 resourceClaimTemplate:
 name: nvbandwidth-test-compute-domain-channel

---
apiVersion: kubeflow.org/v2beta1
kind: MPIJob
metadata:
 name: nvbandwidth-test
spec:
 slotsPerWorker: 4
 launcherCreationPolicy: WaitForWorkersReady
 runPolicy:
 cleanPodPolicy: Running
 sshAuthMountPath: /home/mpiuser/.ssh
 mpiReplicaSpecs:
 Launcher:
 replicas: 1
 template:
 metadata:
 labels:
 nvbandwidth-test-replica: mpi-launcher
 spec:
 affinity:
 nodeAffinity:
 requiredDuringSchedulingIgnoredDuringExecution:
 nodeSelectorTerms:
 - matchExpressions:
 - key: node-role.kubernetes.io/control-plane
 operator: Exists
 containers:
 - image: ghcr.io/nvidia/k8s-samples:nvbandwidth-v0.7-8d103163
 name: mpi-launcher
 securityContext:
 runAsUser: 1000
 command:
 - mpirun
 args:
 - --bind-to
 - core
 - --map-by
 - ppr:4:node
 - -np
 - "8"
 - --report-bindings
 - -q
 - nvbandwidth
 - -t
 - multinode_device_to_device_memcpy_read_ce
 Worker:
 replicas: 2
 template:
 metadata:
 labels:
 nvbandwidth-test-replica: mpi-worker
 spec:
 affinity:
 podAffinity:
 requiredDuringSchedulingIgnoredDuringExecution:
 - labelSelector:
 matchExpressions:
 - key: nvbandwidth-test-replica
 operator: In
 values:
 - mpi-worker
 topologyKey: nvidia.com/gpu.clique
 containers:
 - image: ghcr.io/nvidia/k8s-samples:nvbandwidth-v0.7-8d103163
 name: mpi-worker
 securityContext:
 runAsUser: 1000
 env:
 command:
 - /usr/sbin/sshd
 args:
 - -De
 - -f
 - /home/mpiuser/.sshd_config
 resources:
 limits:
 nvidia.com/gpu: 4
 claims:
 - name: compute-domain-channel
 resourceClaims:
 - name: compute-domain-channel
 resourceClaimTemplateName: nvbandwidth-test-compute-domain-channel
EOF

Apply

1
2
3
root@GB200-POD2-F06-Node05:~/lixd/demo# kubectl apply -f nvbandwidth-test-job.yaml
computedomain.resource.nvidia.com/nvbandwidth-test-compute-domain created
mpijob.kubeflow.org/nvbandwidth-test created

会自动启动 Pod 进行测试

1
2
3
4
5
root@GB200-POD2-F06-Node05:~/lixd/demo# k get po
NAME READY STATUS RESTARTS AGE
nvbandwidth-test-launcher-xl87m 1/1 Running 0 26s
nvbandwidth-test-worker-0 1/1 Running 0 7m41s
nvbandwidth-test-worker-1 1/1 Running 0 7m41s

查看日志

1
 kubectl logs --tail=-1 -l job-name=nvbandwidth-test-launcher

测试结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
nvbandwidth Version: v0.7
Built from Git version: v0.7

MPI version: Open MPI v4.1.4, package: Debian OpenMPI, ident: 4.1.4, repo rev: v4.1.4, May 26, 2022
CUDA Runtime Version: 13000
CUDA Driver Version: 13000
Driver Version: 580.95.05

Process 0 (nvbandwidth-test-worker-0): device 0: NVIDIA GB200 (00000008:01:00)
Process 1 (nvbandwidth-test-worker-0): device 1: NVIDIA GB200 (00000009:01:00)
Process 2 (nvbandwidth-test-worker-0): device 2: NVIDIA GB200 (00000018:01:00)
Process 3 (nvbandwidth-test-worker-0): device 3: NVIDIA GB200 (00000019:01:00)
Process 4 (nvbandwidth-test-worker-1): device 0: NVIDIA GB200 (00000008:01:00)
Process 5 (nvbandwidth-test-worker-1): device 1: NVIDIA GB200 (00000009:01:00)
Process 6 (nvbandwidth-test-worker-1): device 2: NVIDIA GB200 (00000018:01:00)
Process 7 (nvbandwidth-test-worker-1): device 3: NVIDIA GB200 (00000019:01:00)

Running multinode_device_to_device_memcpy_read_ce.
memcpy CE GPU(row) -> GPU(column) bandwidth (GB/s)
 0 1 2 3 4 5 6 7
 0 N/A 822.16 821.69 821.92 821.45 821.30 821.53 821.69
 1 820.90 N/A 821.92 821.61 821.30 821.06 822.00 821.69
 2 820.59 821.77 N/A 821.69 821.45 821.06 821.37 821.30
 3 820.51 821.77 821.61 N/A 821.37 821.22 821.30 821.92
 4 820.75 821.53 821.45 821.85 N/A 821.37 821.61 821.85
 5 820.51 821.53 821.22 821.69 821.69 N/A 821.61 821.77
 6 820.35 821.30 821.53 821.37 821.30 820.90 N/A 821.14
 7 820.59 821.69 820.98 821.37 821.37 821.14 821.30 N/A

SUM multinode_device_to_device_memcpy_read_ce 45997.93

NOTE: The reported results may not reflect the full capabilities of the platform.
Performance can vary with software drivers, hardware clocks, and system topology.

测试结果显示跨节点 GPU-to-GPU 通信带宽稳定在 820 GB/s 左右,远超传统 InfiniBand 等网络互联方案的性能,为大规模分布式 AI 训练提供了强大的通信基础。

总结

通过本文的实战指南,您已经学会了如何在 GB200 平台上部署和配置 NVIDIA DRA Driver,以启用 Multi-Node NVLink (MNNVL) 功能。主要成就包括:

  • 理解核心概念:掌握了 DRA、IMEX 和 ComputeDomains 的工作原理
  • 完成环境部署:成功部署 GPU Operator 和 DRA Driver
  • 验证功能:通过 nvbandwidth 测试确认跨节点 GPU 通信正常工作

ComputeDomains 技术将复杂的底层 GPU 硬件抽象为 Kubernetes 原生资源,使得多节点分布式 AI 工作负载的管理变得简单而高效。未来,随着更多 NVIDIA 架构的支持,这项技术将在 AI 基础设施领域发挥越来越重要的作用。

参考资料

🚀 当 InfiniBand 也不够快:GB200 MNNVL 实测带宽提升 10 倍

2025-12-17 04:00:00

gb200-nvl72-nccl-test.jpeg

在上一篇 告别 TCP/IP 延迟:Kubernetes 中的 RDMA 高性能网络实战 中,我们介绍了如何在 Kubernetes 中启用 RDMA(InfiniBand)能力,实现了相比 TCP/IP 延迟降低 20-40 倍、带宽提升 40 倍以上的效果。然而在超大规模 AI 训练场景下,即便是 InfiniBand 的带宽也可能成为瓶颈——当 GPU 间需要频繁同步梯度时,跨节点通信效率直接决定了整体训练吞吐。那么,有没有比 InfiniBand 更高效的多节点互联方案?答案是 MNNVL(Multi-Node NVLink)

GB200 NVL72 是 NVIDIA 推出的超级系统,专为大规模 AI 训练和推理设计。完整一柜 GB200 NVL72 系统包含 72 个 GB200 GPU + 36 个 Grace CPU,通过第五代 NVLink 高速互连。本文测试的是其中的 2 个节点子集(每节点 4 个 GB200 + 2 个 Grace,共 8 个 GPU),主要关注不同互联方案在多节点场景下的带宽差异。

MNNVL(Multi-Node NVLink) 是 GB200 NVL72 的核心创新特性,它通过 NVLink 实现跨节点 GPU 直连,让多节点 GPU 集群的性能接近单节点。与传统的 InfiniBand 网络相比,MNNVL 具有以下优势(支持 MNNVL 的 NCCL 版本通常会优先选择该通道,也可通过 NCCL_MNNVL_ENABLE=1 显式开启或确认使用):

  • 超低延迟:GPU 之间直接通信,绕过网络协议栈,延迟极低
  • 超高带宽:实测带宽可达 811GB/s,远超传统网络方案
  • 无缝集成:NCCL 自动识别并优先使用 MNNVL,无需额外配置

在实际生产环境中,我们需要了解不同网络方案的性能差异,以便在混合集群、容错场景或特定拓扑下做出最优选择。本文通过 2 节点 8 GPU 的 NCCL micro-benchmark 实测数据 对比 MNNVL、InfiniBand 和 TCP 以太网的性能表现,为 AI 训练集群的网络选型提供参考(并非完整业务训练 benchmark)。


核心亮点:2 节点 8 GPU 配置下,MNNVL 实测带宽 811GB/s,InfiniBand 为 85GB/s性能提升 9.5 倍!实测数据证明 MNNVL 是 GB200 NVL72 多节点通信的最佳选择。

测试结果概览

连接方式 带宽 性能倍数 说明
MNNVL 811GB/s 9.5x 跨节点 GPU 直连,性能最优 ✅
InfiniBand (400G) 85GB/s 1x 传统网络方案,性能中等 ⚠️
TCP 以太网 2GB/s 0.02x 普通网络,性能最低 ❌

说明:上表中的带宽数值均来自 nccl-tests 输出的 Bus bandwidth(busbw) 指标,是 NCCL 对整体通信效率的“等效总线带宽”度量,并不等同于单条物理链路的单向线速。

关键发现

  • 🚀 MNNVL 性能是 IB 的 9.5 倍(811GB/s ÷ 85GB/s ≈ 9.5)
  • 🚀 MNNVL 性能是 TCP 的 405 倍(811GB/s ÷ 2GB/s ≈ 405)
  • 🚀 通信时间减少 89%(从 IB 的 9.5 倍时间降到 1 倍时间)

1. 🖥️ 环境说明

1.1 硬件配置

  • 系统:GB200 NVL72(一柜系统中的 2 节点子集)
  • 节点数量:2 个节点
  • 每个节点配置
    • GPU:4 个 GB200 GPU(本次测试共 8 个 GPU)
    • GPU 显存:每个 GPU 192GB(189471 MiB)
    • CPU:2 个 Grace CPU
    • 网络:1 张 400G InfiniBand 网卡(用于对比测试)
  • 节点间连接
    • MNNVL:通过 Multi-Node NVLink 实现跨节点 GPU 直连
    • InfiniBand:单 400G 网卡作为对比测试

1.2 软件环境

  • 操作系统:Linux
  • CUDA 版本:13.0
  • 驱动版本:580.95.05
  • 测试工具:nccl-tests

1.3 GPU 信息

GPU 基本信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
root@GB200-POD2-F06-Node05:~/lixd/nccl-demo# nvidia-smi
Wed Dec 10 10:24:44 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05 Driver Version: 580.95.05 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GB200 On | 00000008:01:00.0 Off | 0 |
| N/A 37C P0 169W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA GB200 On | 00000009:01:00.0 Off | 0 |
| N/A 37C P0 157W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA GB200 On | 00000018:01:00.0 Off | 0 |
| N/A 38C P0 158W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA GB200 On | 00000019:01:00.0 Off | 0 |
| N/A 37C P0 166W / 1200W | 0MiB / 189471MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+

GPU 拓扑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root@GB200-POD2-F06-Node05:~/lixd/nccl-demo# nvidia-smi topo -m
	GPU0	GPU1	GPU2	GPU3	NIC0	NIC1	NIC2	NIC3	NIC4	NIC5	NIC6	NIC7	CPU Affinity	NUMA Affinity	GPU NUMA ID
GPU0	 X 	NV18	NV18	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	0-71	0,2-17		N/A
GPU1	NV18	 X 	NV18	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	0-71	0,2-17		N/A
GPU2	NV18	NV18	 X 	NV18	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	72-143	1,18-33		N/A
GPU3	NV18	NV18	NV18	 X 	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	72-143	1,18-33		N/A
NIC0	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS	SYS	SYS	SYS	SYS
NIC1	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS	SYS	SYS	SYS
NIC2	SYS	SYS	SYS	SYS	SYS	SYS	 X 	PIX	SYS	SYS	SYS	SYS
NIC3	SYS	SYS	SYS	SYS	SYS	SYS	PIX	 X 	SYS	SYS	SYS	SYS
NIC4	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS	SYS
NIC5	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	SYS	SYS
NIC6	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	 X 	PIX
NIC7	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	SYS	PIX	 X

Legend:

 X = Self
 SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI)
 NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node
 PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU)
 PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge)
 PIX = Connection traversing at most a single PCIe bridge
 NV# = Connection traversing a bonded set of # NVLinks

NIC Legend:

 NIC0: mlx5_0
 NIC1: mlx5_1
 NIC2: mlx5_2
 NIC3: mlx5_3
 NIC4: mlx5_4
 NIC5: mlx5_5
 NIC6: mlx5_6
 NIC7: mlx5_7

2. 🔧 安装 nccl-tests 环境

⚠️ 重要提示:需要在所有待测试节点安装环境,同时安装目录要保持一致。

nccl-tests 包含一系列测试项目,主要用于测试和验证 NCCL 的性能以及操作正确性。

These tests check both the performance and the correctness of NCCL operations.

2.1 OpenMPI-5.0.9

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 下载解压
wget https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.9.tar.gz
tar -zxvf openmpi-5.0.9.tar.gz

# 编译安装
pushd openmpi-5.0.9
./configure --prefix=/usr/local/openmpi
make -j$(nproc)
make install
popd

参数说明:

  • --prefix=/usr/local/openmpi:指定 MPI 安装位置为 /usr/local/openmpi

2.2 NCCL

1
2
3
4
5
6
# clone 源码
git clone https://github.com/NVIDIA/nccl.git
# 编译安装
pushd nccl
make -j$(nproc) src.build CUDA_HOME=/cm/shared/apps/cuda13.0/toolkit/13.0/
popd

参数说明:

  • CUDA_HOME:CUDA 安装位置

注意:需要记录下这里的 nccl 文件夹路径,make 之后会在 nccl 目录下生成 build 目录,后续编译 nccl-tests 的时候会用到该目录。

2.3 nccl-tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# clone 代码
git clone https://github.com/NVIDIA/nccl-tests.git

# 编译安装
pushd nccl-tests
make MPI=1 NAME_SUFFIX=_mpi \
 MPI_HOME=/usr/local/openmpi \
 CUDA_HOME=/cm/shared/apps/cuda13.0/toolkit/13.0 \
 NCCL_HOME=/root/lixd/inference/nccl-demo/nccl/build/
popd

参数说明:

  • MPI=1:开启 MPI

  • NAME_SUFFIX=_mpi:编译产物带上 _mpi 后缀,便于区分

  • CUDA_HOME:CUDA 安装位置

  • MPI_HOME:上一步编译 OpenMPI 的位置

  • NCCL_HOME:上一步 nccl build 的目录,就是 nccl 源码目录增加 /build

构建完成后,会在 build 目录下生成可执行文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@GB200-POD2-F06-Node05:~# ll -lhS /root/lixd/inference/nccl-demo/nccl-tests/build/
total 316M
-rwxr-xr-x 1 root root 35M Nov 19 07:32 all_reduce_perf_mpi*
-rwxr-xr-x 1 root root 35M Nov 19 07:33 alltoall_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:33 hypercube_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:33 sendrecv_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:33 gather_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:33 scatter_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:32 reduce_scatter_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:32 reduce_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:32 broadcast_perf_mpi*
-rwxr-xr-x 1 root root 30M Nov 19 07:32 all_gather_perf_mpi*
-rw-r--r-- 1 root root 6.1M Nov 19 07:32 all_reduce.o
-rw-r--r-- 1 root root 5.9M Nov 19 07:33 alltoall.o
-rw-r--r-- 1 root root 170K Nov 19 07:33 hypercube.o
-rw-r--r-- 1 root root 167K Nov 19 07:33 sendrecv.o
-rw-r--r-- 1 root root 161K Nov 19 07:33 gather.o
-rw-r--r-- 1 root root 158K Nov 19 07:33 scatter.o
-rw-r--r-- 1 root root 157K Nov 19 07:32 reduce_scatter.o
-rw-r--r-- 1 root root 157K Nov 19 07:32 reduce.o
-rw-r--r-- 1 root root 155K Nov 19 07:32 broadcast.o
-rw-r--r-- 1 root root 151K Nov 19 07:32 all_gather.o
-rw-r--r-- 1 root root 22K Nov 19 07:28 timer.o

3. 🚀 开始测试

3.1 配置环境变量

1
2
3
4
5
export CUDA_HOME=/cm/shared/apps/cuda13.0/toolkit/13.0
export MPI_HOME=/usr/local/openmpi
export NCCL_HOME=/root/lixd/inference/nccl-demo/nccl/build/
export PATH=$CUDA_HOME/bin:$MPI_HOME/bin:$PATH
export LD_LIBRARY_PATH=$MPI_HOME/lib:$NCCL_HOME/lib:$CUDA_HOME/lib64:$LD_LIBRARY_PATH

3.2 运行测试命令

注意:以下是一个示例命令,实际测试中根据场景调整参数(如 -g 参数)。

1
2
3
4
5
6
7
8
mpirun --allow-run-as-root \
 -x NCCL_DEBUG=INFO \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -np 2 \
 -H 10.0.6.41:1,10.0.6.42:1 \
 /root/lixd/inference/nccl-demo/nccl-tests/build/all_reduce_perf_mpi \
 -b 16G -e 16G -f 2 -g 4 -c 1 -i 100

ENV 相关参数

  • -x NCCL_DEBUG=INFO:把环境变量 NCCL_DEBUG=INFO 随进程一起下发,使 NCCL 打印初始化、网络选择、通道等调试信息。

  • -x PATH:将当前 shell 的 PATH 原样传递到远端进程,保证可执行文件能被找到。

  • -x LD_LIBRARY_PATH:同样传递动态库搜索路径,避免远端节点找不到 CUDA/OpenMPI/NCCL 的 .so 文件。

  • -x CUDA_HOME -x MPI_HOME -x NCCL_HOME:同上,传递 CUDA、MPI、NCCL home 目录

  • -np 2:进程数为 2

  • -H 10.0.6.41:1,10.0.6.42:1:指定节点列表,这里控制哪台机器跑几个进程。

测试相关参数

  • -b 16G:最小消息 16GB(示例命令中使用)

  • -e 16G:最大消息 16GB(示例命令中使用)

  • -f 2:每次测试数据量按照 2 倍递增

  • -g 4:每个 MPI 进程使用的 GPU 数量,当前是 4 GPU

    • 总 GPU 数 = 进程数 × 每个进程 GPU 数 = 2 × 4 = 8
  • -c 1:使用 1 个通信通道

  • -i 100:warm-up 后跑 100 次取平均

4. 📊 测试结果对比

4.1 MNNVL 测试结果

通过显式启用 NCCL_MNNVL_ENABLE=1NCCL_IMEX_ENABLE=1,指定 NCCL 使用 MNNVL 进行跨节点通信。

测试命令

1
2
3
4
5
6
7
mpirun --allow-run-as-root \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -x NCCL_MNNVL_ENABLE=1 -x NCCL_IMEX_ENABLE=1 \
 -H $node1:4,$node2:4 \
 /opt/nccl-tests/build/all_reduce_perf_mpi \
 -b 16G -e 16G -f 2 -g 1 -c 1 -i 100

测试结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
root@nccl-test-2nsk2:/workspace# mpirun --allow-run-as-root \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -x NCCL_MNNVL_ENABLE=1 -x NCCL_IMEX_ENABLE=1 \
 -H $node1:4,$node2:4 \
 /opt/nccl-tests/build/all_reduce_perf_mpi \
 -b 16G -e 16G -f 2 -g 1 -c 1 -i 100
Warning: Permanently added '172.25.114.45' (ED25519) to the list of known hosts.
# nccl-tests version 2.17.6 nccl-headers=22809 nccl-library=22809
# Collective test starting: all_reduce_perf_mpi
# nThread 1 nGpus 1 minBytes 17179869184 maxBytes 17179869184 step: 2(factor) warmup iters: 1 iters: 20 agg iters: 1 validation: 1 graph: 0
#
# Using devices
# Rank 0 Group 0 Pid 740 on nccl-test-2nsk2 device 0 [0008:01:00] NVIDIA GB200
# Rank 1 Group 0 Pid 741 on nccl-test-2nsk2 device 1 [0009:01:00] NVIDIA GB200
# Rank 2 Group 0 Pid 742 on nccl-test-2nsk2 device 2 [0018:01:00] NVIDIA GB200
# Rank 3 Group 0 Pid 743 on nccl-test-2nsk2 device 3 [0019:01:00] NVIDIA GB200
# Rank 4 Group 0 Pid 1167 on nccl-test-hw5qh device 0 [0008:01:00] NVIDIA GB200
# Rank 5 Group 0 Pid 1168 on nccl-test-hw5qh device 1 [0009:01:00] NVIDIA GB200
# Rank 6 Group 0 Pid 1170 on nccl-test-hw5qh device 2 [0018:01:00] NVIDIA GB200
# Rank 7 Group 0 Pid 1169 on nccl-test-hw5qh device 3 [0019:01:00] NVIDIA GB200
#
# out-of-place in-place
# size count type redop root time algbw busbw #wrong time algbw busbw #wrong
# (B) (elements) (us) (GB/s) (GB/s) (us) (GB/s) (GB/s)
 17179869184 4294967296 float sum -1 37038.7 463.84 811.71 0 37101.0 463.06 810.35 0
# Out of bounds values : 0 OK
# Avg bus bandwidth : 811.031
#
# Collective test concluded: all_reduce_perf_mpi

性能表现

  • 带宽:811GB/s
  • ✅ 跨节点 GPU 直连,性能最优
  • ✅ NCCL 会将两台机器的 4+4 GPU 放在同一跨节点 NVLink 通信域中,跨节点不走 IB/TCP

4.2 InfiniBand 测试结果

  • 通过指定 -x NCCL_MNNVL_ENABLE=0 以及 -x NCCL_IMEX_ENABLE=0 配置,关闭 MNNVL,强制走 IB。
  • -x NCCL_IB_HCA="mlx5_0" 指定 IB 卡

测试命令

1
2
3
4
5
6
7
8
root@nccl-sleep-2nsk2:/workspace# mpirun --allow-run-as-root \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -x NCCL_MNNVL_ENABLE=0 -x NCCL_IMEX_ENABLE=0 \
 -x NCCL_IB_HCA="mlx5_0" \
 -H $node1:4,$node2:4 \
 /opt/nccl-tests/build/all_reduce_perf_mpi \
 -b 16G -e 16G -f 2 -g 1 -c 1 -i 100

测试结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
root@nccl-test-2nsk2:/workspace# mpirun --allow-run-as-root \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -x NCCL_MNNVL_ENABLE=0 -x NCCL_IMEX_ENABLE=0 \
 -x NCCL_IB_HCA="mlx5_0" \
 -H $node1:4,$node2:4 \
 /opt/nccl-tests/build/all_reduce_perf_mpi \
 -b 16G -e 16G -f 2 -g 1 -c 1 -i 100
Warning: Permanently added '172.25.114.34' (ED25519) to the list of known hosts.
# nccl-tests version 2.17.6 nccl-headers=22809 nccl-library=22809
# Collective test starting: all_reduce_perf_mpi
# nThread 1 nGpus 1 minBytes 17179869184 maxBytes 17179869184 step: 2(factor) warmup iters: 1 iters: 20 agg iters: 1 validation: 1 graph: 0
#
# Using devices
# Rank 0 Group 0 Pid 375 on nccl-test-6k8rm device 0 [0008:01:00] NVIDIA GB200
# Rank 1 Group 0 Pid 376 on nccl-test-6k8rm device 1 [0009:01:00] NVIDIA GB200
# Rank 2 Group 0 Pid 377 on nccl-test-6k8rm device 2 [0018:01:00] NVIDIA GB200
# Rank 3 Group 0 Pid 378 on nccl-test-6k8rm device 3 [0019:01:00] NVIDIA GB200
# Rank 4 Group 0 Pid 459 on nccl-test-dbzk7 device 0 [0008:01:00] NVIDIA GB200
# Rank 5 Group 0 Pid 460 on nccl-test-dbzk7 device 1 [0009:01:00] NVIDIA GB200
# Rank 6 Group 0 Pid 461 on nccl-test-dbzk7 device 2 [0018:01:00] NVIDIA GB200
# Rank 7 Group 0 Pid 462 on nccl-test-dbzk7 device 3 [0019:01:00] NVIDIA GB200
#
# out-of-place in-place
# size count type redop root time algbw busbw #wrong time algbw busbw #wrong
# (B) (elements) (us) (GB/s) (GB/s) (us) (GB/s) (GB/s)
 17179869184 4294967296 float sum -1 355729 48.29 84.52 0 353612 48.58 85.02 0
# Out of bounds values : 0 OK
# Avg bus bandwidth : 84.7689
#
# Collective test concluded: all_reduce_perf_mpi

性能表现

  • ⚠️ 带宽:85GB/s(实测值 84.77GB/s,四舍五入)
  • ⚠️ 性能大幅降低,只有 MNNVL 的 1/9.5(811GB/s ÷ 85GB/s ≈ 9.5)

4.3 TCP 网络测试结果

增加以下参数:

  • -x NCCL_MNNVL_ENABLE=0 -x NCCL_IMEX_ENABLE=0:关闭 MNNVL

  • -x NCCL_IB_DISABLE=1:禁用 IB 网络

看看纯 TCP 网络下的性能。

1
2
3
4
5
6
7
8
mpirun --allow-run-as-root \
 -x PATH -x LD_LIBRARY_PATH \
 -x CUDA_HOME -x MPI_HOME -x NCCL_HOME \
 -x NCCL_MNNVL_ENABLE=0 -x NCCL_IMEX_ENABLE=0 \
 -x NCCL_IB_DISABLE=1 \
 -H $node1:4,$node2:4 \
 /opt/nccl-tests/build/all_reduce_perf_mpi \
 -b 1G -e 1G -f 2 -g 1 -c 1 -i 10

ps:因为纯 TCP 太慢,为了缩短测试时间,这里将消息大小从 16GB 降到 1GB,迭代次数从 100 次降到 10 次,因此只能粗略对比数量级差距,而不是与 MNNVL/IB 完全同条件对比。

测试结果

1
2
# 输出
# Avg bus bandwidth : 2.00 GB/s

性能表现

  • 带宽:2GB/s(Bus bandwidth,缩小消息大小后的结果)
  • ❌ 性能极低,只有 MNNVL 的 约 1/405(811GB/s ÷ 2GB/s ≈ 405),更适合作为“数量级感知”而非严谨对比

5. 📝 小结

通过本次 2 节点 8 GPU 的 nccl-tests,对 GB200 NVL72 系统中的不同互联方案有了更直观的量化认识:

  • MNNVL 性能是 InfiniBand 的约 9.5 倍:811GB/s vs 85GB/s(Bus bandwidth 指标),在相同 all-reduce 条件下差距非常明显
  • GB200 NVL72 完整系统包含 72 个 GB200 GPU + 36 个 Grace CPU:硬件规格被设计为大规模 AI 训练/推理的基础设施(本文只是其中 2 节点 8 GPU 的子规模测试)
  • 第五代 NVLink + MNNVL 技术:通过跨节点 GPU 直连,让多节点的通信性能更接近单节点 NVSwitch 域

但是,这么好的性能也是有代价的——GB200 NVL72 售价高达数百万美元 😱

看完测试数据,我只想说: 🤑 贫穷限制了我的想象力,原来"带宽不够"也能用钱解决。


6. 📚 参考资料


告别 TCP/IP 延迟:Kubernetes 中的 RDMA 高性能网络实战

2025-12-03 06:00:00

RDMA in K8s

GPU 算力拉满了,网络却成了瓶颈?在大模型训练和推理场景中,传统 TCP/IP 网络的延迟和 CPU 开销正在严重制约集群性能。RDMA 技术通过绕过内核直接访问内存,降低网络延迟。本文将手把手教你在 Kubernetes 中启用 RDMA 能力,从 Device Plugin 部署到性能验证,让你的 AI 集群真正发挥出硬件的全部潜力。

1. 基本概念

RDMA (Remote Direct Memory Access) 是一种高性能网络通信技术,允许网络中的计算机直接从另一台计算机的内存中读取或写入数据,而无需涉及两台计算机的操作系统内核或 CPU。

1.1 核心特性

  • Zero Copy (零拷贝):应用程序可以直接将数据传输到网络适配器,无需在内核空间和用户空间之间复制数据。
  • Kernel Bypass (内核旁路):应用程序可以直接向硬件发送命令,绕过操作系统内核,从而显著降低延迟。
  • CPU Offload (CPU 卸载):网络传输逻辑由网卡硬件处理,释放 CPU 资源用于计算任务。

1.2 网络协议

RDMA 支持多种网络传输协议,常见的包括:

  • InfiniBand (IB)
    • 专为高性能计算设计的网络标准。
    • 提供极高的吞吐量和极低的延迟。
    • 需要专用的 IB 交换机和网卡,成本较高,但性能最好。
    • 采用基于信用的流控机制,保证无损传输。
  • RoCE (RDMA over Converged Ethernet)
    • 允许在以太网上运行 RDMA 协议。
    • RoCE v1:基于以太网链路层协议,只能在同一个二层广播域内通信,不可路由。
    • RoCE v2:基于 UDP/IP 协议,可以跨三层网络路由,是目前数据中心的主流选择。需要交换机支持 PFC (Priority Flow Control) 和 ECN (Explicit Congestion Notification) 以保证无损传输。
  • iWARP (Internet Wide Area RDMA Protocol)
    • 基于 TCP/IP 协议栈实现 RDMA。
    • 利用 TCP 的可靠传输机制,对网络设备要求较低(普通以太网交换机即可)。
    • 由于 TCP 协议栈的复杂性,性能通常低于 IB 和 RoCE。

在 Kubernetes 环境中,我们通常关注如何将这些高性能网络能力暴露给 Pod 使用。

2. 背景与目标

随着大模型(LLM)训练和推理需求的爆发式增长,分布式计算集群对网络带宽和延迟提出了极高的要求。在传统的 TCP/IP 网络架构中,数据传输需要经过操作系统内核的多次上下文切换和内存拷贝,这在高带宽(如 100Gbps+)场景下会消耗大量的 CPU 资源,并引入不可忽视的延迟,成为制约 GPU 集群性能的瓶颈。

为了解决这一问题,RDMA (Remote Direct Memory Access) 技术被广泛应用。它允许应用程序直接访问远程节点的内存,绕过内核网络栈,从而实现高吞吐、低延迟和低 CPU 占用。

然而,Kubernetes 原生并不直接支持 RDMA 设备的管理和调度。为了在 Kubernetes 集群中充分利用 RDMA 硬件能力,我们需要解决以下关键问题:

  • 设备发现与管理:如何将物理节点的 RDMA 设备(如 IB 卡、RoCE 网卡)暴露给 Kubernetes 调度器。
  • Pod 配置:如何将 RDMA 设备挂载到 Pod 内部,使其能够使用 IB Verbs API 进行通信。
  • 网络方案选择:在复杂的网络环境中,是否需要引入 Multus CNI 等多网卡方案。
  • 性能验证:如何验证 RDMA 功能是否正常开启,并测试实际带宽是否达标。

本文将基于 Mellanox 提供的 k8s-rdma-shared-dev-plugin,详细介绍在 Kubernetes 中启用原生 RDMA 的完整流程与最佳实践。

3. 部署 device-plugin

推荐使用由 Mellanox 官方维护的 DevicePlugin:k8s-rdma-shared-device-plugin

3.1 驱动安装

确保节点已安装对应版本的 MOFED/OFED 驱动及用户态库(libibverbsrdmacm 等)。驱动安装方式本文不赘述。

3.2 获取部署模板

先 clone 项目:

1
git clone https://github.com/Mellanox/k8s-rdma-shared-dev-plugin.git

3.3 部署 Device Plugin

1
2
cd deployment/k8s/base
kubectl apply -k .

部署成功后 kube-system 中会出现 rdma-shared-dp-ds DaemonSet,它会在每个节点挂载 RDMA 设备并注册可调度资源。

1
2
3
root@GB200-POD2-F06-Node05:~# kubectl -n kube-system get po|grep rdma
rdma-shared-dp-ds-vrswv 1/1 Running 1 (27h ago) 31h
rdma-shared-dp-ds-zg242 1/1 Running 1 (27h ago) 31h

3.4 配置

Device Plugin 的核心是 ConfigMap。默认配置可参考:configmap.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: ConfigMap
metadata:
 name: rdma-devices
 namespace: kube-system
data:
 config.json: |
 {
 "periodicUpdateInterval": 300,
 "configList": [
 {
 "resourceName": "hca_shared_devices_a",
 "rdmaHcaMax": 1000,
 "selectors": {
 "vendors": ["15b3"],
 "deviceIDs": ["1021"],
 "ifNames": ["ibp3s0"]
 }
 }
 ]
 }

关键字段:

  • resourceName:Pod 申请资源写的名字,建议自定义如 rdma/ib
  • rdmaHcaMax:单卡允许共享的 Pod 数量,默认 1000
  • selectors:定义哪些物理网卡由 device plugin 接管,是配置的关键
    • vendors:设备厂商 ID,例如 Mellanox 为 15b3
    • deviceIDs:设备 ID,和 IB 卡有关,例如 ConnectX-7为 1021
    • ifNames:设备接口名称。

接下来介绍如何收集所需信息。

4. 采集 RDMA 设备信息

4.1 列出 IB 设备

1
2
ls /sys/class/infiniband
mlx5_0 mlx5_1 mlx5_2 mlx5_3 mlx5_4 mlx5_5 mlx5_6 mlx5_7

4.2 关联网络接口

1
2
3
4
5
6
7
8
9
ibdev2netdev
mlx5_0 port 1 ==> ibp3s0 (Up)
mlx5_1 port 1 ==> ibP2p3s0 (Up)
mlx5_2 port 1 ==> enP6p3s0f0np0 (Up)
mlx5_3 port 1 ==> bond0 (Up)
mlx5_4 port 1 ==> ibP16p3s0 (Up)
mlx5_5 port 1 ==> ibP18p3s0 (Up)
mlx5_6 port 1 ==> enP22p3s0f0np0 (Up)
mlx5_7 port 1 ==> bond0 (Up)

这里可以看到 IB 卡对应的接口名称,例如 mlx5_0 的接口名称为 ibp3s0

这里可以获取到配置中需要的 ifNames 参数。

4.3 获取 PCI 信息

mst status -v 可以列出所有 UP 状态网卡及其 PCI ID:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
root@GB200-POD2-F06-Node05:~/lixd/rdma# mst status -v
MST modules:
------------
 MST PCI module loaded
 MST PCI configuration module loaded
PCI devices:
------------
DEVICE_TYPE MST PCI RDMA NET NUMA
ConnectX7(rev:0) /dev/mst/mt4129_pciconf0 0000:03:00.0 mlx5_0 net-ibp3s0 0

ConnectX7(rev:0) /dev/mst/mt4129_pciconf1 0002:03:00.0 mlx5_1 net-ibP2p3s0 0

ConnectX7(rev:0) /dev/mst/mt4129_pciconf2 0010:03:00.0 mlx5_4 net-ibP16p3s0 1

ConnectX7(rev:0) /dev/mst/mt4129_pciconf3 0012:03:00.0 mlx5_5 net-ibP18p3s0 1

BlueField3(rev:1) /dev/mst/mt41692_pciconf0 0006:03:00.0 mlx5_2 net-enP6p3s0f0np0 0

BlueField3(rev:1) /dev/mst/mt41692_pciconf0.1 0006:03:00.1 mlx5_3 net-bond0 0

BlueField3(rev:1) /dev/mst/mt41692_pciconf1 0016:03:00.0 mlx5_6 net-enP22p3s0f0np0 1

BlueField3(rev:1) /dev/mst/mt41692_pciconf1.1 0016:03:00.1 mlx5_7 net-bond0 1

GB100(rev:0) /dev/mst/mt10561_pciconf0 0008:01:00.0 0

GB100(rev:0) /dev/mst/mt10561_pci_cr0 0008:01:00.0 0

GB100(rev:0) /dev/mst/mt10561_pciconf1 0009:01:00.0 0

GB100(rev:0) /dev/mst/mt10561_pci_cr1 0009:01:00.0 0

GB100(rev:0) /dev/mst/mt10561_pciconf2 0018:01:00.0 1

GB100(rev:0) /dev/mst/mt10561_pci_cr2 0018:01:00.0 1

GB100(rev:0) /dev/mst/mt10561_pciconf3 0019:01:00.0 1

GB100(rev:0) /dev/mst/mt10561_pci_cr3 0019:01:00.0 1

示例中 mlx5_0 的 PCI ID 为 0000:03:00.0

4.4 查询厂商/设备 ID

根据上一步获取到的 PCI ID 查询:

1
2
lspci -n | grep 0000:03:00.0
0000:03:00.0 0207: 15b3:1021
  • 15b3:Mellanox/NVIDIA 厂商 ID
  • 1021:具体设备 ID

这里可以获取到配置中需要的 vendors、deviceIDs 参数。

4.5 生成 selectors

汇总以上信息即可写出 selectors,示例如下(可配置多块网卡):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: ConfigMap
metadata:
 name: rdma-devices
 namespace: kube-system
data:
 config.json: |
 {
 "periodicUpdateInterval": 300,
 "configList": [
 {
 "resourceName": "hca_shared_devices_a",
 "rdmaHcaMax": 1000,
 "selectors": {
 "vendors": ["15b3"],
 "deviceIDs": ["1021"],
 "ifNames": ["ibp3s0"]
 }
 }
 ]
 }

更新 ConfigMap 后重启 Device Plugin:

1
kubectl -n kube-system rollout restart ds rdma-shared-dp-ds

5. 验证

5.1 Node 资源验证

1
kk describe node gb200-pod2-f06-node05 | grep Capacity -A 9

已经能在 Node 上看到 rdma/hca_shared_devices_a 资源了。

5.2 构建测试镜像

基于 Ubuntu 安装 ibv_devicesibv_devinfoibstatibstatus 等工具构建一个用于测试的镜像。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
FROM ubuntu:22.04

RUN apt-get update && \
 apt-get install -y --no-install-recommends \
 perftest \
 ibverbs-utils \
 ibverbs-providers \
 libibumad3 \
 libibverbs1 \
 librdmacm1 \
 infiniband-diags \
 iproute2 && \
 rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["sleep","infinity"]

构建镜像:

1
docker build -t rdma-test:v1 .

已经推送到 dockerhub:lixd96/rdma-test:latest

5.3 Pod 申请资源

启动一个 Pod 申请 RDMA 资源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
 name: mofed-test-pod
spec:
 restartPolicy: OnFailure
 containers:
 - image: rdma-test:v1
 name: mofed-test-ctr
 securityContext:
 capabilities:
 add: [ "IPC_LOCK" ]
 resources:
 limits:
 rdma/hca_shared_devices_a: 1
 command:
 - sh
 - -c
 - |
 ls -l /dev/infiniband /sys/class/infiniband /sys/class/net
 sleep 1000000

进入 Pod,使用 ibv_devicesibv_devinfo 查看 IB 卡信息:

1
2
ibv_devices
ibv_devinfo

可以看到 Pod 内已经识别到 mlx5_0 等 RDMA 设备。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@mofed-test-pod:/# ibv_devices
 device 	 node GUID
 ------ 	----------------
 mlx5_0 	7c8c090300c3cfee
root@mofed-test-pod:/# ibv_devinfo
hca_id:	mlx5_0
	transport:			InfiniBand (0)
	fw_ver:				28.44.2524
	node_guid:			7c8c:0903:00c3:cfee
	sys_image_guid:			7c8c:0903:00c3:cfee
	vendor_id:			0x02c9
	vendor_part_id:			4129
	hw_ver:				0x0
	board_id:			NVD0000000054
	phys_port_cnt:			1
		port:	1
			state:			PORT_ACTIVE (4)
			max_mtu:		4096 (5)
			active_mtu:		4096 (5)
			sm_lid:			1
			port_lid:		1336
			port_lmc:		0x00
			link_layer:		InfiniBand

5.4 性能验证

为了全面验证 RDMA 的性能,我们需要启动两个 Pod 进行带宽和延迟测试。

Pod 1 (Server)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
 name: rdma-server
spec:
 restartPolicy: OnFailure
 containers:
 - image: rdma-test:v1
 name: mofed-test-ctr
 securityContext:
 capabilities:
 add: [ "IPC_LOCK" ]
 resources:
 limits:
 rdma/hca_shared_devices_a: 1
 command:
 - sh
 - -c
 - |
 ls -l /dev/infiniband /sys/class/infiniband /sys/class/net
 sleep 1000000

Pod 2 (Client)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
 name: rdma-client
spec:
 restartPolicy: OnFailure
 containers:
 - image: rdma-test:v1
 name: mofed-test-ctr
 securityContext:
 capabilities:
 add: [ "IPC_LOCK" ]
 resources:
 limits:
 rdma/hca_shared_devices_a: 1
 command:
 - sh
 - -c
 - |
 ls -l /dev/infiniband /sys/class/infiniband /sys/class/net
 sleep 1000000

部署后查看 Pod 状态:

1
2
rdma-server 1/1 Running 0 7m18s 172.25.114.16 gb200-pod2-f06-node05
rdma-client 1/1 Running 0 5m24s 172.25.114.38 gb200-pod2-f06-node05

进入 Pod,使用 ibv_devicesibv_devinfo 查看 IB 卡信息:

1
2
ibv_devices
ibv_devinfo

可以看到 Pod 内已经识别到 mlx5_0 等 RDMA 设备。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@mofed-test-pod:/# ibv_devices
 device 	 node GUID
 ------ 	----------------
 mlx5_0 	7c8c090300c3cfee
root@mofed-test-pod:/# ibv_devinfo
hca_id:	mlx5_0
	transport:			InfiniBand (0)
	fw_ver:				28.44.2524
	node_guid:			7c8c:0903:00c3:cfee
	sys_image_guid:			7c8c:0903:00c3:cfee
	vendor_id:			0x02c9
	vendor_part_id:			4129
	hw_ver:				0x0
	board_id:			NVD0000000054
	phys_port_cnt:			1
		port:	1
			state:			PORT_ACTIVE (4)
			max_mtu:		4096 (5)
			active_mtu:		4096 (5)
			sm_lid:			1
			port_lid:		1336
			port_lmc:		0x00
			link_layer:		InfiniBand

5.4.1 带宽测试

使用 ib_write_bw 工具测试 RDMA 带宽性能。

进入 rdma-server 启动 server:

1
ib_write_bw -d mlx5_0 -i 1

进入 rdma-client 启动 Client:

1
2
# 其中 172.25.114.16 为 rdma-server 的 IP
ib_write_bw -d mlx5_0 -i 1 172.25.114.16

结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@rdma-server:/# ib_write_bw -d mlx5_0 -i 1

************************************
* Waiting for client to connect... *
************************************
---------------------------------------------------------------------------------------
 RDMA_Write BW Test
 Dual-port : OFF		Device : mlx5_0
 Number of qps : 1		Transport type : IB
 Connection type : RC		Using SRQ : OFF
 PCIe relax order: ON
 ibv_wr* API : ON
 CQ Moderation : 1
 Mtu : 4096[B]
 Link type : IB
 Max inline data : 0[B]
 rdma_cm QPs	 : OFF
 Data ex. method : Ethernet
---------------------------------------------------------------------------------------
 local address: LID 0x538 QPN 0x0171 PSN 0x7ef7d8 RKey 0x1fff00 VAddr 0x00aaaaaab00000
 remote address: LID 0x451 QPN 0x0165 PSN 0xac1c4 RKey 0x1fff00 VAddr 0x00aaaaaab80000
---------------------------------------------------------------------------------------
 #bytes #iterations BW peak[MB/sec] BW average[MB/sec] MsgRate[Mpps]
 65536 5000 46711.55 45733.81		 0.731741
---------------------------------------------------------------------------------------

测试结果为 46 GB/s 带宽,IB 卡为 400Gb/s,理论带宽为 50GB/s,该结果已经接近 IB 带宽上限,说明 RDMA 已能正常使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
root@rdma-server:/# ibstat
CA 'mlx5_0'
	CA type: MT4129
	Number of ports: 1
	Firmware version: 28.44.2524
	Hardware version: 0
	Node GUID: 0x7c8c090300c3a7ae
	System image GUID: 0x7c8c090300c3a7ae
	Port 1:
		State: Active
		Physical state: LinkUp
		Rate: 400
		Base lid: 1105
		LMC: 0
		SM lid: 1
		Capability mask: 0xa751e848
		Port GUID: 0x7c8c090300c3a7ae
		Link layer: InfiniBand

5.4.2 延迟测试

除了带宽,延迟也是 RDMA 的重要性能指标。使用 ib_write_lat 测试延迟:

在 rdma-server 中启动:

1
ib_write_lat -d mlx5_0 -i 1

在 rdma-client 中连接:

1
ib_write_lat -d mlx5_0 -i 1 172.25.114.16

测试结果示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---------------------------------------------------------------------------------------
 RDMA_Write Latency Test
 Dual-port : OFF		Device : mlx5_0
 Number of qps : 1		Transport type : IB
 Connection type : RC		Using SRQ : OFF
 PCIe relax order: OFF
 ibv_wr* API : ON
 TX depth : 1
 Mtu : 4096[B]
 Link type : IB
 Max inline data : 220[B]
 rdma_cm QPs	 : OFF
 Data ex. method : Ethernet
---------------------------------------------------------------------------------------
 local address: LID 0x451 QPN 0x0167 PSN 0xca7dd8 RKey 0x1ffbba VAddr 0x00aaaaaab00000
 remote address: LID 0x538 QPN 0x0173 PSN 0x66403f RKey 0x1fecab VAddr 0x00aaaaaab00000
---------------------------------------------------------------------------------------
 #bytes #iterations t_min[usec] t_max[usec] t_typical[usec] t_avg[usec] t_stdev[usec]
 2 100 2.62 3.84 2.74 2.73 0.04

测试结果显示,RDMA 的单向延迟约为 2.73 微秒,相比传统 TCP/IP 网络(通常 50-100 微秒),延迟降低了 20-40 倍。同时标准差仅为 0.04 微秒,说明延迟非常稳定,充分体现了 RDMA 的低延迟优势。

6. 是否需要 Multus CNI?

在 Kubernetes RDMA 方案中,是否引入 Multus CNI 取决于具体的应用场景和网络需求。

6.1 不需要 Multus CNI 的场景

如果你的需求满足以下条件,通常不需要 Multus CNI:

  1. 仅需要 IB Verbs API:应用(如 vLLM、NCCL)直接调用 RDMA 接口,不依赖 IP 协议进行通信(或者仅用于控制面)。
  2. 使用 Shared Device Plugin:本文介绍的 k8s-rdma-shared-dev-plugin 可以在不改变 Pod 网络命名空间结构的情况下,将 RDMA 设备文件(/dev/infiniband/*)暴露给 Pod。
  3. 扁平网络架构:RDMA 网卡和 Kubernetes 管理网卡处于同一网络平面,或者你能够接受 Pod 沿用 Host 网络的部分特性。

在这种模式下,Pod 依然使用默认 CNI(如 Calico、Flannel)分配的 IP 进行常规通信,同时拥有了访问 RDMA 硬件的能力。这是最简单、维护成本最低的方案。

6.2 需要 Multus CNI 的场景

如果存在以下需求,则建议引入 Multus CNI:

  1. 网络隔离:需要将 RDMA 流量与 Kubernetes 管理/业务流量完全物理隔离。
  2. 独立 IP 地址:需要为 RDMA 网卡分配独立的 IP 地址(例如使用 IPoIB 或 RoCEv2 路由模式),以便应用通过特定 IP 进行绑定。
  3. SR-IOV 场景:如果使用 sriov-cni 将物理网卡虚拟化为 VF 直接透传给 Pod,通常需要 Multus 来管理这个额外的网络接口。

6.3 结论

对于大多数基于 k8s-rdma-shared-dev-plugin 的分布式推理和训练任务,不需要 额外部署 Multus CNI。直接通过 Device Plugin 暴露设备,配合应用层的 RDMA 库即可实现高性能通信。引入 Multus 会显著增加网络配置的复杂度,应仅在确有必要(如强隔离、SR-IOV)时使用。

7. 小结

本文系统性地梳理了在 Kubernetes 集群中落地 RDMA 技术的完整路径。从基础概念的认知,到硬件环境的准备,再到 Device Plugin 的部署与配置,最后通过实际的性能压测验证了 RDMA 性能的优越性。

核心要点回顾:

  1. 技术价值:RDMA 通过零拷贝和内核旁路技术,解决了 TCP/IP 网络在高带宽场景下的 CPU 瓶颈和延迟问题,是释放 GPU 集群算力的关键基础设施。
  2. 部署策略:对于大多数 AI 推理和训练场景,使用 k8s-rdma-shared-dev-plugin 配合 Shared 模式是最轻量级的方案。它无需复杂的网络改造,即可让 Pod 获得原生 RDMA 能力。
  3. 架构决策:在引入 Multus CNI 之前应审慎评估。如果不需要物理隔离或独立 IP,直接复用 Host 网络栈(通过 Device Plugin 暴露设备)往往能带来更低的操作复杂度和更高的稳定性。

随着云原生 AI 的持续演进,底层网络设施的性能优化将变得愈发重要。掌握 RDMA 在 Kubernetes 中的配置与管理,将成为构建高性能 AI 平台的必备技能。未来,我们还可以进一步探索 GPUDirect RDMA、SR-IOV 等进阶技术,以应对更大规模、更低延迟的计算挑战。

深入 Longhorn 高可用:数据如何在节点故障时依然安全可靠

2025-11-05 06:00:00

Longhorn HA

在云原生时代,存储的高可用性是生产环境的生命线。一个设计良好的存储系统,不仅要能在节点故障时保证数据不丢失,还要做到业务无感知、自动恢复。

本文将深入剖析 Longhorn 的高可用机制:从两层架构设计到 iSCSI 协议的巧妙运用,从多副本写入到 Raft 共识算法,再到自动故障恢复流程。通过理论分析和实战演示,带你彻底理解 Longhorn 如何在分布式环境中实现数据的高可用性

📚 系列文章:本文是 Longhorn 系列的第二篇,重点剖析高可用原理。如果你还不了解 Longhorn 的基本概念和部署方法,建议先阅读上一篇:云原生分布式存储系统:Longhorn 初体验

1. 架构设计

要理解 Longhorn 的高可用机制,首先需要了解其架构设计。好的架构是高可用的基础。

1.1 两层架构

Longhorn 采用了清晰的分层架构设计:

Longhorn Arch

Longhorn 设计为两层结构:

数据平面(Data Plane)

  • Longhorn Engine:存储控制器,每个卷(Volume)一个独立的 Engine
  • Replica:数据副本,负责将数据持久化到物理磁盘

控制平面(Control Plane)

  • Longhorn Manager:核心控制器,负责调度、管理和监控

这种设计的优势在于:

  • 故障隔离:每个 Volume 独立的 Engine,一个卷的问题不会影响其他卷
  • 灵活调度:Manager 可以根据节点资源动态调度 Engine 和 Replica
  • 易于扩展:数据平面和控制平面解耦,便于独立扩展

1.2 核心组件

在 Kubernetes 中,Longhorn 的核心组件包括:

Longhorn Engine

  • 作为 Pod 运行在需要使用卷的节点上
  • 每个 Volume 对应一个独立的 Engine 实例
  • 负责处理所有 I/O 操作,协调多副本数据同步

Longhorn Replica

  • 作为独立进程运行,存储卷数据的完整副本
  • 默认创建 3 个副本,分布在不同节点上
  • 负责将数据持久化到节点的物理磁盘(默认路径 /var/lib/longhorn

Longhorn Manager

  • 以 DaemonSet 形式运行在每个节点上
  • 负责卷的创建、删除、调度和健康监控
  • 提供 API 供 CSI Driver 调用

2. 工作流程详解

了解了 Longhorn 的架构设计后,让我们通过一个完整的实例,从 PVC 创建到 Pod 使用,详细了解 Longhorn 的工作流程。

2.1 StorageClass

Longhorn 部署完成后,会自动创建两个 StorageClass:

1
kubectl get sc

输出示例:

1
2
3
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
longhorn (default) driver.longhorn.io Delete Immediate true 15h
longhorn-static driver.longhorn.io Delete Immediate true 15h

2.2 创建测试 Pod

让我们通过一个实际例子来演示 Longhorn 的完整工作流程。创建一个使用 Longhorn PVC 的 MariaDB Pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: mariadb-pvc
 namespace: default
spec:
 storageClassName: longhorn  # 使用 Longhorn StorageClass
 accessModes:
 - ReadWriteOnce
 resources:
 requests:
 storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
 name: mariadb-pod
 namespace: default
spec:
 containers:
 - name: mariadb
 image: bitnami/mariadb:latest
 env:
 - name: MYSQL_ROOT_PASSWORD
 value: "your-password"
 ports:
 - containerPort: 3306
 volumeMounts:
 - name: mariadb-data
 mountPath: /bitnami/mariadb
 volumes:
 - name: mariadb-data
 persistentVolumeClaim:
 claimName: mariadb-pvc

2.3 卷创建流程(Provisioning)

当创建 PVC 时,会触发以下流程:

Step 1:PVC 创建

  • 用户创建 PVC,指定使用 longhorn StorageClass

Step 2:CSI Driver 响应

  • Longhorn CSI Driver 监听到 PVC 创建事件
  • 调用 Longhorn Manager 的 API,请求创建 Longhorn Volume

Step 3:Volume CRD 创建

  • Longhorn Manager 创建对应的 Volume CRD 对象
  • 持续监听 API Server 的响应
1
2
# 查看创建的 Volume CRD
kubectl -n longhorn-system get volumes

输出示例:

1
2
NAME DATA ENGINE STATE ROBUSTNESS SCHEDULED SIZE NODE AGE
pvc-7aa74a8d-fa4c-4668-b11c-fc8a827034db v1 attached healthy 5368709120 node-1 2m

Step 4:Engine 和 Replica 调度

  • Longhorn Manager 监听到 Volume CRD 创建
  • 在 Pod 所在节点创建 Longhorn Engine(专用存储控制器)
  • 在多个不同节点创建指定数量的 Replica(默认 3 个)

2.4 卷挂载流程(Attach & Mount)

块设备的挂载分为两个阶段:

阶段 1:Attach(附加到节点)

当 Pod 被调度到某个节点时:

  1. 该节点的 Kubelet 通过 Longhorn CSI Driver 调用 Longhorn Manager
  2. Manager 确保 Volume 已准备就绪
  3. CSI Driver 通过 iSCSI 协议在节点上模拟出一个块设备

查看模拟的块设备:

1
ll /dev/longhorn/

输出示例:

1
2
3
4
total 0
drwxr-xr-x 2 root root 60 Oct 15 10:35 ./
drwxr-xr-x 20 root root 4380 Oct 15 15:36 ../
brw-rw---- 1 root disk 8, 16 Oct 15 10:35 pvc-a63baecf-4a01-480e-8c3d-6dac2a496265

注意文件类型为 b(block),表示这是一个块设备

阶段 2:Mount(挂载到 Pod)

Kubelet 将该块设备格式化为文件系统(如 ext4、xfs),并挂载到 Pod 的指定路径。

小结: 通过 Attach 和 Mount 两个阶段,Longhorn 成功将卷挂载到 Pod 中。其中,iSCSI 协议模拟的块设备是实现高可用的关键,它使得 Longhorn 能够拦截所有 I/O 操作,从而实现多副本同步。接下来我们将深入剖析这个高可用机制。

3. 高可用实现原理

3.1 数据写入流程

这是 Longhorn 高可用的核心机制。当应用写入数据时,经历以下流程:

核心思想: 通过 iSCSI 协议模拟块设备,将所有 I/O 操作拦截并转发给 Engine,由 Engine 使用 Raft 算法同步到多个 Replica,实现数据高可用。

longhorn-iscsi-work-flow

详细步骤:

Step 1:应用写入

  • Pod 中的应用(如 MariaDB)向挂载的目录写入数据
  • 例如:写入 /bitnami/mariadb/data.db

Step 2:文件系统层

  • 数据经过文件系统(ext4/xfs)处理

Step 3:块设备层

  • 文件系统将数据写入块设备 /dev/longhorn/xxx

Step 4:iSCSI 协议转发

  • 因为这是一个模拟的块设备,写入请求不会直接到达磁盘
  • 而是通过 iSCSI 协议转发给 Longhorn Engine

Step 5:Engine 多副本复制

  • Engine 接收到写入请求
  • 使用 Raft 共识算法将数据同步复制到所有 Replica
  • 必须获得多数副本(3个中的2个)的确认后,才返回写入成功

Step 6:Replica 持久化

  • 每个 Replica 将数据持久化到其所在节点的物理磁盘
  • 默认路径:/var/lib/longhorn/replicas/<volume-name>-<id>/

关键要点:

  • 📍 iSCSI 拦截:通过 iSCSI 协议模拟块设备,实现数据拦截和转发
  • 🔄 Raft 共识:使用 Raft 算法确保多副本强一致性
  • 多数派确认:需要 2/3 副本确认才返回写入成功(3副本中至少2个)
  • 🛡️ 分布式部署:三副本分布在不同节点,防止单点故障

3.2 关键技术点

从上面的数据写入流程图可以看出,Longhorn 的高可用实现依赖以下关键技术:

1) iSCSI 协议的巧妙运用

与传统存储(如 Ceph)不同,Longhorn 通过 iSCSI 协议模拟了一个块设备,从而能够:

  • 拦截所有 I/O 操作:应用的写入不会直接到达磁盘
  • 统一数据流向:所有数据必须经过 Engine 处理
  • 实现多副本同步:Engine 可以控制数据的复制流程

2) Raft 共识算法

Longhorn 使用 Raft 算法确保数据一致性:

  • 强一致性:写入必须获得多数副本确认
  • 容错能力:N 个副本可以容忍 (N-1)/2 个副本故障
  • 自动选主:Engine 故障时可以自动选举新的 Leader

3) 数据副本分布

1
2
# 查看副本分布
kubectl -n longhorn-system get replicas

Longhorn Manager 会智能调度,确保:

  • 同一卷的副本分布在不同节点上
  • 避免单点故障风险
  • 考虑节点的资源使用情况

4. 故障恢复机制

通过多副本和 Raft 共识算法,Longhorn 实现了数据的高可用性。但真正考验存储系统可靠性的,是当节点故障时的自动恢复能力。本章将详细介绍 Longhorn 如何检测故障、降级运行,并自动重建副本。

4.1 故障检测

健康监控

  • Longhorn Manager 持续监控所有节点和组件的健康状态
  • Engine 与所有 Replica 保持心跳检测
  • 当节点宕机或网络分区时,快速检测到连接丢失

4.2 降级运行

系统降级

  • 发现副本故障后,卷状态变为 Degraded(降级)
  • 但卷仍然继续可用,业务不中断
  • 原因:Raft 算法允许多数副本(N/2 + 1)存活时继续工作

举例:3 副本的卷

  • 1 个副本故障:剩余 2 个副本形成多数派,继续服务
  • 2 个副本故障:只剩 1 个副本,无法形成多数派,只读模式

4.3 自动重建

重建流程

Step 1:检测故障

1
2
# 查看卷状态变为 Degraded
kubectl -n longhorn-system get volumes

Step 2:调度新副本

  • Longhorn Manager 在健康节点上调度新 Replica
  • 选择资源充足且负载较低的节点

Step 3:数据同步

  • 新 Replica 启动后,从健康副本同步数据
  • 增量同步:只同步差异数据,加快恢复速度
  • 同步过程中,卷保持可用状态

Step 4:恢复健康

  • 数据同步完成后,新副本加入 Raft 组
  • 副本数恢复到预设值(如 3 个)
  • 卷状态从 Degraded 恢复为 Healthy

查看恢复进度:

1
2
3
# 通过 Web UI 查看
# 或使用命令行
kubectl -n longhorn-system describe volume <volume-name>

4.4 故障恢复示例

模拟节点故障:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. 查看副本分布
kubectl -n longhorn-system get replicas -o wide

# 2. 关闭一个节点
# (例如:node-2)

# 3. 观察卷状态变化
kubectl -n longhorn-system get volumes -w

# 4. 查看新副本创建
kubectl -n longhorn-system get replicas -w

恢复时间线:

  • 0s:节点故障
  • ~10s:Longhorn 检测到故障,卷状态变为 Degraded
  • ~30s:新副本开始在其他节点上创建
  • ~5min:数据同步完成(取决于数据量)
  • 完成:卷状态恢复为 Healthy

5. 高可用优势

通过前面的分析,我们深入了解了 Longhorn 的高可用实现机制。那么,与其他存储方案相比,Longhorn 的优势在哪里?它适合什么样的场景?本章将进行详细对比分析。

5.1 与其他方案对比

特性 NFS Ceph Longhorn
高可用性 ❌ 单点故障 ✅ 高可用 ✅ 高可用
自动故障恢复 ❌ 需手动处理 ✅ 自动恢复 ✅ 自动恢复
部署复杂度 ✅ 简单 ❌ 复杂 ✅ 简单
资源占用 ✅ 低 ❌ 高 ✅ 中等
数据一致性 ⚠️ 弱一致性 ✅ 强一致性 ✅ 强一致性(Raft)
故障容忍度 0 N/2 N/2

5.2 核心优势总结

1. 微服务架构

  • 每个 Volume 独立的 Engine,故障隔离
  • 便于扩展和维护

2. 强一致性保证

  • Raft 共识算法确保数据一致性
  • 多数派写入机制

3. 自动故障恢复

  • 无需人工干预
  • 业务无感知
  • 快速数据重建

4. 云原生设计

  • 深度集成 Kubernetes
  • 使用 CRD 和 Operator 模式
  • 声明式 API

6. 小结

本文从架构、原理到实战,全面剖析了 Longhorn 如何实现云原生存储的高可用性。通过数据写入流程图和故障恢复示例,我们深入理解了其背后的核心技术机制:

架构层面

  • 采用两层架构设计(数据平面 + 控制平面),职责清晰
  • 每个 Volume 独立的 Engine,实现故障隔离
  • 多副本设计,默认 3 副本分布在不同节点

技术实现

  • 巧妙运用 iSCSI 协议模拟块设备,拦截所有 I/O 操作
  • 使用 Raft 共识算法确保多副本数据强一致性
  • 写入需要多数副本确认(3个中的2个),平衡性能与可靠性

高可用保证

  • 数据多副本:默认 3 副本,可容忍 1 个节点故障
  • 自动故障检测:持续监控组件健康状态
  • 降级运行:副本故障时卷仍可用,业务不中断
  • 自动恢复:在健康节点自动重建副本,增量同步数据

适用场景

  • 中小规模生产环境:比 Ceph 轻量,比 NFS 可靠
  • 对数据可靠性要求高:强一致性保证
  • 希望运维简单:自动故障恢复,无需人工干预

总结

Longhorn 通过精心的架构设计和成熟的分布式算法,在简单性、可靠性和性能之间取得了完美平衡。它的高可用机制不仅理论上完善(Raft 共识、多副本复制),更在实践中证明了可靠性(自动故障恢复、降级运行)。

对于追求"简单可靠"的中小规模 Kubernetes 集群来说,Longhorn 无疑是一个"刚刚好"的云原生存储解决方案——既不会像 NFS 那样让你担心数据安全,也不会像 Ceph 那样让你头疼运维复杂度

7. 参考资料

云原生分布式存储系统:Longhorn 初体验

2025-10-15 06:00:00

Longhorn

本文将介绍云原生分布式存储系统 Longhorn,包括其核心概念、架构原理,以及如何在 Kubernetes 集群中部署和使用。

1. 概述

1.1 Longhorn 简介

Longhorn 是一个开源的、云原生的分布式块存储系统,专为 Kubernetes 环境设计。它最初由 Rancher Labs 开发,现已成为云原生计算基金会(CNCF)的孵化项目。

主要特点:

  • 轻量级部署,易于安装和使用
  • 分布式架构,支持数据高可用
  • 云原生设计,完美集成 Kubernetes
  • 支持快照、备份和恢复
  • 提供友好的 Web UI 管理界面

1.2 适用场景

Longhorn 适用于以下场景:

  • 轻量级生产环境:相比 Ceph 等重量级方案更易部署和维护
  • 高可用需求:相比单点 NFS 提供更好的可靠性
  • 中小规模集群:在存储性能和运维复杂度之间取得平衡

2. 核心架构

2.1 整体架构

Longhorn Arch

Longhorn 的核心设计理念是为每个 Volume 创建独立的 Engine 进行管理,Engine 负责将 Volume 复制到多个节点,以实现高可用存储。

为什么为每个 Volume 创建一个独立的 Engine?

这样可以保证当某个 Engine 出现问题时,其他 Volume 不受影响,实现了故障隔离。

2.2 核心组件

Longhorn 主要包含以下核心组件:

控制平面组件:

  • longhorn-manager:核心控制器(DaemonSet),运行在每个节点,负责卷的创建、删除、调度等管理工作
  • longhorn-ui:Web 管理界面,提供可视化的存储管理功能

数据平面组件:

  • instance-manager:每个节点运行一个,管理该节点上所有的 Engine 和 Replica 实例
  • engine-image:Longhorn 引擎的可执行镜像

CSI 组件(Kubernetes 官方):

  • csi-provisioner:负责动态创建 PV
  • csi-attacher:负责将卷附加到节点
  • csi-resizer:负责卷的扩容
  • csi-snapshotter:负责快照功能
  • csi-plugin:CSI 驱动插件(DaemonSet),每个节点运行一个

2.3 数据存储与高可用

数据存放位置:Longhorn 使用集群节点的本地磁盘(默认 /var/lib/longhorn)组成分布式存储池,数据副本分布在这些节点的磁盘上。

1
2
3
root@node1:/var/lib/longhorn/replicas# ls
pvc-19d50ca0-780d-4073-bb72-36a7ffe9e3e2-1084ec0e
pvc-4f0bf5ea-0dc8-49be-8a3f-559f95416092-4c99b141

高可用原理

  • 默认为每个卷创建 3 个副本(可配置)
  • Longhorn Manager 自动将副本调度到不同节点,防止单点故障
  • 当节点或磁盘故障导致副本失效时,自动在健康节点上重建副本

3. 环境准备

3.1 系统要求

安装 Longhorn 前需要确保环境满足以下要求:

完整要求参考:https://longhorn.io/docs/1.7.3/deploy/install/#installation-requirements

  • Kubernetes >= 1.21
  • 所有节点需要:
    • 安装并运行 open-iscsi
    • 安装 NFSv4 client
    • 主机文件系统使用 ext4 或 XFS
    • 安装 bashcurlfindmntgrepawkblkidlsblk 等命令
    • 开启 Mount propagation

3.2 依赖安装说明

Longhorn 需要以下依赖:

1) open-iscsi

  • 用途:提供 iSCSI 协议支持,Longhorn 通过 iSCSI 将卷附加到节点
  • 安装:apt-get install open-iscsi(Ubuntu/Debian)

2) NFS 客户端

  • 用途:Longhorn 的备份功能需要 NFSv4/v4.1/v4.2,RWX 卷需要 NFSv4.1
  • 安装:apt-get install nfs-common(Ubuntu/Debian)

3) Mount Propagation

  • 用途:允许容器挂载的卷共享给同一 Pod 或节点的其他容器
  • Kubernetes 默认已开启,无需配置

注意:第4章会介绍使用 DaemonSet 自动安装这些依赖的方式(推荐),这里仅作说明。如需手动安装,参考 官方文档

4. 部署安装

4.1 环境初始化

使用 DaemonSet 自动安装依赖(推荐)

官方提供了 DaemonSet 方式自动在所有节点安装依赖,无需手动登录每个节点操作:

1
2
3
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.9.1/deploy/prerequisite/longhorn-iscsi-installation.yaml

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.9.1/deploy/prerequisite/longhorn-nfs-installation.yaml

检查安装状态

使用以下脚本检查初始化是否成功:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
echo "正在检查 Longhorn 安装 Pod 的日志..."
echo "=================================="

# 检查 iscsi-installation
echo "检查 ISCSI 安装:"
pods=$(kubectl -n longhorn-system get pods -l app=longhorn-iscsi-installation -o name)
for pod in $pods; do
 if kubectl -n longhorn-system logs $pod -c iscsi-installation | grep -q "install successfully"; then
 echo "✅ $pod: ISCSI 初始化成功"
 else
 echo "❌ $pod: ISCSI 初始化未成功或日志未找到成功信息"
 fi
done

echo "----------------------------------"

# 检查 nfs-installation
echo "检查 NFS 安装:"
pods=$(kubectl -n longhorn-system get pods -l app=longhorn-nfs-installation -o name)
for pod in $pods; do
 if kubectl -n longhorn-system logs $pod -c nfs-installation | grep -q "install successfully"; then
 echo "✅ $pod: NFS 初始化成功"
 else
 echo "❌ $pod: NFS 初始化未成功或日志未找到成功信息"
 fi
done

echo "=================================="
echo "检查完成。"

日志中输出 iscsi install successfullynfs install successfully 表示安装成功。

4.2 使用 Helm 安装 Longhorn

推荐使用 Helm 进行安装:

1
2
3
4
5
6
7
8
9
# 添加 Helm 仓库
helm repo add longhorn https://charts.longhorn.io
helm repo update

# 安装 Longhorn
helm install longhorn longhorn/longhorn \
 --namespace longhorn-system \
 --create-namespace \
 --version 1.7.3

注意:1.8.2 和 1.9.1 版本需要 Kubernetes 1.25+,1.7.3 只需要 1.21+

4.3 验证安装

1) 检查 Pod 状态

1
kubectl -n longhorn-system get pod

正常情况下,所有 Pod 应处于 Running 状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
NAME READY STATUS RESTARTS AGE
csi-attacher-6bc8956d47-67fp6 1/1 Running 0 52m
csi-attacher-6bc8956d47-nb869 1/1 Running 0 52m
csi-attacher-6bc8956d47-tnchc 1/1 Running 0 52m
csi-provisioner-b65c98945-5knc2 1/1 Running 0 52m
csi-provisioner-b65c98945-897p7 1/1 Running 0 52m
csi-provisioner-b65c98945-q9pf5 1/1 Running 0 52m
csi-resizer-7cd74679f-7wn4v 1/1 Running 0 52m
csi-resizer-7cd74679f-p2m2h 1/1 Running 0 52m
csi-resizer-7cd74679f-qkdl6 1/1 Running 0 52m
csi-snapshotter-74db7b474-6n2tm 1/1 Running 0 52m
csi-snapshotter-74db7b474-cwbjd 1/1 Running 0 52m
csi-snapshotter-74db7b474-cx5fs 1/1 Running 0 52m
engine-image-ei-d91f5974-7xlhz 1/1 Running 0 53m
instance-manager-290ed8a6055485f167480d21dec82fb9 1/1 Running 0 52m
longhorn-csi-plugin-94xjf 3/3 Running 1 52m
longhorn-driver-deployer-6fc5d44f84-nmqdl 1/1 Running 0 53m
longhorn-manager-rpkbn 2/2 Running 0 53m
longhorn-ui-5b45479667-8dmb2 1/1 Running 0 53m

5. Demo

5.1 创建 PVC 并使用

部署完成后,默认会创建两个 StorageClass:

1
kubectl get sc

输出示例:

1
2
3
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
longhorn (default) driver.longhorn.io Delete Immediate true 53m
longhorn-static driver.longhorn.io Delete Immediate true 53m

1) 创建 PVC

使用 Longhorn 存储类创建 PVC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: longhorn-pvc
spec:
 accessModes:
 - ReadWriteOnce
 storageClassName: longhorn
 resources:
 requests:
 storage: 5Gi

2) 创建 Pod 使用 PVC

创建一个 Pod 挂载使用该 PVC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
 name: test-longhorn
spec:
 containers:
 - name: nginx
 image: nginx:latest
 volumeMounts:
 - name: longhorn-vol
 mountPath: /data
 volumes:
 - name: longhorn-vol
 persistentVolumeClaim:
 claimName: longhorn-pvc

部署后可以进入 Pod 验证挂载:

1
2
3
4
5
6
7
8
9
# 进入 Pod
kubectl exec -it test-longhorn -- bash

# 查看挂载点
df -h | grep /data

# 写入测试数据
echo "Hello Longhorn" > /data/test.txt
cat /data/test.txt

5.2 访问 Web UI

Longhorn 提供了 Web UI 用于管理和监控。

修改 Service 为 NodePort 类型

默认情况下,longhorn-frontend Service 是 ClusterIP 类型,需要修改为 NodePort:

1
kubectl -n longhorn-system patch svc longhorn-frontend -p '{"spec":{"type":"NodePort"}}'

查看分配的端口:

1
kubectl -n longhorn-system get svc longhorn-frontend

输出示例:

1
2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
longhorn-frontend NodePort 10.96.123.456 <none> 80:30123/TCP 1h

通过任意节点 IP + NodePort 访问 Web UI:http://<node-ip>:30123

Web UI 界面

Longhorn Web UI

在 Web UI 中可以:

  • 查看和管理卷(Volume)
  • 查看节点和磁盘状态
  • 创建快照和备份
  • 监控存储使用情况

6. 小结

本文全面介绍了 Longhorn 这款云原生分布式存储系统,从架构原理到实际部署使用,涵盖了以下内容:

  • 核心架构:采用微服务架构,每个卷独立运行 Engine 和 Replica,实现故障隔离;详细说明了各核心组件的作用
  • 环境准备:系统要求和依赖说明(open-iscsi、NFS 客户端等)
  • 部署安装:通过 DaemonSet 自动安装依赖,使用 Helm 快速部署 Longhorn
  • 使用配置:创建 PVC、配置副本数、PVC 扩容、访问 Web UI 等实际操作
  • 常见问题:Pod 启动失败、数据库初始化、节点故障恢复、卸载清理等常见问题的解决方案

存储方案选型建议

在 Kubernetes 环境中,不同场景适合不同的存储方案:

测试/开发环境 → NFS

  • ✅ 部署最简单,一台 NFS Server 即可
  • ✅ 配置方便,无需额外组件
  • ❌ 单点故障风险,可靠性较低
  • ❌ 性能一般,不适合生产环境

大型生产环境 → Ceph

  • ✅ 高性能、高可用、高扩展性
  • ✅ 功能强大,支持对象存储、块存储、文件存储
  • ❌ 架构复杂,运维成本高
  • ❌ 资源占用大,需要专业团队维护
  • ❌ 对于小规模集群过于"重量级"

中小规模生产环境 → Longhorn

  • ✅ 比 Ceph 轻量级,部署运维简单
  • ✅ 比 NFS 可靠,支持副本和高可用
  • ✅ 云原生设计,与 Kubernetes 深度集成
  • ✅ Web UI 友好,易于管理和监控
  • ⚠️ 性能不如 Ceph,但满足大多数场景需求

总结:如果你的生产环境规模不大(几十个节点以内),又希望在存储可靠性和运维复杂度之间取得平衡,Longhorn 是最佳选择。它既保证了生产级的可靠性,又避免了 Ceph 的复杂度,是"刚刚好"的分布式存储方案。

7. 参考资料