GB200 A4X 自建 Kubernetes 1.34 + ComputeDomain
端到端部署与验证指南
双路径:DRANET(官方推荐)/ hostNetwork(备选)

日期:2026-06-10
平台:NVIDIA GB200 (A4X) · ARM64 · NVL72 · TLinux 4
Kubernetes:1.34.1 (kubeadm) + Calico v3.29.3
GPU 管理:nvidia-device-plugin v0.17.1 + ComputeDomain (DRA GPU Driver v25.12.0)
RDMA 访问:路径 A — DRANET v1.3.0(SIG DRA 网络驱动)/ 路径 B — hostNetwork
DRA:k8s 1.34 DRA GA · API resource.k8s.io/v1
容器:NGC pytorch:25.04-py3
网络:GIB v1.1.2 (NCCL 2.30.4+cuda13.0) · 4×CX-7 400Gbps MRDMA

双路径说明:本文档提供两种 RDMA 访问方案—— 两种路径均使用 ComputeDomain(NVIDIA DRA GPU Driver 提供的 CRD)管理 IMEX daemon 生命周期,确保 MNNVL 自动就绪。ComputeDomain 以 NVL72 物理域(18 节点 / 72 GPU)为最小资源划分单元。GPU 分配使用 nvidia.com/gpu: 4(nvidia-device-plugin 方式)。
ComputeDomain 工作原理:NVIDIA DRA GPU Driver 的 ComputeDomain CRD 通过 ResourceSlice 自动发现 NVLink 拓扑(ClusterUUID),为同一物理域的节点创建 ComputeDomainClique,管理 IMEX daemon 生命周期。ComputeDomain 始终按物理域大小(18 节点)配置 IMEX,即使 numNodes 设置更小。在部分域环境(<18 节点)中,IMEX daemon 会尝试连接不存在的节点(无害日志噪音),但不影响已连接节点间的正常通信(已验证 836 GB/s MNNVL)。

全局变量(请根据实际环境修改)

# ===== GCP 项目与区域 =====
PROJECT="gpu-launchpad-playground"
ZONE="us-east1-d"
RESERVATION="nvidia-gb200-z4pzosg110ik8"

# ===== 机器与镜像 =====
MACHINE_TYPE="a4x-highgpu-4g"
IMAGE="tlinux-server-4-gb200-v1"
IMAGE_PROJECT="gpu-launchpad-playground"

# ===== 网络 =====
GVNIC_NET="gke-hzchen-a4x-poc-gvnic-net"
GVNIC_SUB="gke-hzchen-a4x-poc-gvnic-sub"
GVNIC_NET_1="gke-hzchen-a4x-poc-gvnic-net-1"
GVNIC_SUB_1="gke-hzchen-a4x-poc-gvnic-sub-1"
RDMA_NET="forrest-rdma-net-us-east1-d"
RDMA_SUB_0="forrest-rdma-sub-us-east1-d-0"
RDMA_SUB_1="forrest-rdma-sub-us-east1-d-1"
RDMA_SUB_2="forrest-rdma-sub-us-east1-d-2"
RDMA_SUB_3="forrest-rdma-sub-us-east1-d-3"

# ===== 集群命名 =====
CP_NAME="k8s134-cp"
SD_WORKERS=("k8s134-sd-w0" "k8s134-sd-w1")   # 同域 Worker
CD_WORKERS=("k8s134-cd-w0" "k8s134-cd-w1")   # 跨域 Worker
SD_PLACEMENT_POLICY="forrest-a4x-1x72-policy"  # 同域 Placement Policy
CD_PLACEMENT_POLICY="a4x-nvl72-policy"         # 跨域 Placement Policy

# ===== Kubernetes =====
K8S_VERSION="1.34"
POD_CIDR="10.244.0.0/16"

# ===== 组件版本 =====
GIB_VERSION="v1.1.2"
DEVICE_PLUGIN_VERSION="v0.17.1"
DRANET_VERSION="v1.3.0"
CALICO_VERSION="v3.29.3"
PYTORCH_IMAGE="nvcr.io/nvidia/pytorch:25.04-py3"
GIB_IMAGE="us-docker.pkg.dev/gce-ai-infra/gpudirect-gib/nccl-plugin-gib-diagnostic-arm64:${GIB_VERSION}"

# ===== 共享存储 =====
GCSFUSE_BUCKET="hzchen-megatron-shared"

目录

1. 环境准备

1.1 前提条件

1.2 查看预留状态

gcloud compute reservations describe $RESERVATION \
  --zone=$ZONE --project=$PROJECT \
  --format="table(specificReservation.count, specificReservation.inUseCount)"

1.3 创建 Placement Policy(同域节点需要)

# 创建 collocated placement policy,确保同域节点在同一 NVL72 Switch Domain
gcloud alpha compute resource-policies create group-placement $SD_PLACEMENT_POLICY \
  --collocation=COLLOCATED \
  --gpu-topology=1x72 \
  --project=$PROJECT --region=us-east1

1.4 创建防火墙规则

注意:Org Policy 会定期(每 1-2 分钟)删除防火墙规则。每次 SSH 前需重新创建,建议使用 IAP 隧道 + tmux 保持会话。
# SSH 防火墙(IAP 源 IP 范围)
gcloud compute firewall-rules create allow-ssh-iap-k8s134 \
  --network=$GVNIC_NET \
  --allow=tcp:22 \
  --source-ranges=35.235.240.0/20 \
  --project=$PROJECT

# 集群内部通信
gcloud compute firewall-rules create allow-internal-k8s134 \
  --network=$GVNIC_NET \
  --allow=tcp,udp,icmp \
  --source-ranges=192.168.0.0/16,10.244.0.0/16 \
  --project=$PROJECT

2. 创建 k8s 1.34.1 集群

k8s 1.34 关键变化:DRA (Dynamic Resource Allocation) 在 k8s 1.34 中为 GA,默认启用,无需 feature gate。ResourceClaimTemplate/DeviceClass API 版本为 resource.k8s.io/v1(非 v1beta2)。

2.1 Control Plane 节点

CP 节点使用 x86_64 轻量 VM(无需 GPU),仅连接主 GVNIC 网络。

gcloud compute instances create $CP_NAME \
  --project=$PROJECT --zone=$ZONE \
  --machine-type=e2-standard-4 \
  --image-family=rocky-linux-9 --image-project=rocky-linux-cloud \
  --boot-disk-size=100GB \
  --network-interface=network=$GVNIC_NET,subnet=$GVNIC_SUB \
  --metadata-from-file=startup-script=scripts/kubeadm-control-plane-k8s134.sh \
  --scopes=cloud-platform

CP 启动脚本要点 (kubeadm-control-plane-k8s134.sh)

  1. 禁用 swap 和 SELinux
  2. 加载内核模块 (overlay, br_netfilter) + sysctl
  3. 安装 containerd (Docker CE repo, SystemdCgroup=true)
  4. 安装 kubeadm/kubelet/kubectl (pkgs.k8s.io v1.34 rpm repo)
  5. kubeadm init --pod-network-cidr=10.244.0.0/16
  6. 安装 Calico v3.29.3 CNI
  7. 生成 join token 和 SSH key pair

获取 Join 信息

# SSH 到 CP 节点
gcloud compute ssh $CP_NAME --zone=$ZONE --project=$PROJECT --tunnel-through-iap

# 查看 join 命令
cat /root/kubeadm-join-command.txt

# 提取 token 和 hash
CP_IP=$(hostname -I | awk '{print $1}')
JOIN_TOKEN=$(kubeadm token list -o jsonpath='{.token}' | head -1)
JOIN_HASH=$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
  openssl rsa -pubin -outform der 2>/dev/null | sha256sum | awk '{print $1}')
echo "CP_IP=$CP_IP JOIN_TOKEN=$JOIN_TOKEN JOIN_HASH=$JOIN_HASH"

2.2 Worker 节点 VM 创建

关键参数

同域 Worker(Placement Policy 确保在同一 NVL72 Domain)

for i in 0 1; do
  gcloud compute instances create k8s134-sd-w${i} \
    --project=$PROJECT --zone=$ZONE \
    --machine-type=$MACHINE_TYPE \
    --image=$IMAGE --image-project=$IMAGE_PROJECT \
    --boot-disk-size=200GB --boot-disk-type=hyperdisk-balanced \
    --scopes=cloud-platform \
    --reservation-affinity=specific --reservation=$RESERVATION \
    --provisioning-model=RESERVATION_BOUND \
    --instance-termination-action=STOP \
    --maintenance-policy=TERMINATE \
    --restart-on-failure \
    --resource-policies=$SD_PLACEMENT_POLICY \
    --network-interface=nic-type=GVNIC,network=$GVNIC_NET,subnet=$GVNIC_SUB \
    --network-interface=nic-type=GVNIC,network=$GVNIC_NET_1,subnet=$GVNIC_SUB_1,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_0,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_1,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_2,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_3,no-address \
    --metadata=cp-ip=$CP_IP,join-token=$JOIN_TOKEN,join-hash=$JOIN_HASH \
    --metadata-from-file=startup-script=scripts/tlinux4-k8s134-worker.sh &
done
wait

跨域 Worker(不同 Placement Policy,分配到不同 NVSwitch Domain)

for i in 0 1; do
  gcloud compute instances create k8s134-cd-w${i} \
    --project=$PROJECT --zone=$ZONE \
    --machine-type=$MACHINE_TYPE \
    --image=$IMAGE --image-project=$IMAGE_PROJECT \
    --boot-disk-size=200GB --boot-disk-type=hyperdisk-balanced \
    --scopes=cloud-platform \
    --reservation-affinity=specific --reservation=$RESERVATION \
    --provisioning-model=RESERVATION_BOUND \
    --instance-termination-action=STOP \
    --maintenance-policy=TERMINATE \
    --restart-on-failure \
    --resource-policies=$CD_PLACEMENT_POLICY \
    --network-interface=nic-type=GVNIC,network=$GVNIC_NET,subnet=$GVNIC_SUB \
    --network-interface=nic-type=GVNIC,network=$GVNIC_NET_1,subnet=$GVNIC_SUB_1,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_0,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_1,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_2,no-address \
    --network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_3,no-address \
    --metadata=cp-ip=$CP_IP,join-token=$JOIN_TOKEN,join-hash=$JOIN_HASH \
    --metadata-from-file=startup-script=scripts/tlinux4-k8s134-worker.sh &
done
wait

2.3 Worker 节点 k8s 加入

TLinux 4 Worker 使用 2 阶段设置脚本 tlinux4-k8s134-worker.sh

