GPU 集群 IaC:Terraform + Ansible 部署自动化

AI 加速器系列 · 第 6 篇

100 台 GPU 服务器到货。每台要装驱动、CUDA、Container Runtime、GPU Operator、配置 NCCL 环境变量。手动来?一台 30 分钟,100 台 = 一周。核心思想:正确的事情做一次,用代码重复 100 遍


1. 手动部署有多痛苦

一台裸 GPU 节点变成 K8s 可调度工作节点的依赖链:

markdown 复制代码
Kernel  →  GPU Driver  →  CUDA Toolkit  →  Container Runtime
                                              │
                    ┌─────────────────────────┘
                    ▼
            GPU Operator  →  Workload (训练/推理 Pod)
                    │
                    ├── NCCL 环境变量
                    ├── InfiniBand / RoCE 网卡
                    └── 节点标签 & 污点

每一层都可能断:

  • Driver vs Kernel 不兼容 :NVIDIA 550.x 要求 Kernel 5.15+,镜像跑着 5.19 HWE。Nouveau 没禁用干净,跟闭源驱动抢 GPU 控制权------nvidia-smi 直接报错。
  • CUDA 版本错配 :训练框架要 CUDA 12.1,你装了 11.8。容器 mount 的 cuDNN 跟编译时不一致------CUDNN_STATUS_VERSION_MISMATCH
  • NCCL 环境变量就错了一个值NCCL_SOCKET_IFNAME 指向管理网卡而不是 RoCE 网卡。64 卡 AllReduce 跑在 1Gbps 管理网上------30 秒一圈的同步变成 15 分钟。

中后期排错返工消耗是初始部署的 3 倍------要在 100 台里找出"哪几台配错了、哪一步配错了"。IaC 应对:一次性把依赖链写进代码,测试通过后 100 台 = ansible-playbook -i inventory deploy.yml --forks 100,30 分钟全部就绪。


2. Terraform:基础设施声明式管理

Terraform 解决"云资源从无到有"。.tf 文件里声明你要什么,它算出怎么变过去。

GPU 集群三大资源域

scss 复制代码
┌─────────────────────────────────────────────────────┐
│                  Terraform State                     │
├───────────────┬──────────────────┬──────────────────┤
│   Compute     │     Network      │    Storage       │
├───────────────┼──────────────────┼──────────────────┤
│ GPU 实例      │ 低延迟 VPC       │ 并行文件系统      │
│ 自动扩缩组    │ Placement Group  │ (Lustre/GPFS)    │
│ Spot/抢占式   │ 高带宽子网       │ 对象存储(模型)    │
│               │ EFA/RDMA 网卡    │                  │
└───────────────┴──────────────────┴──────────────────┘

Compute :H100 集群决策------p5.48xlarge(8xH100 NVSwitch)+ Spot 实例(省 70%)+ Cluster 放置组(同一机架,最小 NCCL 延迟)。

Network :多节点 GPU 训练瓶颈不在计算------在网络。Placement Group strategy="cluster"(NCCL Ring AllReduce 延迟跟跳数直接相关),弹性网卡做管理/数据分离,安全组开放 NCCL 动态端口。

Storage :训练数据驻留并行文件系统(Lustre,100Gbps+ 带宽),checkpoint 写对象存储。创建 FSx for Lustre 时 per_unit_storage_throughput >= 1000------默认 125 MB/s/TiB 远远不够。

Terraform HCL 核心代码

hcl 复制代码
# placement.tf ------ 最关键
resource "aws_placement_group" "gpu_cluster" {
  name     = "h100-training-pg"
  strategy = "cluster"               # 物理聚集,最小化 NCCL 延迟
}

# instances.tf
resource "aws_instance" "gpu_node" {
  count           = var.instance_count
  ami             = data.aws_ami.gpu_ubuntu.id
  instance_type   = "p5.48xlarge"    # 8 × H100 80GB
  placement_group = aws_placement_group.gpu_cluster.name

  instance_market_options {
    market_type = "spot"             # 训练支持 checkpoint 就用它,省 70%
    spot_options { instance_interruption_behavior = "terminate" }
  }

  dynamic "network_interface" {
    for_each = var.enable_efa ? [1] : []
    content {
      network_interface_id = aws_network_interface.efa[count.index].id
      device_index         = 2       # 数据面网卡------独立于管理网
    }
  }

  root_block_device {
    volume_type = "gp3"
    volume_size = 500
    iops        = 16000
  }

  tags = { Name = "gpu-node-${count.index}", ManagedBy = "terraform" }
}

# storage.tf
resource "aws_fsx_lustre_file_system" "training_data" {
  storage_capacity            = 4800
  deployment_type             = "PERSISTENT_2"
  per_unit_storage_throughput = 1000  # 高吞吐不能省
  subnet_ids                  = [aws_subnet.compute.id]
}

output "gpu_private_ips" {
  value = aws_instance.gpu_node[*].private_ip  # 交给 Ansible 的桥梁
}

Terraform 的价值不在于"能创建资源"------控制台也能点。而在于写一次模块,16 台和 16000 台的差别只是 -var instance_count=16000 ,并且 plan 让你变更前就知道什么会被创建/修改/销毁。


