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"的?