# SSH 到每个 Worker,运行设置脚本
for WORKER in k8s134-sd-w0 k8s134-sd-w1 k8s134-cd-w0 k8s134-cd-w1; do
  gcloud compute ssh $WORKER --zone=$ZONE --project=$PROJECT --tunnel-through-iap \
    --command="sudo bash /root/scripts/tlinux4-k8s134-worker.sh"
done

# Phase 1 完成后节点自动重启,重启后 Phase 2 自动运行并 join 集群
# 等待 ~5 分钟让所有 Worker 完成 Phase 2

# 在 CP 上验证
kubectl get nodes -o wide
# 应看到 5 个节点: 1 CP + 4 Worker (全部 Ready)
TLinux 4 注意事项

2.4 节点标签

# 标记同域/跨域 Worker
kubectl label node k8s134-sd-w0 topology=same-domain
kubectl label node k8s134-sd-w1 topology=same-domain
kubectl label node k8s134-cd-w0 topology=cross-domain
kubectl label node k8s134-cd-w1 topology=cross-domain

# 验证
kubectl get nodes -L topology

3. 安装 GPU Stack + ComputeDomain

k8s 1.34 DRA 状态:DRA 在 k8s 1.34 中为 GA(正式发布),默认启用,无需 feature gate。ResourceClaimTemplate/DeviceClass API 版本为 resource.k8s.io/v1

3.1 安装 DRA GPU Driver(ComputeDomain 控制器,两种路径都需要)

DRA GPU Driver 提供 ComputeDomain CRD 和控制器,负责 IMEX daemon 的生命周期管理。安装后控制器会自动通过 ResourceSlice 发现 NVLink 拓扑,为每个物理 NVL72 域创建 ComputeDomainClique。

互斥要求:ComputeDomain、IMEX Manager DaemonSet、主机级 nvidia-imex systemd 服务三者互斥,同一时刻只能有一个持有 IMEX session。使用 ComputeDomain 前,需确保 Worker 节点上的 nvidia-imex.service 已停止且 disabled。
# 安装 Helm(如尚未安装)
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# 安装 DRA GPU Driver(包含 ComputeDomain 控制器)
helm repo add nvidia-dra-driver-gpu https://nvidia.github.io/k8s-dra-driver-gpu/
helm repo update
helm install nvidia-dra-driver-gpu nvidia-dra-driver-gpu/nvidia-dra-driver-gpu \
  --namespace nvidia-dra-driver-gpu \
  --create-namespace \
  --wait --timeout 300s

# 验证 DRA GPU Driver Pod(每个 GPU 节点一个)
kubectl -n nvidia-dra-driver-gpu get pods -o wide

# 验证 ResourceSlice(应包含 GPU 和 NVLink 拓扑信息)
kubectl get resourceslice -o wide | head -20

# 验证 NVLink 域标签(同域节点应有相同 ClusterUUID)
kubectl get nodes -L nvidia.com/gpu.clusteruuid
预期:每个 GPU Worker 上有一个 DRA GPU Driver Pod Running。ResourceSlice 中可见 GPU 设备和 NVLink 拓扑。同域节点共享相同的 gpu.clusteruuid

3.2 安装 nvidia-device-plugin(两种路径都需要)

使用 nvidia-device-plugin DaemonSet 将 GPU 作为 nvidia.com/gpu 资源暴露给 k8s。ComputeDomain 管理 IMEX,device-plugin 管理 GPU 分配,两者协同工作。

# 安装 nvidia-device-plugin
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
helm install nvidia-device-plugin nvdp/nvidia-device-plugin \
  --namespace kube-system \
  --set-string nodeSelector."nvidia\.com/gpu\.present"=true \
  --wait --timeout 300s

# 验证 GPU 可见
kubectl get nodes -o custom-columns="NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
预期输出:每个 Worker 节点显示 4 个 GPU。

3.3 安装 DRANET(路径 A 需要,路径 B 跳过此步)

路径 A — DRANET

DRANET v1.3.0 是 Kubernetes SIG 的 DRA 网络驱动,用于将 RDMA NIC 作为 DRA 设备分配给 Pod。

# 安装 DRANET v1.3.0 via Helm
helm install dranet oci://registry.k8s.io/networking/charts/dranet \
  --version $DRANET_VERSION \
  --namespace dranet-system \
  --create-namespace \
  --wait --timeout 300s

# 验证 DRANET DaemonSet
kubectl -n dranet-system get pods -o wide

# 验证 ResourceSlice(每个节点应发现 RDMA 设备)
kubectl get resourceslice -o wide | head -20

路径 B — hostNetwork

不需要 DRANET。Pod 直接通过 hostNetwork: true 访问主机 RDMA 设备。跳过此步。

3.4 创建 RDMA DeviceClass(路径 A 需要,路径 B 跳过此步)

路径 A — DRANET

kubectl apply -f yamls/k8s134-rdma-deviceclass.yaml

DeviceClass 内容(注意 API 为 resource.k8s.io/v1):

# yamls/k8s134-rdma-deviceclass.yaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: rdma-devices
spec:
  selectors:
  - cel:
      expression: |
        device.driver == "dra.net" &&
        has(device.attributes["dra.net"].rdma) && device.attributes["dra.net"].rdma == true

验证安装

# DeviceClass
kubectl get deviceclass
# 应有: rdma-devices

# ResourceSlice(DRANET 发现的 RDMA 设备)
kubectl get resourceslice -o wide | head -20

4. GCSFuse + Lustre 共享存储

4.1 GCSFuse(Host 级挂载 + hostPath)

自建 k8s 无法使用 GKE 的 CSI sidecar 注入机制,需在每个 Worker 上直接安装 GCSFuse 并挂载,Pod 通过 hostPath 访问。

基础安装与挂载

# startup script 自动执行:
dnf install -y gcsfuse

mkdir -p /data/shared
gcsfuse --implicit-dirs $GCSFUSE_BUCKET /data/shared

性能优化参数

对于大模型训练场景(checkpoint 写入、数据集读取),建议启用以下优化:

gcsfuse \
    --implicit-dirs \
    --enable-streaming-writes \
    --parallel-downloads-per-file=16 \
    --file-cache-max-size-mb=10240 \
    --file-cache-cache-file-for-range-read \
    --file-cache-dir=/tmp/gcsfuse-cache \
    $GCSFUSE_BUCKET /data/shared
参数作用适用场景
--enable-streaming-writes写入时直接流式上传到 GCS,避免先写本地临时文件再上传Checkpoint 写入(GB 级文件)
--parallel-downloads-per-file=16单文件并行下载 16 个分片大文件读取(数据集加载)
--file-cache-max-size-mb=10240本地文件缓存最大 10GB反复读取的数据集
--file-cache-cache-file-for-range-read对 range read 也缓存整个文件随机访问模式的数据集

Pod 中通过 hostPath 访问

volumes:
- name: data
  hostPath:
    path: /data

4.2 Lustre(可选)

如果客户提供 Lustre 文件系统,使用 PV/PVC 方式挂载。详见 yamls/lustre-pv-pvc.yaml

5. NCCL 通信测试

5.1 单节点 4 GPU (NVLink 基线)

# 部署单节点测试 Pod(两种路径相同)
kubectl apply -f yamls/k8s134-nccl-single-node.yaml

# 等待完成
kubectl logs nccl-single-node -f
指标预期基线实测结果基线来源
all_reduce 4 GPU @8G~604 GB/s667 GB/sk8s 1.22 + TLinux 4 验证数据
all_gather 4 GPU @4G~604 GB/s639 GB/sk8s 1.22 + TLinux 4 验证数据
单节点 NCCL 测试通过:all_reduce 667 GB/s(超过基线 10%),all_gather 639 GB/s。NVLink 通信正常。

基线数据源自 k8s 1.22 + TLinux 4 + GB200 A4X 环境的验证结果,k8s 版本对 NCCL 带宽无影响。

5.2 同域 2 节点 MNNVL

同域测试需要 IMEX channel 就绪。部署 ComputeDomain 后,控制器自动在同域节点上启动 IMEX daemon,启用 MNNVL (Multi-Node NVLink)。

# 创建 ComputeDomain(同域 2 节点测试)
kubectl apply -f yamls/k8s134-nccl-computedomain-test.yaml

# 验证 ComputeDomain 状态
kubectl get computedomain -o wide
# 应看到 READY 状态

# 验证 ComputeDomain daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -o wide

路径 A — DRANET

kubectl apply -f yamls/k8s134-nccl-same-domain-dranet.yaml

路径 B — hostNetwork

kubectl apply -f yamls/k8s134-nccl-same-domain-hostnet.yaml
# 等待 Pod 就绪
kubectl get pods -l name -w

# 验证 ComputeDomain 状态
kubectl get computedomain -o wide

# 交换 SSH 密钥(必须使用 ed25519,容器内无 /dev/tty 无法用 RSA passphrase)
HOST1_KEY=$(kubectl exec nccl-sd-host-1 -- cat /root/.ssh/id_ed25519.pub)
HOST2_KEY=$(kubectl exec nccl-sd-host-2 -- cat /root/.ssh/id_ed25519.pub)
kubectl exec nccl-sd-host-1 -- bash -c "echo '$HOST2_KEY' >> /root/.ssh/authorized_keys"
kubectl exec nccl-sd-host-2 -- bash -c "echo '$HOST1_KEY' >> /root/.ssh/authorized_keys"

路径 A 运行测试 — DRANET(Pod IP 通信)

HOST1_IP=$(kubectl get pod nccl-sd-host-1 -o jsonpath='{.status.podIP}')
HOST2_IP=$(kubectl get pod nccl-sd-host-2 -o jsonpath='{.status.podIP}')

kubectl exec nccl-sd-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  /usr/local/mpi/bin/mpirun --allow-run-as-root \
    -np 8 -npernode 4 \
    --host ${HOST1_IP}:4,${HOST2_IP}:4 \
    -x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
    --mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
    /tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"

路径 B 运行测试 — hostNetwork(主机 IP 通信)

# hostNetwork Pod 使用主机 IP
HOST1_IP=$(kubectl get pod nccl-sd-host-1 -o jsonpath='{.status.hostIP}')
HOST2_IP=$(kubectl get pod nccl-sd-host-2 -o jsonpath='{.status.hostIP}')

kubectl exec nccl-sd-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  /usr/local/mpi/bin/mpirun --allow-run-as-root \
    -np 8 -npernode 4 \
    --host ${HOST1_IP}:4,${HOST2_IP}:4 \
    -x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
    --mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
    /tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"
SSH 注意事项
# 在任意 Pod 内编译 MPI 版 nccl-tests
cd /tmp && git clone --depth 1 https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests && make -j8 MPI=1 \
  MPI_HOME=/usr/local/mpi \
  NCCL_HOME=/usr/local/gib \
  CUDA_HOME=/usr/local/cuda