3. Ansible:GPU 节点的终极配置手册

Ansible 的优势是幂等性:playbook 重复执行,已配好的步骤自动跳过。

Playbook 结构(严格依赖顺序)

vbnet 复制代码
00-prerequisites.yml    → 内核参数、禁用 Nouveau、GCC/Make
01-nvidia-driver.yml    → NVIDIA 驱动 + CUDA Toolkit
02-container-runtime.yml → nvidia-container-toolkit + containerd
03-gpu-operator.yml     → Helm 部署 GPU Operator
04-network-tuning.yml   → NCCL 环境变量、RoCE/IB 绑卡
05-validation.yml       → GPU Burn + NCCL-Test

Step 0:前置

yaml 复制代码
- hosts: gpu_nodes
  become: yes
  tasks:
    - lineinfile:
        path: /etc/modprobe.d/blacklist-nouveau.conf
        line: "blacklist nouveau"
        create: yes
    - copy:
        dest: /etc/sysctl.d/99-gpu.conf
        content: |
          vm.max_map_count = 2147483647
          kernel.numa_balancing = 0
    - apt:
        name: [build-essential, gcc, make, "linux-headers-{{ ansible_kernel }}"]
    - reboot:
        reboot_timeout: 300

vm.max_map_count:GPU 应用创建大量内存映射;numa_balancing=0:关闭自动 NUMA 页面迁移------避免随机延迟抖动。

Step 1:驱动 + CUDA

yaml 复制代码
- hosts: gpu_nodes
  become: yes
  tasks:
    - apt_repository:
        repo: "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64 /"
    - apt:
        name: [nvidia-driver-550, cuda-toolkit-12-4]
    - command: nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
      register: gpu_check

驱动版本原则:不追最新,追最稳定。数据中心驱动分 production branch(550.x)和 new feature branch(560.x)。训练集群用 production。

Step 2:Container Runtime

yaml 复制代码
- hosts: gpu_nodes
  become: yes
  tasks:
    - apt:
        name: nvidia-container-toolkit
    - shell: nvidia-ctk runtime configure --runtime=containerd
      args:
        creates: /etc/containerd/config.toml
    - systemd:
        name: containerd
        state: restarted
        enabled: yes

nvidia-container-toolkit 把宿主机 GPU 驱动和 CUDA 库通过 LD_PRELOAD 和 cgroup hooks "透传"进容器------不需要每个镜像打包 2GB CUDA 运行时。

Step 3:GPU Operator

yaml 复制代码
- hosts: gpu_nodes[0]
  tasks:
    - kubernetes.core.helm:
        name: gpu-operator
        chart_ref: nvidia/gpu-operator
        chart_version: "v24.3.0"
        namespace: gpu-operator
        create_namespace: yes
        values:
          driver.enabled: false      # Ansible 已装好驱动------不让 Operator 重复装
          toolkit.enabled: true
          devicePlugin.enabled: true
          dcgm.enabled: true         # DCGM Exporter → Prometheus GPU 监控
          migManager.enabled: false  # 训练集群不做 MIG 切分
          gfd.enabled: true          # 自动发现 GPU 型号并打节点标签

driver.enabled: false:让 Operator 自己装驱动会每节点跑 DaemonSet 从容器镜像拉驱动,网络慢时超时。预装最稳。

Step 4:NCCL 网络调优(最容易出事的步骤)

NCCL 是多 GPU 通信核心------PyTorch DDP/FSDP、DeepSpeed、Megatron-LM 底层都调它的 AllReduce/AllGather。行为全由环境变量控制------设错一个可能不报错,吞吐差 40%。

yaml 复制代码
- hosts: gpu_nodes
  become: yes
  tasks:
    - lineinfile:
        path: /etc/environment
        line: "{{ item }}"
      loop:
        - 'NCCL_SOCKET_IFNAME=eth1'         # RoCE 网卡------绝不能指管理网
        - 'NCCL_IB_DISABLE=0'                # 启用 InfiniBand
        - 'NCCL_NET_GDR_LEVEL=5'             # GPUDirect RDMA:显存→网卡,跳过 CPU
        - 'NCCL_PROTO=Simple'                # RDMA 零拷贝
        - 'NCCL_ALGO=Ring'                   # AllReduce Ring------带宽最优
        - 'NCCL_NSOCKS_PERTHREAD=4'
        - 'NCCL_BUFFSIZE=4194304'            # 4MB,匹配 400Gbps 网卡
        - 'NCCL_DEBUG=WARN'

NCCL 环境变量速查

变量 作用 推荐值 设错的代价
NCCL_SOCKET_IFNAME 通信网卡 eth1(RoCE) 走管理网→延迟爆增→吞吐崩盘
NCCL_IB_DISABLE 禁用 IB 0(启用) 贵价 IB 闲置→降级 TCP/IP
NCCL_NET_GDR_LEVEL GPUDirect RDMA 5 设 0→4 次拷贝;设 5→1 次拷贝
NCCL_ALGO AllReduce 算法 Ring 错误选 Tree→大数据带宽远低于 Ring
NCCL_DEBUG 日志级别 WARN INFO→日志撑爆;ERROR→无排查线索
NCCL_P2P_DISABLE 禁用 P2P 不设(启用) 走 host memory→吞吐掉 30%

