2.4 训练作业的 Operator 范式:从 Job 到训练 CRD 一个分布式训练不是「一个 Pod」,而是「一群互相绑定、同生共死的 Pod」——K8s 原生的 Job 不懂这种「合唱团」语义,于是有了 Operator:用一份声明式 CRD 把训练作业的复杂生命周期交给控制器自动驱动。 2.4.1 为什么普通 Job 不够用 K8s 原生的 Job 资源能管理「跑完即退出」的批处理任务,比如跑一个数据转换脚本。看起来训练也是「跑完退出」,似乎用 Job 就够了。但分布式训练有几个普通 Job 难以表达的特性: 多角色协作:一个分布式训练有 master、worker、parameter server 等多种角色,每个角色是一组 Pod,普通 Job 只能管一组同构 Pod。
一个分布式训练不是「一个 Pod」,而是「一群互相绑定、同生共死的 Pod」——K8s 原生的 Job 不懂这种「合唱团」语义,于是有了 Operator:用一份声明式 CRD 把训练作业的复杂生命周期交给控制器自动驱动。
K8s 原生的 Job 资源能管理「跑完即退出」的批处理任务,比如跑一个数据转换脚本。看起来训练也是「跑完退出」,似乎用 Job 就够了。但分布式训练有几个普通 Job 难以表达的特性:
WORLD_SIZE、RANK;MPI 要配 hostfile;DeepSpeed 要注入启动脚本——每种框架有不同的启动约定。下表对比普通 Job 与训练需求的差距:
| 训练需求 | 普通 Job | 差距 |
|---|---|---|
| 多角色(master/worker) | 不支持 | 大 |
| Gang 调度(全部成功或全失败) | 不支持 | 大 |
| 框架启动脚本注入 | 不支持 | 中 |
| 检查点恢复 | 手工 | 中 |
| 状态机与失败重试 | 简单重试 | 中 |
Operator 模式 是 CoreOS 在 2016 年提出的 K8s 扩展范式,核心思想是:把人类运维专家的领域知识,编码成一个自定义控制器(Custom Controller),让它自动管理一类复杂应用的声明式 CRD(Custom Resource Definition)。
Operator 的两大组成:
PyTorchJob、MPIJob。Operator 的工作回路是经典的「Reconcile(调谐)」循环:控制器周期性地比较「用户声明的目标状态」与「集群里的实际状态」,发现有差距就采取行动消除差距。例如:
PyTorchJob 要 4 个 worker,但只有 3 个 Running → 控制器创建第 4 个。PyTorchJob 状态置为 Succeeded。💡 判读:Operator 的本质是「领域知识自动化」。传统运维里靠人记忆的「分布式训练怎么启动、失败了怎么办」被编码进控制器,变成可版本化、可演进的代码。这也是云原生「声明式 + 自动化」哲学的极致体现。
Kubeflow Training Operator(前身是 tf-operator、pytorch-operator 的合并项目)是 K8s 上承载训练作业的事实标准。它提供了一系列针对不同框架的 CRD:
| CRD | 对应框架 | 典型场景 |
|---|---|---|
PyTorchJob |
PyTorch DDP | PyTorch 分布式数据并行训练 |
MPIJob |
MPI / Horovod | Horovod、跨框架 AllReduce |
DeepSpeedJob |
DeepSpeed | 大模型 ZeRO 训练 |
TFJob |
TensorFlow | TF 分布式训练 |
XGBoostJob |
XGBoost | 分布式树模型 |
MXNetJob |
MXNet | MXNet 分布式训练 |
每个 CRD 都遵循相似的 schema:声明 master/worker 的 replica 数、每个 replica 的 template(镜像、资源、命令)。一个 PyTorchJob 的伪声明:
apiVersion: kubeflow.org/v1 kind: PyTorchJob metadata: name: resnet50-train spec: pytorchReplicaSpecs: Master: replicas: 1 template: spec: containers: - name: pytorch image: my-trainer:v1 resources: limits: nvidia.com/gpu: 1 Worker: replicas: 7 template: spec: containers: - name: pytorch image: my-trainer:v1 resources: limits: nvidia.com/gpu: 1
这份声明表达了「我要跑一个 1 master + 7 worker 的 PyTorchJob,每个 Pod 1 张 GPU」。Training Operator 会自动:
WORLD_SIZE=8、RANK=0..7、MASTER_ADDR 等环境变量。Succeeded。PyTorchJob、MPIJob、DeepSpeedJob 是最常用的三种,它们的关系与适用场景需要辨析清楚。
deepspeed_launcher。| 选型维度 | PyTorchJob | MPIJob | DeepSpeedJob |
|---|---|---|---|
| 训练框架 | PyTorch | 任意(Horovod) | DeepSpeed + PyTorch |
| 并行策略 | 数据并行 | 数据并行 | 数据并行 + ZeRO |
| 大模型友好 | 一般 | 一般 | 强 |
| 配置复杂度 | 低 | 中 | 中 |
| 2024 后趋势 | 主流 | 收缩 | 大模型标配 |
💡 选型经验:新项目用 PyTorch 做数据并行 →
PyTorchJob;要训百亿以上大模型 →DeepSpeedJob;老项目用 Horovod →MPIJob。三者的底层都是把训练脚本跑在 K8s 编排的 Pod 群上,区别在于启动脚本与角色约定。
一个常见的混淆是:Operator 和调度器都管 Pod,它们怎么分工?
二者协作:Operator 创建 Pod → 调度器决定 Pod 落到哪台节点 → Pod 运行 → Operator 监控 Pod 状态。Operator 不直接决定节点,调度器不直接创建 Pod。
但分布式训练有个特殊诉求——Gang Scheduling(成组调度):「我要 8 个 worker,要么 8 个全部调度成功,要么一个都不调度」。原生调度器是逐个调度 Pod 的,可能出现「调度了 5 个,剩 3 个资源不够,前 5 个干等」的死锁。这时需要 Volcano、Kueue 等批处理调度器配合,做 Gang 调度。第 3 章会详细讲。
Operator 不只为训练服务。云原生 AI 生态里很多组件都是 Operator:
InferenceService CRD,自动部署推理服务(第 6 章)。Workload 与 ClusterQueue,做作业排队(第 3 章)。RayCluster 与 RayJob,部署 Ray 集群(第 5 章)。掌握 Operator 范式,等于掌握了 K8s 上承载一切复杂应用的标准方法论。这也是为什么本章把 Operator 作为「从 K8s 基础设施走向 AI 工作负载」的过渡——后续章节你会反复看到它的身影。
到这里,第 2 章的四节构成了一条完整的「K8s 承载 AI」地基链:
但这套地基仍是「独占模式」——一张 GPU 给一个 Pod。第 3 章将在这套地基上叠加:GPU 共享(MIG/MPS)、拓扑感知调度、批处理调度器、弹性训练,把 GPU 利用率从 30% 推向 80%+。
第 2 章完结。下一章《第 3 章 共享 GPU、拓扑感知与高级调度策略》将在这套地基上深入 GPU 共享与拓扑优化。