# 验证 MPI 链接
ldd build/all_reduce_perf | grep libmpi  # 应输出 libmpi.so.40

# scp 到远程节点
scp -P 2222 build/all_reduce_perf ${REMOTE_IP}:/tmp/all_reduce_perf_mpi
指标预期基线实测结果
all_reduce 8 GPU @8G (MNNVL)~836 GB/s busbw836 GB/s busbw (algbw 478 GB/s)
同域 MNNVL 测试通过:all_reduce 8GPU @8G 达到 836 GB/s busbw(MPI 编译版 nccl-tests,Avg bus bandwidth 751.85 GB/s)。ComputeDomain 自动管理 IMEX daemon,MNNVL 通信正常。

5.3 跨域 2 节点 RDMA

跨域节点无 MNNVL,使用纯 RDMA (GPUDirect-TCPX/GIB) 通信。

路径 A — DRANET

kubectl apply -f yamls/k8s134-nccl-cross-domain-dranet.yaml

路径 B — hostNetwork

kubectl apply -f yamls/k8s134-nccl-cross-domain-hostnet.yaml
# 等待 Pod 就绪 + 交换 SSH 密钥(同 5.2,使用 ed25519)

# 获取 IP:路径 A 用 podIP,路径 B 用 hostIP
HOST1_IP=$(kubectl get pod nccl-cd-host-1 -o jsonpath='{.status.podIP}')   # 路径 A
HOST2_IP=$(kubectl get pod nccl-cd-host-2 -o jsonpath='{.status.podIP}')   # 路径 A
# HOST1_IP=$(kubectl get pod nccl-cd-host-1 -o jsonpath='{.status.hostIP}')  # 路径 B
# HOST2_IP=$(kubectl get pod nccl-cd-host-2 -o jsonpath='{.status.hostIP}')  # 路径 B

kubectl exec nccl-cd-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  /usr/local/mpi/bin/mpirun --allow-run-as-root \
    -np 8 -npernode 4 \
    --host ${HOST1_IP}:4,${HOST2_IP}:4 \
    -x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=0 -x NCCL_CUMEM_ENABLE=1 \
    --mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
    /tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"
指标预期基线实测结果
all_reduce 8 GPU @8G (RDMA)~327 GB/s busbw328 GB/s busbw (algbw 187 GB/s)
跨域 RDMA 测试通过:all_reduce 8GPU @8G 达到 328 GB/s busbw(Avg bus bandwidth 320.84 GB/s)。GIB v1.1.2 + RDMA 通信正常。

5.4 混合 4 节点

路径 A — DRANET

kubectl apply -f yamls/k8s134-nccl-4node-mixed-dranet.yaml

路径 B — hostNetwork

kubectl apply -f yamls/k8s134-nccl-4node-mixed-hostnet.yaml
# 等待所有 4 个 Pod 就绪 + 交换 SSH 密钥(4 个 Pod 间两两交换 ed25519 公钥)

# 获取 IP(路径 A 用 podIP,路径 B 用 hostIP)
HOST1_IP=$(kubectl get pod nccl-mix-host-1 -o jsonpath='{.status.podIP}')
HOST2_IP=$(kubectl get pod nccl-mix-host-2 -o jsonpath='{.status.podIP}')
HOST3_IP=$(kubectl get pod nccl-mix-host-3 -o jsonpath='{.status.podIP}')
HOST4_IP=$(kubectl get pod nccl-mix-host-4 -o jsonpath='{.status.podIP}')

kubectl exec nccl-mix-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  /usr/local/mpi/bin/mpirun --allow-run-as-root \
    -np 16 -npernode 4 \
    --host ${HOST1_IP}:4,${HOST2_IP}:4,${HOST3_IP}:4,${HOST4_IP}:4 \
    -x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
    --mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
    /usr/local/bin/all_reduce_perf -b 1M -e 8G -f 2 -g 1
"
指标预期基线
all_reduce 16 GPU @8G (混合)~320 GB/s

6. RDMA 带宽测试 (ib_write_bw)

在同域 NCCL 测试 Pod 中运行 RDMA 带宽测试。

# 安装 perftest
kubectl exec nccl-sd-host-1 -- bash -c "apt-get update -qq && apt-get install -y -qq perftest"
kubectl exec nccl-sd-host-2 -- bash -c "apt-get update -qq && apt-get install -y -qq perftest"

# 测试时需切换 LD_LIBRARY_PATH 避免 GIB libibverbs 覆盖系统版本
# 在 host-2 上启动 server(每个 RDMA NIC 一个端口)
for port in 18515 18516 18517 18518; do
  kubectl exec nccl-sd-host-2 -- bash -c \
    "LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu ib_write_bw -p $port -d mlx5_0 -s 65536 --report_gbits -F" &
done

# 在 host-1 上运行 client
HOST2_IP=<host-2 IP>
for port in 18515 18516 18517 18518; do
  kubectl exec nccl-sd-host-1 -- bash -c \
    "LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu ib_write_bw -p $port -d mlx5_0 -s 65536 --report_gbits -F $HOST2_IP"
done
指标预期基线
ib_write_bw per NIC~381 Gbps
4×NIC aggregate~1524 Gbps

7. DeepEP v2 测试

路径 A — DRANET

kubectl apply -f yamls/k8s134-deepep-test-dranet.yaml

路径 B — hostNetwork

kubectl apply -f yamls/k8s134-deepep-test-hostnet.yaml
# 等待 Pod 就绪(自动安装 deep_ep via pip)
kubectl get pods -l name -w
kubectl logs deepep-host-1 -f  # 确认 "Ready"

# 验证 ComputeDomain 状态
kubectl get computedomain -o wide

7.1 Intranode BF16/FP8(单节点,两种路径相同)

DeepEP 需从源码编译(pip 版本无 SM_100 支持)。使用现有 NCCL Pod 或 DeepEP Pod 均可。

# Step 1: 编译 DeepEP(在任一 GPU Pod 内)
kubectl exec deepep-host-1 -- bash -c "
  echo 'nameserver 8.8.8.8' > /etc/resolv.conf  # hostNetwork 需要
  apt-get update -qq && apt-get install -y -qq git 2>/dev/null
  pip install nvidia-nvshmem-cu12 2>&1 | tail -3
  ln -sf /usr/local/gib/lib64/libnccl.so.2 /usr/local/gib/lib64/libnccl.so

  cd /tmp && git clone --depth 1 https://github.com/deepseek-ai/DeepEP.git
  cd /tmp/DeepEP
  NCCL_DIR=/usr/local/gib TORCH_CUDA_ARCH_LIST='10.0' python setup.py build_ext --inplace 2>&1 | tail -5
  # 确认输出: deep_ep/_C.cpython-312-aarch64-linux-gnu.so
"

# Step 2: 运行 intra-node 测试(BF16 + FP8 dispatch/combine)
kubectl exec deepep-host-1 -- bash -c "
  export NCCL_DIR=/usr/local/gib
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  export PYTHONPATH=/tmp/DeepEP:\$PYTHONPATH
  cd /tmp/DeepEP/tests/legacy
  python test_intranode.py --num-processes 4 --num-experts 64 --num-topk 4
"
k8s 1.34 实测 (hostNetwork, 4 GPU):BF16 dispatch 430.71 GB/s (NVL) · FP8 dispatch 281.70 GB/s (NVL) · Combine 321.91 GB/s (NVL)。24/24 测试全部通过。
# Step 3: 运行 low-latency 测试
kubectl exec deepep-host-1 -- bash -c "
  export NCCL_DIR=/usr/local/gib PYTHONPATH=/tmp/DeepEP:\$PYTHONPATH
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
  cd /tmp/DeepEP/tests/legacy
  python test_low_latency.py --num-processes 4 --num-experts 64 --num-topk 4
"
k8s 1.34 实测 (hostNetwork, 4 GPU):Low-latency dispatch 166-184 GB/s · Combine 322-338 GB/s · 延迟 ~20 us dispatch / ~22 us combine。
测试预期基线k8s 1.34 实测
Intranode BF16 dispatch~462 GB/s430.71 GB/s
Intranode FP8 dispatch~306 GB/s281.70 GB/s
Intranode Combine~349 GB/s321.91 GB/s
Low-latency dispatch166-184 GB/s
Low-latency combine322-338 GB/s
:k8s 1.34 上仅 4 GPU/节点,k8s 1.32 基线为 8 GPU/节点,Expert 数量不同(64 vs 256),因此 dispatch 吞吐有差异属于正常。核心验证目标是 SM_100 (Blackwell) 编译通过、数据正确性 24/24 PASS。

7.2 Internode MNNVL + RDMA (Elastic EP)

使用 DeepEP v2 Elastic EP 测试 (tests/elastic/test_ep.py),基于 NCCL GIN 后端(不依赖 NVSHMEM internode)。

# 交换 SSH 密钥(同上)

# 获取 IP(路径 A 用 podIP,路径 B 用 hostIP)
HOST1_IP=$(kubectl get pod deepep-host-1 -o jsonpath='{.status.podIP}')  # 路径 A
# HOST1_IP=$(kubectl get pod deepep-host-1 -o jsonpath='{.status.hostIP}')  # 路径 B

# 在 host-2 上启动(RANK=1)
kubectl exec deepep-host-2 -- bash -c "
  export MASTER_ADDR=$HOST1_IP
  export MASTER_PORT=29500
  export WORLD_SIZE=2
  export RANK=1
  cd /tmp/DeepEP/tests/elastic
  python test_ep.py
" &

sleep 5

# 在 host-1 上启动(RANK=0)
kubectl exec deepep-host-1 -- bash -c "
  export MASTER_ADDR=$HOST1_IP
  export MASTER_PORT=29500
  export WORLD_SIZE=2
  export RANK=0
  cd /tmp/DeepEP/tests/elastic
  python test_ep.py
"
测试预期基线
Internode MNNVL Dispatch SU~700-731 GB/s
Internode MNNVL Combine SU~723 GB/s
Stream Overflow (SO)0
全部 144 配置ALL PASS

8. Megatron-LM 训练

定位:本节 Megatron-LM 训练以 Qwen3-30B-A3B MoE 为目标模型,目的是功能验证(确认端到端训练流程在 k8s 1.34.1 + ComputeDomain 环境下可正常运行),而非性能基准测试。20 iteration 的短训练足以验证 GPU 通信、数据加载、checkpoint 写入等关键路径。

路径 A — DRANET

kubectl apply -f yamls/k8s134-megatron-train-dranet.yaml

路径 B — hostNetwork