核心原则:让数据从 GPU 显存到对面 GPU 显存路径最短。每一步间接访问都是延迟------AllReduce 是同步操作,一个节点慢,全场等。

Step 5:验证------跑分说话

yaml 复制代码
- hosts: gpu_nodes
  tasks:
    - shell: docker run --rm --gpus all nvidia/cuda:12.4.0-devel-ubuntu22.04 \
      bash -c "cd /gpu-burn && make && ./gpu_burn 120"

    - shell: kubectl run nccl-test --rm -i --restart=Never \
      --image=nvidia/cuda:12.4.0-devel-ubuntu22.04 \
      --overrides='{"spec":{"containers":[{"name":"t","resources":{"limits":{"nvidia.com/gpu":8}}}]}}' \
      -- mpirun -np 8 ./all_reduce_perf -b 8 -e 128M -f 2 -g 1

    - command: nvidia-dcgm -r 1

GPU Burn 暴露散热问题、供电不足、PCIe 信号错误。NCCL-Test:8 卡 H100 带宽不到 350 GB/s(理论 400)= 配置有问题。


4. 完整流水线

vbnet 复制代码
┌───────────┐      ┌─────────────┐      ┌─────────────┐      ┌───────────┐
│ Terraform │ ───→ │   Ansible   │ ───→ │   ArgoCD    │ ───→ │  验证通过  │
├───────────┤      ├─────────────┤      ├─────────────┤      ├───────────┤
│ 创建 VPC  │      │ 前置准备     │      │ GPU Operator│      │ GPU Burn  │
│ 创建实例  │      │ 驱动+CUDA   │      │ K8s Pods    │      │ NCCL-Test │
│ 创建存储  │      │ Container RT│      │ 训练 Job    │      │ DCGM 诊断  │
│ 输出 IPs  │      │ NCCL 调优   │      │             │      │           │
└───────────┘      └─────────────┘      └─────────────┘      └───────────┘
  ~10 分钟             ~15 分钟             ~5 分钟             ~2 分钟

实际命令:

bash 复制代码
# 1. Terraform 创建基础设施
terraform apply -auto-approve

# 2. 输出 IP 列表生成 Ansible inventory
terraform output -json gpu_private_ips | jq -r '.[]' > inventory

# 3. Ansible 全量并行------forks=100 是关键
ansible-playbook -i inventory site.yml --forks 100

# 4. ArgoCD 接管,新节点自动 sync
argocd app sync gpu-cluster

--forks 100 是关键:Ansible 默认 forks=5,100 台跑 20 轮。设 forks=100,所有节点同时配------总时间 = 单台时间(~15 分钟),跟节点数无关。


一句话总结

Terraform 管"有什么",Ansible 管"怎么配"。前者声明 GPU 集群底层资源------网络、计算、存储------一份代码适配多云;后者把裸金属变成 GPU 工作节点------从内核参数到 NCCL 环境变量,依赖顺序精确,幂等可重试。两者联合:100 台 GPU 节点从零到可训练,30 分钟而不是一周。NCCL 环境变量是整条链上最被忽视的陷阱------一个 NCCL_SOCKET_IFNAME 设错,能让 8xH100 集群跑出 1080Ti 的通信性能。

下一篇:GPU Operator 深度解析------为什么一个 Helm Chart 能管住驱动、Device Plugin、DCGM、MIG 全部生命周期?它是怎么发现新 GPU、怎么给 Kubelet 注册资源、怎么做到"一个 Pod 申请 0.5 张 GPU"的?

相关推荐
Dr.kangder2 小时前
嵌入式面试总结(二十二)——指针
java·面试·职场和发展·架构·嵌入式
Smoothcloud_润云2 小时前
从“模型服务”到“Agent 调度”:AI 推理基础设施为什么正在重构?
llm·agent·gpu
嘟嘟07172 小时前
顺时针螺旋填充 n×n 矩阵:手撕 generateMatrix 的四个 for 循环
javascript·算法·面试
安逸Ai2 小时前
卷积神经网络 CNN 是什么?为什么适合处理图像?
人工智能·面试
ShineWinsu2 小时前
对于C++:缓冲区管理的详细解析
linux·c++·面试·编程·笔试·io·缓冲区
做前端的娜娜子3 小时前
#浏览器存储方案:localStorage、sessionStorage 与 Cookie
前端·面试·掘金·金石计划
Dr.kangder3 小时前
嵌入式面试总结(二十一)——C语言关键字
c语言·开发语言·面试·职场和发展·架构·虚拟化
Escalating_xu4 小时前
【Linux】进程间通信:从匿名管道、FIFO 到共享内存、消息队列与信号量
java·linux·面试
程序员爱钓鱼5 小时前
Rust 方法 Method详解:self、方法调用与API设计
后端·面试·rust