分布式训练最难排查的故障,往往不是 Pod 崩溃,而是所有组件看起来都正常:Kubeflow 任务处于 Running,Pod 没有重启,也没有 OOMKill,但 GPU 大部分时间没有计算。来源摘要描述的正是这种反直觉现象:训练任务健康运行,GPU 却有约 60% 的时间处于空闲状态。
这类问题不能只看 Kubernetes 控制面。分布式训练依赖 NCCL 等通信库持续交换梯度,一个慢节点、一次重传或一条被策略影响的连接,都可能让其余 GPU 停下来等待。结合标题中的 Cilium,排障重点应从“Pod 是否存活”转向“训练进程是否在等待网络”。摘要没有披露最终根因,因此下面给出的是一套可以落地的诊断方法,而不是对原案例结论的推测。
Running 不等于训练正在推进
Kubernetes 的健康状态只能回答容器是否存活、探针是否通过,无法证明 GPU 一直有计算任务。同步数据并行训练通常按以下节奏运行:
- 每张 GPU 执行前向和反向计算。
- Worker 通过 AllReduce 聚合梯度。
- 所有 Worker 完成同步后进入下一步。
只要一个 Worker 的网络路径变慢,其他 Worker 就会卡在集合通信屏障上。此时进程没有崩溃,显存仍被占用,Pod 也保持 Running,但 GPU 利用率会呈现周期性下降。平均值可能只有 40%,却看不出计算阶段与等待阶段的边界。
先把任务、节点和 GPU 的状态放在同一条时间线上。下面的命令可以直接运行,只需修改命名空间和标签:
NS=kubeflow
SELECTOR='training.kubeflow.org/job-name=my-training-job'
kubectl get pods -n "$NS" -l "$SELECTOR" -o wide
kubectl top pods -n "$NS" -l "$SELECTOR" --containers
kubectl get events -n "$NS" --sort-by=.lastTimestamp | tail -n 50
for pod in $(kubectl get pods -n "$NS" -l "$SELECTOR" -o name); do
echo "===== $pod ====="
kubectl logs -n "$NS" "$pod" --tail=100 | tail -n 30
done
这里要找的不是单一错误,而是相关性:某个 Worker 是否总在同一步停顿,它是否固定运行在某个节点,利用率下降时是否伴随 NCCL 超时、连接重建或 Kubernetes 网络事件。
把 NCCL 等待变成可观察证据
如果训练框架使用 NCCL,可以临时提高日志级别,并明确记录网络子系统。下面是一个可改造到 Kubeflow PyTorchJob Pod 模板中的容器片段。字段结构会随 Kubeflow CRD 版本变化,因此应合并到现有 spec.pytorchReplicaSpecs.*.template.spec.containers,不要直接覆盖完整任务:
containers:
- name: pytorch
env:
- name: NCCL_DEBUG
value: INFO
- name: NCCL_DEBUG_SUBSYS
value: INIT,NET,COLL
- name: TORCH_DISTRIBUTED_DEBUG
value: DETAIL
重新运行一个短基准任务后,重点检查:
- 各 Worker 是否选择了预期的网卡和传输方式。
- AllReduce 卡顿是否总与同一个 rank 相关。
- 是否出现连接超时、重试、接口回退或通信初始化耗时异常。
- 每个训练 step 的耗时是否与 GPU 利用率低谷对齐。
调试日志可能非常大,也可能降低训练速度。应只在缩短后的复现任务中启用,并确保日志系统不会因为突发写入产生新的瓶颈。
从 Cilium 数据面验证丢包、拒绝与路径差异
标题把 Kubeflow 和 Cilium 放在一起,意味着网络数据面值得单独检查。Cilium 可能同时承担 CNI、NetworkPolicy、服务转发和可观测性职责。策略没有显式拒绝流量,并不代表路径一定适合高吞吐集合通信;MTU 不一致、节点间链路差异、连接跟踪压力或错误的接口选择,也可能造成抖动。
如果集群安装了 Cilium CLI 和 Hubble,可以这样检查。将命名空间改成训练任务所在的命名空间:
NS=kubeflow
cilium status --wait
cilium connectivity test
hubble observe \
--namespace "$NS" \
--since 10m \
--verdict DROPPED
hubble observe \
--namespace "$NS" \
--since 10m \
--protocol TCP
DROPPED 流能直接提示策略或数据面拒绝,但“没有 drop”不能排除网络问题。还应比较 Worker 所在节点的错误计数、MTU 和实际吞吐:
NS=kubeflow
SELECTOR='training.kubeflow.org/job-name=my-training-job'
kubectl get pods -n "$NS" -l "$SELECTOR" \
-o custom-columns='POD:.metadata.name,NODE:.spec.nodeName,IP:.status.podIP'
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
kubectl -n kube-system logs -l k8s-app=cilium --since=15m --tail=200
可以进一步部署 iperf3 或运行 NCCL Tests,在相同节点组合上测量吞吐。关键是复用训练任务的节点、Pod 网络和安全策略;从运维机测试节点 IP,无法代表训练 Pod 之间的真实路径。
一个有效的实验矩阵应至少包含:同节点 Worker、跨节点 Worker、受相同 Cilium 策略约束的测试 Pod,以及临时放宽策略后的对照组。放宽策略只能用于隔离变量,不能直接作为生产修复方案。
不要一看到网络就关闭 NetworkPolicy
网络策略经常成为第一个怀疑对象,但直接删除策略会掩盖证据并扩大安全风险。更稳妥的顺序是:
- 固定训练代码、数据集和 batch size,先得到可重复的 step-time 基线。
- 记录 rank、Pod、节点和 Pod IP 的映射。
- 用 NCCL 日志定位等待发生在初始化、点对点连接还是集合通信阶段。
- 用 Hubble 检查同一时间窗口内的转发、拒绝和连接行为。
- 运行节点间吞吐测试,并与节点组合逐一对照。
- 每次只改变一个变量,例如策略、MTU、网卡选择或节点位置。
如果调整 NCCL_SOCKET_IFNAME,也要把它视为需要验证的实验,而不是通用答案。不同集群可能使用 eth0、辅助高速网卡、RDMA 设备或云厂商虚拟接口,写错后反而会迫使 NCCL 走更慢的路径。
上线前的检查清单
处理这类问题时,建议保留一份跨层检查表:
- Kubeflow 任务状态之外,是否持续记录 step time 和 samples/sec。
- GPU 利用率、网络吞吐、重传和 NCCL 日志是否使用统一时间戳。
- 是否能从 rank 追溯到 Pod、节点、网卡和 Cilium endpoint。
- Cilium 与 Hubble 是否处于健康状态,策略命中是否符合预期。
- MTU、节点网络和训练容器看到的接口是否一致。
- 是否通过短训练任务或 NCCL Tests 复现,而不是反复运行昂贵的完整训练。
- 修改策略或通信参数后,是否同时验证性能与安全边界。
“Pod 全部 Running”只说明调度和容器生命周期没有明显故障。面对 GPU 长时间空闲,更有价值的问题是:每个 rank 此刻在计算、读数据,还是等待另一个 rank?一旦把 GPU 指标、NCCL 日志和 Cilium 流量放到同一时间线上,60% 的空闲时间就不再只是仪表盘上的异常数字,而会变成可以定位和验证的工程问题。