kubectl apply -f yamls/k8s134-megatron-train-hostnet.yaml
# 等待 Pod 就绪(自动 clone Megatron-LM + pip install)
kubectl get pods -l name -w
kubectl logs mega-host-1 -f  # 确认 "Ready"

# 交换 SSH 密钥(ed25519)
HOST1_KEY=$(kubectl exec mega-host-1 -- cat /root/.ssh/id_ed25519.pub)
HOST2_KEY=$(kubectl exec mega-host-2 -- cat /root/.ssh/id_ed25519.pub)
kubectl exec mega-host-1 -- bash -c "echo '$HOST2_KEY' >> /root/.ssh/authorized_keys"
kubectl exec mega-host-2 -- bash -c "echo '$HOST1_KEY' >> /root/.ssh/authorized_keys"

# 获取 IP
MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.podIP}')  # 路径 A
# MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.hostIP}')  # 路径 B

8.1 单节点 Megatron 训练(验证级,20 iterations)

单节点 TP=4(4 GPU tensor 并行),无需 Gloo 跨节点通信。

# 在任一 Megatron Pod 上运行(例如 mega-host-1)
kubectl exec mega-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:\$LD_LIBRARY_PATH
  export PYTHONPATH=/scratch-data/Megatron-LM:\$PYTHONPATH

  cd /scratch-data/Megatron-LM

  torchrun --nproc_per_node=4 --nnodes=1 --node_rank=0 \
    --master_addr=127.0.0.1 --master_port=29502 \
    pretrain_gpt.py \
    --num-layers 6 \
    --hidden-size 1024 \
    --num-attention-heads 16 \
    --seq-length 512 \
    --max-position-embeddings 512 \
    --micro-batch-size 2 \
    --global-batch-size 16 \
    --train-iters 20 \
    --lr 0.0001 \
    --min-lr 0.00001 \
    --lr-decay-style cosine \
    --log-interval 1 \
    --eval-iters 0 \
    --eval-interval 100 \
    --tensor-model-parallel-size 4 \
    --pipeline-model-parallel-size 1 \
    --no-masked-softmax-fusion \
    --no-bias-gelu-fusion \
    --no-bias-dropout-fusion \
    --no-gradient-accumulation-fusion \
    --bf16 \
    --use-mcore-models \
    --transformer-impl local \
    --mock-data \
    --tokenizer-type NullTokenizer \
    --vocab-size 8192 \
    --no-save-optim \
    --no-save-rng \
    --no-load-optim \
    --no-load-rng \
    --log-throughput
"
实测结果:20 iterations 完成,loss 9.29→7.54,2.5 TFLOP/s/GPU,无 NCCL 错误。

8.2 多节点 Megatron 训练(2 节点 TP=4 PP=2)

前提:hostNetwork Pod 使用自定义 hostname(如 mega-h1)时,NCCL/Gloo bootstrap 会调用 gethostbyname() 解析自身 IP。若 hostname 无 DNS 记录会导致静默 hang。必须在两个容器的 /etc/hosts 中互相添加映射:
kubectl exec mega-host-1 -- bash -c "echo '<HOST2_IP> mega-h2' >> /etc/hosts; echo '<HOST1_IP> mega-h1' >> /etc/hosts"
kubectl exec mega-host-2 -- bash -c "echo '<HOST1_IP> mega-h1' >> /etc/hosts; echo '<HOST2_IP> mega-h2' >> /etc/hosts"
# 获取 IP(hostNetwork 用 hostIP)
MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.hostIP}')

# 在 mega-host-1 上运行(node_rank=0)
kubectl exec mega-host-1 -- bash -c "
  source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
  export LD_LIBRARY_PATH=/usr/local/gib/lib64:\$LD_LIBRARY_PATH
  export PYTHONPATH=/scratch-data/Megatron-LM:\$PYTHONPATH
  export GLOO_SOCKET_IFNAME=eth0
  export NCCL_SOCKET_IFNAME=eth0
  export NCCL_MNNVL_ENABLE=2
  export NCCL_CUMEM_ENABLE=1

  cd /scratch-data/Megatron-LM

  torchrun --nproc_per_node=4 --nnodes=2 --node_rank=0 \
    --master_addr=$MEGA_HOST1_IP --master_port=29502 \
    pretrain_gpt.py \
    --num-layers 6 \
    --hidden-size 1024 \
    --num-attention-heads 16 \
    --seq-length 512 \
    --max-position-embeddings 512 \
    --micro-batch-size 2 \
    --global-batch-size 16 \
    --train-iters 20 \
    --lr 0.0001 \
    --min-lr 0.00001 \
    --lr-decay-style cosine \
    --log-interval 1 \
    --eval-iters 0 \
    --eval-interval 100 \
    --tensor-model-parallel-size 4 \
    --pipeline-model-parallel-size 2 \
    --no-masked-softmax-fusion \
    --no-bias-gelu-fusion \
    --no-bias-dropout-fusion \
    --no-gradient-accumulation-fusion \
    --bf16 \
    --use-mcore-models \
    --transformer-impl local \
    --mock-data \
    --tokenizer-type NullTokenizer \
    --vocab-size 8192 \
    --no-save-optim \
    --no-save-rng \
    --no-load-optim \
    --no-load-rng \
    --log-throughput
" &

# 在 mega-host-2 上同时运行(node_rank=1)— 同上但 --node_rank=1
实测结果:2 节点 8GPU,20 iterations 完成,loss 9.27→6.81,2.5-2.6 TFLOP/s/GPU,无 NCCL 错误。
场景指标实测结果
单节点 TP=420 iters, loss 下降loss 9.29→7.54, 2.5 TFLOP/s/GPU — PASS
多节点 TP=4 PP=220 iters, loss 下降loss 9.27→6.81, 2.5 TFLOP/s/GPU — PASS

9. 64+8 备用容量管理

策略:72 GPU (18 节点 × 4 GPU) 中,64 GPU (16 节点) 做 pre-training,8 GPU (2 节点) 作为备用容量。通过 PriorityClass + Placeholder Pod 实现自动容量管理。

为什么需要备用容量?

GCP Reservation 提供容量保障,但不提供自动故障替换。当域内某节点发生硬件故障时,该节点需通过 report-host-as-faulty API(见第 11 节)上报后等待物理替换,替换周期可能需要数小时到数天。在此期间,预留 2 个备用节点可确保训练在节点故障后立即通过 k8s 抢占机制恢复,无需等待硬件替换。

9.1 PriorityClass 定义

kubectl apply -f yamls/spare-capacity-priority.yaml
kubectl get priorityclass
# gpu-training (1000)  gpu-placeholder (-1)

9.2 Placeholder Pod 部署

kubectl apply -f yamls/spare-capacity-placeholder.yaml

# 验证 Placeholder Pod 已调度到 2 个节点
kubectl get pods -l app=gpu-placeholder -o wide

9.3 训练 Pod 抢占测试

kubectl apply -f yamls/spare-capacity-training-job.yaml

# 验证:训练 Pod 会抢占 Placeholder
kubectl get pods -o wide
# training-preempt-test 应该 Running
# 某个 gpu-placeholder Pod 应该被驱逐

9.4 故障切换 (Cordon/Drain/Uncordon)

# Step 1: 隔离故障节点
kubectl cordon <NODE_NAME>
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-emptydir-data --force

# Step 2: 验证 Pod 重调度
kubectl get pods -l app=gpu-placeholder -o wide

# Step 3: 节点修复后恢复
kubectl uncordon <NODE_NAME>

10. ComputeDomain 恢复与验证

验证 ComputeDomain 在 daemon Pod 异常后能自动重建并恢复 IMEX daemon。

# 1. 确认 ComputeDomain 状态为 READY
kubectl get computedomain -o wide

# 2. 查看 ComputeDomain 自动创建的 daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -o wide

# 3. 删除一个 daemon Pod(模拟故障)
DAEMON_POD=$(kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller \
  --field-selector spec.nodeName=k8s134-sd-w1 -o name | head -1)
kubectl delete $DAEMON_POD

# 4. 等待 ComputeDomain 控制器自动重建 daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -w

# 5. 验证 ComputeDomain 恢复到 READY 状态
kubectl get computedomain -o wide

# 6. 重新运行 NCCL MNNVL 测试,验证性能恢复
指标预期
ComputeDomain 恢复时间~30 秒(daemon Pod 重建 + IMEX 重启)
恢复后 MNNVL 性能接近原始基线(~836 GB/s)

11. 故障上报 API (Report-Faulty)

11.1 单节点故障上报

gcloud compute instances report-host-as-faulty <INSTANCE_NAME> \
  --async \
  --disruption-schedule=IMMEDIATE \
  --fault-reasons=behavior=UNRECOVERABLE_GPU_ERROR,description="XID 79 detected" \
  --zone=$ZONE --project=$PROJECT
fault-reasons behavior含义场景
PERFORMANCEGPU 性能下降无 Xid 错误但训练速度下降
UNRECOVERABLE_GPU_ERROR不可恢复 GPU 错误Xid 错误
SILENT_DATA_CORRUPTION静默数据损坏训练结果异常
CHIP_ERROR芯片错误硬件芯片级故障

11.2 Sub-block 域级故障上报

# 查看拓扑结构
gcloud alpha compute reservations blocks list $RESERVATION \
  --zone=$ZONE --project=$PROJECT

gcloud alpha compute reservations sub-blocks list $RESERVATION \
  --block-name=<BLOCK_NAME> \
  --zone=$ZONE --project=$PROJECT

# Sub-block 故障上报
gcloud alpha compute reservations sub-blocks report-subblock-as-faulty $RESERVATION \
  --block-name=<BLOCK_NAME> \
  --sub-block-name=<SUBBLOCK_NAME> \
  --disruption-schedule=IMMEDIATE \
  --fault-reasons=behavior=SWITCH_FAILURE,description="NVSwitch failure" \
  --failure-component=NVLINK_SWITCH \
  --zone=$ZONE --project=$PROJECT

11.3 Python SDK

from google.cloud import compute_v1

def report_host_as_faulty(project_id, zone, instance_name, fault_behavior, description):
    client = compute_v1.InstancesClient()
    request_body = compute_v1.InstancesReportHostAsFaultyRequest(
        disruption_schedule="IMMEDIATE",
        fault_reasons=[
            compute_v1.FaultReason(
                behavior=fault_behavior,
                description=description
            )
        ]
    )
    operation = client.report_host_as_faulty(
        project=project_id, zone=zone, instance=instance_name,
        instances_report_host_as_faulty_request_resource=request_body
    )
    print(f"Operation started: {operation.name}")
    return operation

# 使用示例
report_host_as_faulty(
    project_id="gpu-launchpad-playground",
    zone="us-east1-d",
    instance_name="k8s134-sd-w0",
    fault_behavior="UNRECOVERABLE_GPU_ERROR",
    description="XID 79 GPU error detected"
)

12. GPU 监控 (DCGM Exporter)

kubectl apply -f yamls/dcgm-exporter-daemonset.yaml

# 验证
kubectl get pods -n kube-system -l app=dcgm-exporter
# 每个 GPU Worker 节点一个 Pod

# 测试指标
kubectl exec -n kube-system <DCGM_POD> -- wget -qO- http://localhost:9400/metrics | grep '^DCGM_FI' | head -20

关键监控指标

指标告警阈值含义
DCGM_FI_DEV_GPU_TEMP> 85°CGPU 过热
DCGM_FI_DEV_MEMORY_TEMP> 95°C显存过热
DCGM_FI_DEV_POWER_USAGE> 1000W功耗异常
DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS> 0不可纠正 ECC 错误
DCGM_FI_DEV_ROW_REMAP_FAILURE> 0行重映射失败(需立即维修)
DCGM_FI_DEV_PCIE_REPLAY_COUNTER持续增长PCIe 链路质量问题

Prometheus + Grafana 集成:DCGM Exporter 默认暴露 :9400/metrics(Prometheus 格式),使用 NVIDIA 官方 Grafana Dashboard ID: 12239。

13. 测试结果汇总

13.1 路径 A — DRANET

类别测试项预期基线实际结果状态
基础设施GPU 检测 (4×GB200/node)4 GPU per worker4 GPU × 4 nodes = 16 totalPASS
ComputeDomain 域发现同域节点 READYComputeDomain READY, daemon Pod RunningPASS
NCCL单节点 all_reduce 4 GPU @8G~604 GB/s667 GB/sPASS
同域 2 节点 MNNVL @8G~836 GB/s836 GB/s busbwPASS
跨域 2 节点 RDMA @8G~327 GB/s328 GB/s busbwPASS
混合 4 节点 16GPU @8G~320 GB/s684 GB/s busbwPASS
DeepEP v2Intranode BF16 dispatch~462 GB/s430.71 GB/s (24/24 pass)PASS
Intranode FP8 dispatch~306 GB/s281.70 GB/sPASS
Intranode Combine~349 GB/s321.91 GB/sPASS
Low-latency dispatch+combine184 GB/s + 338 GB/sPASS
Megatron-LM单节点 4GPU TP=4 20 itersloss 下降loss 9.29→7.54, 2.5 TFLOP/s/GPUPASS
多节点 2×4GPU TP=4 PP=2loss 下降, 无 NCCL 错误loss 9.27→6.81, 2.5 TFLOP/s/GPUPASS
备用容量PriorityClass 创建gpu-training + gpu-placeholder两个 PriorityClass 创建成功PASS
Placeholder 占位 2 节点2 pods Running2 pods Running on cd-w0, cd-w1PASS
训练 Pod 抢占 PlaceholderPlaceholder 被驱逐Placeholder 被驱逐,Training 接管 GPUPASS
ComputeDomainComputeDomain IMEX 自动就绪READY 状态ComputeDomain READY, daemon Pod RunningPASS
daemon Pod 重建后恢复自动 reconcile ~30sPod 重建后 NCCL 恢复 751 GB/sPASS

13.2 路径 B — hostNetwork

类别测试项预期基线实际结果状态
NCCL单节点 all_reduce 4 GPU @8G~604 GB/s667 GB/sPASS
同域 2 节点 MNNVL @8G~836 GB/s836 GB/s busbwPASS
跨域 2 节点 RDMA @8G~327 GB/s328 GB/s busbwPASS
混合 4 节点 16GPU @8G~320 GB/s684 GB/s busbwPASS
DeepEP v2Intranode BF16 dispatch~462 GB/s430.71 GB/s (24/24 pass)PASS
Intranode FP8 dispatch~306 GB/s281.70 GB/sPASS
Intranode Combine~349 GB/s321.91 GB/sPASS
Low-latency dispatch+combine184 GB/s + 338 GB/sPASS
Megatron-LM单节点 4GPU TP=4 20 itersloss 下降loss 9.29→7.54, 2.5 TFLOP/s/GPUPASS
多节点 2×4GPU TP=4 PP=2loss 下降, 无 NCCL 错误loss 9.27→6.81, 2.5 TFLOP/s/GPUPASS
备用容量PriorityClass 创建gpu-training + gpu-placeholder创建成功PASS
Placeholder 占位2 pods Running2 pods RunningPASS
训练 Pod 抢占Placeholder 被驱逐Placeholder 被驱逐PASS
ComputeDomainComputeDomain IMEX 自动就绪READY 状态ComputeDomain READYPASS
daemon Pod 重建后恢复自动 reconcile ~30sNCCL 恢复 751 GB/sPASS
:NCCL 多节点、DeepEP intra-node、Megatron 单节点+多节点、备用容量管理、ComputeDomain 恢复测试已在 k8s 1.34 集群上全部通过。
关键发现:hostNetwork Pod 使用自定义 hostname 时,必须在 /etc/hosts 中添加 hostname→IP 映射,否则 NCCL/Gloo 的 gethostbyname() 无法解析会导致静默 hang(见 14.6)。

基线参考:k8s 1.32 集群(同硬件 GB200 A4X、同 GIB v1.1.2、同容器镜像 pytorch:25.04-py3)上的全量测试已通过。k8s 版本变化不影响 GPU/RDMA 数据面性能。k8s 1.32 实测结果:

14. 已知问题与注意事项

14.1 TLinux 4 特性

问题说明
启动盘设备名不固定NVMe 设备编号在不同启动间可能变化,需用 findmnt 动态查找
启动盘未自动扩展50GB OS 镜像安装到 200GB 盘后不自动扩展根分区,需手动 growpart + xfs_growfs
缺少基础组件sudo、gcloud CLI、perftest、CUDA Toolkit 均未预装,需手动安装
Docker CE repo需硬编码 RHEL 9 baseurl,TLinux 4 不被 Docker 官方自动识别

14.3 k8s 1.34 DRA + ComputeDomain 注意事项

项目说明
DRA API 版本ResourceClaimTemplate/DeviceClass 使用 resource.k8s.io/v1(GA API,非 v1beta2)
DRA 状态k8s 1.34 中 DRA 为 GA,默认启用,无需 feature gate
DRANET 版本v1.3.0,Helm chart: oci://registry.k8s.io/networking/charts/dranet
ComputeDomainComputeDomain 始终按物理域大小配置 IMEX(18 节点),即使 numNodes: 2。<18 节点环境下 IMEX daemon 会持续尝试连接不存在的节点(无害但有日志噪音)。已验证可在部分域环境正常工作(836 GB/s MNNVL)
ComputeDomain 互斥ComputeDomain、IMEX Manager DaemonSet、主机级 nvidia-imex systemd 服务三者互斥,同一时刻只能有一个持有 IMEX session,否则报 NV_ERR_IN_USE
NCCL 测试二进制GIB v1.1.2 自带的 /usr/local/bin/all_reduce_perf 未链接 MPI,多节点测试会退化为独立单 GPU 基准。必须从 nccl-tests 源码编译 MPI 版:make MPI=1 MPI_HOME=/usr/local/mpi NCCL_HOME=/usr/local/gib
mpirun 路径/usr/local/mpi/bin/mpirun(非 /usr/local/gib/bin/mpirun
Calico 多网卡A4X Worker 有 6 个 NIC (2 GVNIC + 4 MRDMA),Calico 需设置 IP_AUTODETECTION_METHOD=interface=eth0 避免选取 MRDMA 接口 IP
SSH 密钥容器内必须用 ed25519ssh-keygen -t ed25519),RSA 因无 /dev/tty 会失败

14.4 双路径差异对比

特性路径 A — DRANET路径 B — hostNetwork
RDMA 访问DRA ResourceClaimTemplate + DeviceClasshostNetwork: true
Pod 网络独立 CNI 网络命名空间共享主机网络栈
SSH 端口2222(客户要求)2222(避免与主机 sshd 冲突,客户要求)
DNS自动(CNI 配置)需设置 dnsPolicy: ClusterFirstWithHostNet(YAML 中已配置)
DRANET DaemonSet需要安装不需要
DeviceClass需要创建 rdma-devices不需要
IMEX 管理ComputeDomain (DRA GPU Driver)ComputeDomain (DRA GPU Driver)
GPU 分配nvidia-device-plugin (nvidia.com/gpu: 4)nvidia-device-plugin (nvidia.com/gpu: 4)
端口冲突风险低(独立网络命名空间)高(共享主机端口)
安全性更好(网络隔离)较低(privileged + hostNetwork)

14.4 短主机名(Megatron 必需)

Megatron-LM 使用 Gloo 进行进程通信。如果 Pod hostname 过长,会触发 File name too long 错误。解决方案:在 Pod spec 中设置 hostname 字段为短名称(如 mega-h1)。

14.5 DeepEP v2 Internode 测试

DeepEP v2 internode 使用 Elastic EP 测试 (tests/elastic/test_ep.py),基于 NCCL GIN 后端。不要使用 tests/legacy/test_internode.py,该测试会因 LEGACY_NUM_MAX_NVL_PEERS=8 assertion 在 4-GPU A4X 上失败。

14.6 Megatron 多节点 hostNetwork hostname 解析

hostNetwork Pod 设置自定义 hostname(如 mega-h1)时,NCCL bootstrap 和 Gloo 会调用 gethostname()gethostbyname() 获取自身 IP。如果该 hostname 没有 DNS 记录,解析失败会导致 静默 hang(不报错,进程无限等待)。

症状initialized tensor model parallel 后不再有输出,进程占用 100% CPU。

修复:在每个容器的 /etc/hosts 中添加所有节点的 hostname→IP 映射:

# 在 mega-host-1 和 mega-host-2 中都执行
echo "<HOST1_IP> mega-h1" >> /etc/hosts
echo "<HOST2_IP> mega-h2" >> /etc/hosts

根因:k8s 1.22 的 Megatron YAML 未设置 hostname 字段,容器继承宿主机真实 hostname(可正常解析),因此未遇此问题。
替代方案:不设置 Pod hostname 字段,让容器使用宿主机默认 hostname;或配合 headless Service + subdomain 字段使 k8s DNS 自动注册。
同时需设置 NCCL_SOCKET_IFNAME=eth0GLOO_SOCKET_IFNAME=eth0 确保网络接口选择正确。

14.7 ib_write_bw IBVERBS 错误

GIB 的 libibverbs 库 (/usr/local/gib/lib64/) 可能覆盖系统版本。运行 ib_write_bw 前需切换:export LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu

14.8 DeepEP 构建 libnccl.so 缺失

GIB 提供 libnccl.so.2 但无 libnccl.so 符号链接。构建 DeepEP 时需创建:ln -sf /usr/local/gib/lib64/libnccl.so.2 /usr/local/gib/lib64/libnccl.so

14.9 DeepEP SM_100 (Blackwell) 编译

DeepEP 需从源码编译(pip 版本无 SM_100 支持)。关键配置:

14.10 Megatron-LM core_v0.17.0 参数变更

15. ComputeDomain 深入指南

定位:ComputeDomain 是 NVIDIA DRA GPU Driver(v25.12.0+)提供的 CRD,通过 DRA 机制管理 IMEX daemon 生命周期。本文档以 ComputeDomain 作为唯一的 IMEX 管理方案,以 NVL72 物理域(18 节点 / 72 GPU)为最小资源划分单元。
实测结论:在 k8s 1.34 + 部分 NVL72 域(2/18 节点)环境下,ComputeDomain 可正常工作,NCCL all_reduce 达到 836 GB/s

15.1 ComputeDomain 概述

概念说明
ComputeDomain CRD定义需要 IMEX 通信的节点组。API: resource.nvidia.com/v1beta1
ComputeDomainClique控制器根据物理 NVLink 域自动创建的子资源,记录域内所有节点的 IP 和状态
Daemon DaemonSet控制器自动创建的 DaemonSet,在每个域内节点上运行 nvidia-imex daemon pod
ResourceClaimTemplate控制器自动创建的模板(名称由 ComputeDomain spec 指定),Pod 通过 resourceClaimTemplateName 引用获取 IMEX channel
DeviceClasscompute-domain-default-channel.nvidia.com(自动创建)和 compute-domain-daemon.nvidia.com

工作流程

  1. 用户创建 ComputeDomain 资源(指定 numNodes
  2. DRA GPU Driver 控制器检测物理 NVLink 域,创建 ComputeDomainClique
  3. 控制器创建 daemon DaemonSet → 在域内节点上启动 IMEX daemon pods
  4. 控制器创建 ResourceClaimTemplate(自动生成)
  5. IMEX daemon pods 通过 pod CIDR 网络互联(port 50000),建立 IMEX session
  6. ComputeDomain 状态变为 Ready
  7. 用户 Pod 通过 resourceClaimTemplateName 获取 compute-domain-channel → 获得 /dev/nvidia-caps-imex-channels/channel0

15.2 域发现与调度机制

DRA GPU Driver 如何发现物理域

DRA GPU Driver 在每个 GPU 节点上运行 kubelet plugin,读取 GPU 硬件属性(包括 NVLink ClusterUUID),通过 ResourceSlice 发布到 k8s API。ComputeDomain 控制器基于 ResourceSlice 中的 ClusterUUID 对节点分组,自动创建 ComputeDomainClique

# 查看节点的 NVLink 域标签
kubectl get nodes -L nvidia.com/gpu.clusteruuid

# 查看 ResourceSlice(包含 GPU 和 NVLink 拓扑)
kubectl get resourceslice -o wide | head -20

# 查看 ComputeDomainClique(每个物理域一个)
kubectl get computedomainclique -o wide

调度机制

Pod 通过 resourceClaimTemplateName 引用 ComputeDomain 的 IMEX channel。DRA 调度器插件确保 Pod 只调度到 ComputeDomain daemon 已就绪的节点上。结合 nodeAffinitynvidia.com/gpu.clusteruuid)可将 Pod 限定到特定物理域。

RDMA 方式调度约束适用场景
DRANETDRA 调度器自动处理 GPU + RDMA NIC + IMEX channel 的协同分配推荐方案。网络隔离好,DRA 原生集成
hostNetworkPod 使用 hostNetwork: true,需设置 dnsPolicy: ClusterFirstWithHostNetNCCL_SOCKET_IFNAME=eth0备选方案。无需 DRANET,部署更简单

15.3 常见问题排查

症状原因解决方案
NV_ERR_IN_USE: Failed to allocate Imex session Host 级 nvidia-imex systemd 服务或 IMEX Manager DaemonSet 仍在运行,占用 IMEX channels systemctl stop nvidia-imex && systemctl disable nvidia-imex
kubectl delete ds imex-manager -n kube-system
ComputeDomain status: NotReady,daemon pods 0/1 Daemon pods 无法通过 pod CIDR 互联(port 50000)。Calico IPIP 隧道流量(protocol 4)被 VPC 防火墙阻断 添加 IPIP 协议防火墙规则:
gcloud compute firewall-rules create allow-ipip --allow=4 --source-ranges=192.168.0.0/16
或切换 Calico 为 VXLAN 模式(使用 UDP,已被现有规则允许)
IMEX daemon 日志显示 Attempting connection for target node N(N=2~17) ComputeDomain 按物理域大小(18 节点)配置 IMEX,即使实际只有 2 节点。不存在的 16 个 peer 永远连不上 正常行为。IMEX 设置 IMEX_WAIT_FOR_QUORUM != FULL,不等待所有节点即可工作。日志噪音可忽略
FailedResourceClaimCreation: resourceclaimtemplate not found ComputeDomain 刚创建时,ResourceClaimTemplate 尚未被控制器生成(需 1-3 秒) 等待。控制器创建 ResourceClaimTemplate 后 kubelet 自动重试
NodePrepareResources: DeadlineExceeded DRA kubelet plugin 等待 ComputeDomain 变为 Ready,但 IMEX daemon 未就绪 检查上述 NV_ERR_IN_USE 和 pod CIDR 连通性问题。ComputeDomain Ready 后 kubelet 自动重试

15.4 64+8 备用容量与 ComputeDomain 兼容性

背景

NVL72 域包含 18 个节点(72 GPU)。生产环境中常见的资源策略是 64+8

详细方案见第 9 节 — 64+8 备用容量管理

关键结论:64+8 与 ComputeDomain 完全兼容

64+8 是"整域分配内部的优化策略",不是"域内共享"。
整个域(72 GPU)属于同一个租户,只是该租户选择用 64 GPU 跑训练、8 GPU 做备用。ComputeDomain 覆盖整域所有 18 节点,spare 节点上的 daemon pod 保持 IMEX channel 就绪,故障替换时零延迟。

ComputeDomain 下的 64+8 方案

工作原理

  1. 为整域创建一个 ComputeDomain → daemon pod 在全部 18 节点上运行 → IMEX 覆盖整域
  2. 训练 Pod(高优先级)调度到 16 个节点,每个 Pod 通过 ResourceClaim 获取 IMEX channel
  3. Placeholder Pod(低优先级)调度到 2 个 spare 节点,不需要 IMEX channel ResourceClaim
  4. spare 节点上 IMEX daemon 已经在运行,IMEX channel 已就绪(但无人使用)

节点故障恢复流程

# 初始状态
域内 18 节点:
  Node 0~15: 训练 Pod(高优先级, 带 ComputeDomain channel ResourceClaim)
  Node 16~17: Placeholder Pod(低优先级, 无 IMEX channel)
  全部 18 节点: ComputeDomain daemon pod running ✓

# Node 5 故障 →
  Node 5 上的训练 Pod 被驱逐
  训练框架创建替换 Pod(高优先级)
  → 替换 Pod 抢占 Node 16 上的 Placeholder(PriorityClass 抢占)
  → Node 16 上 IMEX daemon 已在运行 ✓
  → 替换 Pod 的 ResourceClaim 分配 IMEX channel → 立即可用 ✓
  → 训练恢复(16 节点: 0~4, 6~16)

ComputeDomain 下的 YAML 示例(结合第 9 节的 PriorityClass):

# 1. ComputeDomain — 覆盖整域
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
  name: training-domain
spec:
  numNodes: 18    # 覆盖整域所有节点
  channel:
    resourceClaimTemplate:
      name: training-imex-channel

---
# 2. 训练 Pod(高优先级,带 IMEX channel)
apiVersion: v1
kind: Pod
metadata:
  name: training-worker-0
spec:
  priorityClassName: training-high-priority   # 见第 9 节
  nodeSelector:
    nvidia.com/gpu.clusteruuid: "804c7837-..."   # 指定域
  containers:
  - name: trainer
    image: nvcr.io/nvidia/pytorch:25.04-py3
    resources:
      limits:
        nvidia.com/gpu: 4
      claims:
      - name: imex-channel
      - name: rdma-nics      # 如使用 DRANET
  resourceClaims:
  - name: imex-channel
    resourceClaimTemplateName: training-imex-channel   # ComputeDomain 自动创建
  - name: rdma-nics
    resourceClaimTemplateName: rdma-nics-worker-0

---
# 3. Placeholder Pod(低优先级,不需要 IMEX channel)
apiVersion: v1
kind: Pod
metadata:
  name: spare-placeholder-0
spec:
  priorityClassName: spare-low-priority   # 见第 9 节
  nodeSelector:
    nvidia.com/gpu.clusteruuid: "804c7837-..."
  containers:
  - name: placeholder
    image: registry.k8s.io/pause:3.9
    resources:
      limits:
        nvidia.com/gpu: 4
  # 注意:不需要 resourceClaims — placeholder 不使用 MNNVL

15.5 跨域训练:2 域 ComputeDomain + DRANET 示例

场景

训练任务需要 2 个完整 NVLink 域的算力:36 节点 × 4 GPU = 144 GPU。

┌── Domain A (ClusterUUID: 804c7837) ──┐    ┌── Domain B (ClusterUUID: 89164aa7) ──┐
│ node 0~17 (18 nodes, 72 GPU)         │    │ node 0~17 (18 nodes, 72 GPU)         │
│ 域内通信: MNNVL ~836 GB/s            │    │ 域内通信: MNNVL ~836 GB/s            │
└──────────────┬───────────────────────┘    └──────────────┬──────────────────────┘
               └────────── 跨域通信: RDMA ~327 GB/s ──────┘
                        (NCCL 自动选择路径,无需配置)

核心设计

1. 一个 ComputeDomain 覆盖所有域

apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
  name: training-job-cd
spec:
  numNodes: 36                          # 2 域 × 18 节点
  channel:
    resourceClaimTemplate:
      name: training-job-imex-channel   # 控制器自动创建

控制器自动为每个物理域创建独立的 ComputeDomainClique。域内 daemon pods 互联建立 IMEX session,跨域 daemon pods 不互联(IMEX session 是域级的)。

2. 一个共享的 RDMA ResourceClaimTemplate

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: training-job-rdma
spec:
  spec:
    devices:
      requests:
      - name: rdma-nics
        exactly:
          deviceClassName: rdma-devices
          count: 4

所有 36 个 Pod 引用同一个模板。每个 Pod 实例化时,k8s 从模板生成独立的 ResourceClaim,DRANET 为每个 Pod 分配 4 个 RDMA NIC。

3. 每个 Pod 只有 nodeAffinity 不同

# Domain A 的 Pod (rank 0~17)
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: nvidia.com/gpu.clusteruuid
          operator: In
          values: ["804c7837-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]   # Domain A
  podAntiAffinity:     # 确保每个 Pod 落在不同节点
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: job
          operator: In
          values: ["training-job-2domain"]
      topologyKey: kubernetes.io/hostname

# Domain B 的 Pod (rank 18~35): 仅 clusteruuid 值不同
values: ["89164aa7-yyyy-yyyy-yyyy-yyyyyyyyyyyy"]             # Domain B

4. 每个 Pod 的 resourceClaims 完全相同

resourceClaims:
- name: compute-domain-channel
  resourceClaimTemplateName: training-job-imex-channel   # 所有 Pod 共用
- name: rdma-nics
  resourceClaimTemplateName: training-job-rdma            # 所有 Pod 共用

平台 Job 控制器生成 Pod 的伪代码

# 平台调度器分配了 2 个域给训练任务
allocated_domains = [
    {"uuid": "804c7837-...", "node_count": 18},   # Domain A
    {"uuid": "89164aa7-...", "node_count": 18},   # Domain B
]

# 1. 创建 ComputeDomain(覆盖所有域)
total = sum(d["node_count"] for d in allocated_domains)   # 36
create_compute_domain("training-job-cd", num_nodes=total)

# 2. 创建共享 RDMA ResourceClaimTemplate
create_rdma_template("training-job-rdma", count=4)

# 3. 为每个节点生成 Pod — 唯一差异是 nodeAffinity 的 ClusterUUID
rank = 0
for domain in allocated_domains:
    for i in range(domain["node_count"]):
        create_pod(
            name=f"train-{rank}",
            cluster_uuid=domain["uuid"],
            imex_template="training-job-imex-channel",
            rdma_template="training-job-rdma",
            gpu_count=4, rank=rank,
        )
        rank += 1

NCCL 通信行为(自动,无需配置)

通信场景NCCL 自动选择的路径预期带宽
同节点 GPU 间NVLink (node-internal)~604 GB/s
同域跨节点 GPU 间MNNVL (ComputeDomain IMEX)~836 GB/s
跨域 GPU 间RDMA (DRANET CX-7 400Gbps)~327 GB/s

NCCL 通过 NCCL_MNNVL_ENABLE=2 自动检测 NVLink 域拓扑,域内用 MNNVL,跨域降级到 RDMA。训练代码无需区分。

完整 YAML 参考yamls/k8s134-nccl-2domain-computedomain-dranet.yaml(包含 4 个示例 Pod,每域 2 个)

15.6 跨域 64+8:2 域备用容量 + ComputeDomain 示例

场景

在 15.5 的 2 域训练基础上,每个域预留 2 个节点(8 GPU)作为备用容量,用于节点故障时快速替换。

Domain A (18 nodes, 72 GPU)                  Domain B (18 nodes, 72 GPU)
┌───────────────────────────────────┐       ┌───────────────────────────────────┐
│ node 0~15: 训练 Pod (64 GPU)      │       │ node 0~15: 训练 Pod (64 GPU)      │
│   priorityClassName: training-high │       │   priorityClassName: training-high │
│   resourceClaims: [imex, rdma]     │       │   resourceClaims: [imex, rdma]     │
│───────────────────────────────────│       │───────────────────────────────────│
│ node 16~17: Placeholder (8 GPU)   │       │ node 16~17: Placeholder (8 GPU)   │
│   priorityClassName: spare-low     │       │   priorityClassName: spare-low     │
│   无 resourceClaims               │       │   无 resourceClaims               │
│───────────────────────────────────│       │───────────────────────────────────│
│ 全 18 节点: ComputeDomain daemon ✓│       │ 全 18 节点: ComputeDomain daemon ✓│
└───────────────────────────────────┘       └───────────────────────────────────┘
训练: 32 nodes, 128 GPU
备用:  4 nodes,  16 GPU

与纯 2 域方案(15.5)的 3 个差异

新增内容作用
PriorityClass: training-high-priority (value=1000000)训练 Pod 可抢占低优先级 Placeholder
PriorityClass: spare-low-priority (value=100, preemptionPolicy: Never)Placeholder 被抢占时立即释放节点,但自身不能抢占其他 Pod
每域 2 个 Placeholder Pod占住 spare 节点的 GPU 资源,等待被抢占替换

关键设计要点

1. ComputeDomain numNodes: 36 — 必须包含 spare 节点

不能写 32(只算训练节点)。如果 spare 节点没有 ComputeDomain daemon,故障恢复时替换 Pod 的 IMEX channel ResourceClaim 无法满足,Pod 会卡在 Pending。

2. Placeholder Pod 不需要 IMEX channel 和 RDMA claim

# Placeholder — 只占 GPU,不做计算
containers:
- name: placeholder
  image: registry.k8s.io/pause:3.9    # 极低资源消耗
  resources:
    limits:
      nvidia.com/gpu: 4                # 占住 GPU,阻止其他 Pod 使用
# 没有 resourceClaims
#   → IMEX channel 和 RDMA NIC 保持空闲
#   → 替换训练 Pod 到来时可直接 claim,零延迟

3. 替换 Pod 的 nodeAffinity 必须指向故障节点所在的域

# Domain A node-5 故障 → 替换 Pod 的 nodeAffinity:
values: ["804c7837-xxxx"]    # 必须是 Domain A,不能跨域替换!
# MNNVL 是域内物理链路,跨域替换会导致训练拓扑不一致

4. preemptionPolicy: Never 防止 Placeholder 抢占其他 Pod

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: spare-low-priority
value: 100
preemptionPolicy: Never    # Placeholder 只能被抢占,不能主动抢占

故障恢复时序

t=0    Domain A node-5 硬件故障
t=~5s  node-5 NotReady → 训练 Pod (rank 5) 被驱逐
t=~10s 训练框架检测到 rank 5 丢失
       → 创建替换 Pod:
           priorityClassName: training-high-priority
           nodeAffinity: clusteruuid=804c7837 (Domain A)
           resourceClaims: [imex-channel, rdma-nics]

t=~12s 调度器发现 Domain A 无空闲 GPU 节点
       → 发现 spare-a-0 (spare-low-priority) 可被抢占
       → 驱逐 spare-a-0 → 调度替换 Pod 到该节点

t=~15s 替换 Pod 启动:
       IMEX channel: ComputeDomain daemon 已在运行 → claim 满足 ✓
       RDMA NIC:     DRANET 分配 4 个 CX-7           ✓
       GPU:          nvidia-device-plugin 分配 4 GPU  ✓

t=~30s 训练从 checkpoint 恢复

完整 YAML 参考yamls/k8s134-2domain-64plus8-computedomain-dranet.yaml(包含 PriorityClass + ComputeDomain + 每域各 1 个训练 Pod 和 2 个 Placeholder 示例)

16. TLinux 4.0 ARM64 自定义 GCE 镜像构建

GB200 A4X Worker 节点使用 TencentOS Server 4.0 ARM64 自定义镜像。由于 GCE 官方不提供 TencentOS 预构建镜像,需要在一台 ARM64 Builder VM 上通过 dnf installroot 跨盘构建系统,编译 NVIDIA Open Kernel 驱动,并封装为 GCE 自定义镜像。

为何不能直接用 Rocky Linux 9?
客户要求使用 TencentOS Server 4.0(基于 EL9、内核 6.6)。核心挑战在于:Google 预编译的 GCE Guest Agent 依赖 libboost_regex.so.1.75.0libicu*.so.67,而 TLinux 4 仅提供更高版本(boost 1.82、libicu 71/72)。由于 ABI 不兼容,直接软链接会导致 OS Login 崩溃、SSH via IAP 失败。本节包含完整的修复方案。

16.1 构建环境变量

在 Builder VM 上声明以下变量,后续所有命令直接引用:

export PROJECT_ID="gpu-launchpad-playground"
export ZONE="us-central1-a"
export BUILDER_VM="tlinux-builder"
export DISK_DEVICE="/dev/nvme0n2"          # Builder VM 附加的 50GB 数据盘
export MOUNT_DIR="/mnt/tlinux"
export IMAGE_NAME="tlinux-server-4-gb200-v2"
export SNAPSHOT_NAME="tlinux-v4-snap-v2"
export NVIDIA_VERSION="580.126.20"         # R580 Tesla 驱动版本

16.2 磁盘分区与文件系统准备

在附加的 50GB 数据盘上建立 GPT 分区表:512MB UEFI ESP(FAT32)+ 剩余空间根分区(XFS)。

# 安装分区工具
sudo dnf install -y parted gdisk xfsprogs wget curl

# GPT 分区:EFI 512MB + 系统根分区
sudo parted -s "$DISK_DEVICE" mklabel gpt
sudo parted -s "$DISK_DEVICE" mkpart primary fat32 1MiB 513MiB
sudo parted -s "$DISK_DEVICE" set 1 esp on
sudo parted -s "$DISK_DEVICE" mkpart primary xfs 513MiB 100%

# 刷新分区表
sudo partprobe "$DISK_DEVICE"
sleep 3

# 格式化
sudo mkfs.vfat -F32 "${DISK_DEVICE}p1"
sudo mkfs.xfs -f "${DISK_DEVICE}p2"

# 挂载
sudo mkdir -p "$MOUNT_DIR"
sudo mount "${DISK_DEVICE}p2" "$MOUNT_DIR"
sudo mkdir -p "$MOUNT_DIR/boot/efi"
sudo mount "${DISK_DEVICE}p1" "$MOUNT_DIR/boot/efi"

16.3 TencentOS 4.0 基础系统安装

配置腾讯镜像源,通过 dnf installroot 跨盘安装基础系统和编译依赖(含 RDMA 用户态库)。

# 创建临时镜像源配置
cat <<EOF | sudo tee /tmp/tlinux4.repo
[tlinux4-baseos]
name=TencentOS Server 4.0 BaseOS
baseurl=https://mirrors.tencent.com/tlinux/4.0/BaseOS/aarch64/os/
enabled=1
gpgcheck=0
retries=20

[tlinux4-appstream]
name=TencentOS Server 4.0 AppStream
baseurl=https://mirrors.tencent.com/tlinux/4.0/AppStream/aarch64/os/
enabled=1
gpgcheck=0
retries=20
EOF

# 跨盘安装 TencentOS 4.0 核心系统
sudo dnf -y --installroot="$MOUNT_DIR" --config=/tmp/tlinux4.repo --releasever=4.0 \
    --disablerepo="*" --enablerepo="tlinux4-*" \
    --setopt=max_parallel_downloads=1 \
    install \
    systemd kernel kernel-core kernel-modules kernel-devel kernel-headers \
    grub2-efi-aa64 shim-aa64 efibootmgr dnf iproute passwd \
    openssh-server openssh-clients NetworkManager tar gzip git make gcc \
    elfutils-libelf-devel binutils kmod pciutils lshw wget curl lsof rsync tmux \
    rdma-core rdma-core-devel librdmacm librdmacm-utils libibverbs ucx-ib mstflint

16.4 Chroot 环境初始化

绑定挂载宿主机虚拟设备到目标系统,然后进入 chroot 配置 fstab 和 GRUB。

# 绑定挂载虚拟设备
sudo mount --bind /dev "$MOUNT_DIR/dev"
sudo mount --bind /dev/pts "$MOUNT_DIR/dev/pts"
sudo mount --bind /proc "$MOUNT_DIR/proc"
sudo mount --bind /sys "$MOUNT_DIR/sys"
sudo mount --bind /sys/firmware/efi/efivars "$MOUNT_DIR/sys/firmware/efi/efivars" || true

# 同步 DNS 和镜像源
sudo cp /etc/resolv.conf "$MOUNT_DIR/etc/resolv.conf"
sudo mkdir -p "$MOUNT_DIR/etc/yum.repos.d"
sudo cp /tmp/tlinux4.repo "$MOUNT_DIR/etc/yum.repos.d/tlinux4.repo"

fstab 与 GRUB 配置

进入 chroot 后,根据分区 UUID 构建启动挂载体系。需要修正从宿主机残留的 BLS Root UUID。

sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex

# 读取分区 UUID
ROOT_UUID=$(blkid -o value -s UUID /dev/nvme0n2p2)
EFI_UUID=$(blkid -o value -s UUID /dev/nvme0n2p1)

# 写入 fstab
cat <<FSTAB_CFG > /etc/fstab
UUID=$ROOT_UUID / xfs defaults 0 0
UUID=$EFI_UUID /boot/efi vfat defaults,uid=0,gid=0,umask=0077,shortname=winnt 0 2
FSTAB_CFG

# GRUB 配置:GCE ARM64 PL011 串口 + 屏蔽 nouveau
cat <<'GRUB_CFG' > /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="TencentOS"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="console=ttyAMA0,115200 earlycon=pl011,mmio32,0x09000000 rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvme_core.io_timeout=255"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_CFG

# 生成 UEFI 启动配置
grub2-mkconfig -o /boot/grub2/grub.cfg
grub2-mkconfig -o /boot/efi/EFI/tencentos/grub.cfg || true

EOF

16.5 GCE Guest Agent 依赖注入与修复

OS Login libicu 67 修复(关键步骤)
Google 预编译的 google_oslogin_nss_cache 硬编码链接了 Rocky 9 的 libicu*.so.67,而 TLinux 4 仅有 libicu 71/72。由于大版本间 ABI 签名已变更,软链接欺骗(ln -s libicu.so.71 libicu.so.67)会导致运行时崩溃。必须从 Rocky 9 提取原生 libicu 67 库文件注入。
# 1. 注入 boost-regex 1.75.0(GCE Guest Agent 依赖)
wget -O /tmp/boost-regex-1.75.0-13.el9_7.aarch64.rpm \
    https://dl.rockylinux.org/pub/rocky/9/BaseOS/aarch64/os/Packages/b/boost-regex-1.75.0-13.el9_7.aarch64.rpm
sudo rpm -ivh --nodeps --root="$MOUNT_DIR" /tmp/boost-regex-1.75.0-13.el9_7.aarch64.rpm

# 2. 提取 Rocky 9 原生 libicu 67 并注入
dnf download libicu --destdir=/tmp || yumdownloader libicu --destdir=/tmp
mkdir -p /tmp/icu67-extract
rpm2cpio /tmp/libicu-67.1-*.el9_*.aarch64.rpm | cpio -idmv -D /tmp/icu67-extract/
sudo cp -d /tmp/icu67-extract/usr/lib64/libicu*.so.67* "$MOUNT_DIR/usr/lib64/"
rm -rf /tmp/icu67-extract /tmp/libicu-67.1-*.el9_*.aarch64.rpm

# 3. 在 chroot 内安装 GCE Guest Agent
sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex

# 验证 OS Login 动态库已补全
ldd /usr/bin/google_oslogin_nss_cache

# 配置 GCE Agent 软件源
cat <<'GCREPO' > /etc/yum.repos.d/google-cloud.repo
[google-compute-engine]
name=Google Compute Engine
baseurl=https://packages.cloud.google.com/yum/repos/google-compute-engine-el9-aarch64-stable
enabled=1
gpgcheck=0

[google-cloud-sdk]
name=Google Cloud SDK
baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el9-aarch64
enabled=1
gpgcheck=0
GCREPO

# 安装并启用 GCE 组件
dnf install -y google-compute-engine google-guest-agent google-osconfig-agent
systemctl enable NetworkManager sshd
systemctl enable google-guest-agent google-startup-scripts google-shutdown-scripts google-osconfig-agent

EOF

16.6 NVIDIA GPU Open Kernel 驱动编译

GB200 需要 Open Kernel Module(--kernel-module-type=open)。闭源驱动在 NVLink 跨节点高速互联和 TCPXo DMA-BUF 处理上存在兼容问题。

sudo chroot "$MOUNT_DIR" /bin/bash <<EOF
set -ex

# 下载 R580 Tesla 驱动
curl -L "https://us.download.nvidia.com/tesla/${NVIDIA_VERSION}/NVIDIA-Linux-aarch64-${NVIDIA_VERSION}.run" \
    -o /tmp/nvidia.run

# 屏蔽 nouveau
cat <<'NOUVEAU' > /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
options nouveau modeset=0
NOUVEAU

# 查找内核版本
KERNEL_VER=\$(rpm -q --qf "%{VERSION}-%{RELEASE}.%{ARCH}\n" kernel | head -n 1)

# 编译安装 Open Kernel Module
sh /tmp/nvidia.run \
    --silent \
    --no-questions \
    --accept-license \
    --kernel-module-type=open \
    --kernel-name="\${KERNEL_VER}" \
    --kernel-source-path="/lib/modules/\${KERNEL_VER}/build" \
    --utility-prefix="/usr" \
    --opengl-prefix="/usr"

rm -f /tmp/nvidia.run
EOF

16.7 Fabric Manager 与 IMEX 部署

Fabric Manager 和 IMEX 的 RPM 版本必须与 GPU 驱动版本(${NVIDIA_VERSION})完全一致。将预先下载的 RPM 文件注入 chroot 安装。

# 将版本匹配的 RPM 复制到目标盘
sudo cp /tmp/nvidia-fabricmanager.rpm "$MOUNT_DIR/tmp/"
sudo cp /tmp/nvidia-imex.rpm "$MOUNT_DIR/tmp/"

sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex

# 安装 FM 和 IMEX
dnf localinstall -y /tmp/nvidia-fabricmanager.rpm /tmp/nvidia-imex.rpm

# 配置 IMEX channel 参数(Blackwell 必需)
echo "options nvidia NVreg_CreateImexChannel0=1" > /etc/modprobe.d/nvidia-imex.conf

# 启用开机自启
systemctl enable nvidia-fabricmanager
systemctl enable nvidia-imex

rm -f /tmp/nvidia-fabricmanager.rpm /tmp/nvidia-imex.rpm
EOF

清理与安全卸载

删除临时文件,按依赖顺序递归卸载,避免镜像中残留设备锁。

# 删除 chroot 临时文件
sudo rm -f "$MOUNT_DIR/etc/resolv.conf"
sudo rm -f "$MOUNT_DIR/etc/yum.repos.d/tlinux4.repo"

# 按依赖顺序卸载
sudo umount "$MOUNT_DIR/sys/firmware/efi/efivars" || true
sudo umount "$MOUNT_DIR/sys" || true
sudo umount "$MOUNT_DIR/proc" || true
sudo umount "$MOUNT_DIR/dev/pts" || true
sudo umount "$MOUNT_DIR/dev" || true
sudo umount "$MOUNT_DIR/boot/efi" || true
sudo umount "$MOUNT_DIR" || true

# 验证无残留挂载
mount | grep "$MOUNT_DIR" || echo "磁盘已完全卸载"

16.8 GCE 镜像封装

将构建完成的数据盘快照并生成支持 UEFI + GVNIC + IDPF 的 ARM64 自定义镜像。

# 创建磁盘快照
gcloud compute snapshots create "$SNAPSHOT_NAME" \
    --project="$PROJECT_ID" \
    --source-disk="tlinux-root-disk" \
    --source-disk-zone="$ZONE" \
    --storage-location="us"

# 从快照生成自定义镜像(含 UEFI、GVNIC、IDPF 网卡特性)
gcloud compute images create "$IMAGE_NAME" \
    --project="$PROJECT_ID" \
    --source-snapshot="$SNAPSHOT_NAME" \
    --guest-os-features="UEFI_COMPATIBLE,GVNIC,IDPF" \
    --architecture="ARM64" \
    --storage-location="us"

完成后,镜像名称(如 tlinux-server-4-gb200-v2)即可用于第 2.2 节 Worker VM 创建中的 --image 参数。

16.9 部署验证

用一台非 GPU 的 ARM64 VM 验证镜像的基本启动和 GCE Agent 功能。

# 创建测试 VM
gcloud compute instances create tlinux-test-boot \
    --project="$PROJECT_ID" \
    --zone="$ZONE" \
    --machine-type="t2a-standard-4" \
    --image="$IMAGE_NAME" \
    --network-interface=nic-type=GVNIC,subnet=default \
    --metadata=enable-oslogin=TRUE

验证清单

检查项命令预期结果
内核版本 uname -r TencentOS 6.6.x 内核
GCE Guest Agent systemctl status google-guest-agent active (running)
OS Login SSH gcloud compute ssh tlinux-test-boot --tunnel-through-iap SSH 连接成功
GPU 驱动(需 GPU VM) nvidia-smi 显示 R580 驱动版本和 GPU 信息
Fabric Manager systemctl is-enabled nvidia-fabricmanager nvidia-imex enabled
串行日志 gcloud compute instances get-serial-port-output tlinux-test-boot 无 google-oslogin-cache 错误

文档生成于 2026-06-17 · GCP GPU Infrastructure Team
基于 gpu-launchpad-playground 项目 us-east1-d 区域 GB200 A4X 集群验证