这一节继续固定在 Kubeflow Trainer v2.1.0。
前面你已经把 torch-distributed Runtime 单独安装好了。现在我们不再关心"怎么安装",而是看一次:
kubectl apply -f mnist-trainjob.yaml
之后,到底发生了什么。
Kubeflow Trainer v2 的核心抽象就是 TrainJob + TrainingRuntime/ClusterTrainingRuntime;官方也明确把 Runtime 定位成平台管理员维护的可复用训练模板。trainer.kubeflow.org
1. 第一阶段:用户提交 TrainJob
你执行:
kubectl apply -f mnist-trainjob.yaml
假设里面最核心的是:
yaml
apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
metadata:
name: mnist-demo
spec:
runtimeRef:
name: torch-distributed
kind: ClusterTrainingRuntime
此时第一站不是 Trainer Controller,而是:
arduino
kubectl
↓
Kubernetes API Server
也就是说,TrainJob 首先是一个 Kubernetes Resource。
这也是为什么:
arduino
kubectl get trainjobs
能直接看到它。
2. Admission Webhook 先检查:这个任务合法吗?
前面你已经亲自遇到过:
vbscript
validator.trainjob.trainer.kubeflow.org
denied the request
所以现在可以把流程理解得更准确一些:
arduino
kubectl apply
↓
API Server
↓
Trainer Admission Webhook
↓
验证 TrainJob
其中一个关键检查就是:
yaml
runtimeRef:
name: torch-distributed
引用的 Runtime 是否真的存在。
之前:
swift
torch-distributed 不存在
所以:
Webhook
↓
拒绝
TrainJob 根本没有真正创建成功。
现在 Runtime 已经存在,这一关才能过去。
Trainer v2 的 Runtime 就是专门提供给 TrainJob 引用的模板;ClusterTrainingRuntime 是集群级,可被不同 namespace 中的 TrainJob 复用。trainer.kubeflow.org
3. Webhook 和 Controller 不要混在一起
这是很容易混淆的一点。
Webhook 更像门卫:
这个 TrainJob 合法吗?
runtimeRef 对吗?
参数结构合法吗?
Controller 是真正干活的人:
TrainJob 已经创建了
↓
现在我要把它变成可运行的 workload
所以生命周期是:
提交
↓
Admission 校验
↓
TrainJob 保存到 etcd
↓
Trainer Controller 开始 reconcile
这也是 Kubernetes Operator 最典型的运行方式。
4. Trainer Controller 看到了新的 TrainJob
TrainJob 成功写进 API Server 后,Trainer Controller 会观察到它。
可以简化理解为:
yaml
Trainer Controller:
"发现 mnist-demo 了。"
然后读取:
yaml
runtimeRef:
name: torch-distributed
接着去获取:
bash
ClusterTrainingRuntime/torch-distributed
于是:
diff
TrainJob
+
ClusterTrainingRuntime
第一次真正合并起来。
这也是 Trainer v2 的核心设计:AI Practitioner 提交较简单的 TrainJob,平台管理员通过 Runtime 封装 Kubernetes 和分布式训练环境复杂度。Kubeflow
5. Runtime 到底贡献了什么?
假设 TrainJob 只说:
arduino
我要:
1 个 node
这个 image
执行这个 command
它本身并不知道完整的:
Pod 怎么组织
分布式进程怎么启动
需要哪些环境变量
网络怎么配置
底层 workload 用什么
这些复杂内容由 Runtime 提供。
所以可以理解成:
markdown
TrainJob
--------------------
"这次我要训练什么"
ClusterTrainingRuntime
--------------------
"这种训练应该怎么运行"
然后 Controller 做:
markdown
TrainJob
+
Runtime Template
↓
生成最终执行规格
对于平台设计,这个分离非常重要。
6. 接下来为什么会看到 JobSet?
Kubeflow Trainer v2 并不是直接自己重新发明一套 Pod 编排系统,而是和 Kubernetes AI workload 生态结合。
官方当前架构明确提到 Trainer 与 JobSet 集成,用于 AI workload orchestration。Kubeflow
所以你可以先把 v2.1.0 的典型执行链理解成:
TrainJob
↓
Trainer Controller
↓
Runtime
↓
JobSet
↓
Kubernetes Pods
JobSet 可以理解成:
用来组织一组相关 Job/Pod 的 Kubernetes 工作负载抽象。
对于分布式训练特别合适,因为训练并不总是:
1 Job
→ 1 Pod
而可能是:
一个训练任务
↓
多个互相关联的训练进程
↓
多个 Pod
Kubeflow Trainer v2 的设计中,JobSet 是重要的底层运行机制。GitHub
7. 所以你应该开始同时观察三个资源
提交 TrainJob:
kubectl apply -f mnist-trainjob.yaml
第一层:
arduino
kubectl get trainjobs
这表示:
用户层训练任务
第二层:
arduino
kubectl get jobsets
这表示:
Trainer 转换出来的底层 workload
第三层:
arduino
kubectl get pods
这才表示:
真正正在运行的 Container
所以不要只盯:
arduino
kubectl get pods
更有学习价值的是同时看:
TrainJob
↓
JobSet
↓
Pod
8. 我们实际追踪一次
先:
arduino
kubectl get trainjobs
如果创建成功,应该能看到:
mnist-demo
然后:
arduino
kubectl get jobsets
应该能看到由 Trainer 创建出的关联 JobSet。
再:
arduino
kubectl get pods
你会看到真正运行 MNIST 的 Pod。
为了观察生命周期,可以直接:
arduino
kubectl get trainjobs -w
另一个终端:
arduino
kubectl get jobsets -w
再一个:
arduino
kubectl get pods -w
这样你能亲眼看到:
markdown
TrainJob 创建
↓
JobSet 出现
↓
Pod 创建
↓
ContainerCreating
↓
Running
↓
Completed
9. Pod 才是真正运行 python train.py 的地方
这一点我们前几节已经讲过,但现在终于能看到完整来源。
最终:
Pod
↓
Container
↓
Image
↓
Command
↓
python train.py
所以真正训练的仍然是:
PyTorch
Trainer Controller 不会帮你执行:
scss
loss.backward()
JobSet 也不会。
Kubernetes Scheduler 也不会。
它们只是一步步保证:
你的训练进程以正确的方式被创建和运行。
10. Scheduler 在哪里介入?
当 Pod 被创建后:
ini
Pod
status = Pending
此时 Kubernetes Scheduler 开始找 Node。
例如:
css
Node A
CPU 不够
Node B
满足资源要求
于是:
css
Pod
↓
Node B
然后 kubelet 拉镜像、启动 Container。
所以完整链路继续展开:
arduino
TrainJob
↓
Trainer Controller
↓
JobSet
↓
Pod
↓
Kubernetes Scheduler
↓
Node
↓
Container Runtime
↓
Container
↓
train.py
这时候你应该能看出:
Trainer 和 Kubernetes Scheduler 不是一个东西。
Trainer 负责"训练任务应该变成什么 workload"。
Scheduler 负责:
"这个 Pod 到底放到哪台机器。"
11. 如果 Pod 一直 Pending,该看哪里?
以后训练平台排障时,这会很常见。
如果:
arduino
kubectl get pods
显示:
Pending
先:
sql
kubectl describe pod <pod-name>
重点看 Events。
例如:
bash
Insufficient cpu
Insufficient memory
Insufficient nvidia.com/gpu
这说明:
TrainJob 创建成功了,Trainer 也创建出 workload 了,只是 Kubernetes 找不到合适资源。
这和:
runtime not found
完全不是一层问题。
12. 如果 Pod Running 但训练失败呢?
例如:
javascript
Pod
Running
↓
Error
那么:
xml
kubectl logs <pod-name>
可能看到:
ModuleNotFoundError
FileNotFoundError
CUDA OOM
Dataset not found
Python exception
这已经进入:
训练程序 / 镜像 / 数据层。
也不是 Kubeflow Controller 本身的问题。
13. 现在可以建立非常重要的"分层排障"思维
以后看到:
训练任务失败
不能直接说:
Kubeflow 有问题。
而是沿着链路定位。
第一层:TrainJob 根本创建不了
例如:
runtimeRef invalid
Webhook denied
看:
TrainJob YAML
Admission Webhook
Runtime
第二层:TrainJob 有,但没有底层 workload
看:
Trainer Controller
Runtime reconciliation
Controller logs
第三层:JobSet 有,但 Pod 没起来
看:
JobSet
Kubernetes Job
Pod creation
第四层:Pod Pending
看:
arduino
Scheduler
Resource
Node
Quota
第五层:Pod 启动了但 Container 失败
看:
Image
Command
Environment
Volume
第六层:Python 跑起来后训练失败
看:
css
PyTorch
Dataset
CUDA
OOM
Training code
这个排障层次,比死记 Kubeflow 命令重要很多。
14. TrainJob Status 又是什么?
从用户角度,我们不希望每次都自己查:
JobSet
Pod
Container
所以 TrainJob 自己会有 Status。
你可以:
arduino
kubectl get trainjob mnist-demo -o yaml
重点看:
lua
status:
Controller 会不断 reconcile:
markdown
底层 workload 状态
↓
Trainer Controller
↓
更新 TrainJob Status
最终上层平台只需要看 TrainJob,就可以获得训练任务生命周期信息。
这也是 Kubernetes Controller 很重要的职责之一:
把底层资源的真实状态向上收敛到高级资源。
15. 为什么训练平台一般不会直接让用户看 JobSet?
现在你应该能理解了。
普通用户只关心:
sql
训练任务:
mnist-demo
状态:
Running
他们不关心:
JobSet 名字
内部 Job
Pod OwnerReference
Controller reconcile
所以企业训练平台通常会:
用户
↓
Training Job 页面
↓
平台 API
↓
TrainJob
而底下:
JobSet
Pod
Node
是平台内部实现。
这就是抽象层次。
16. 把整个生命周期重新串一次
现在完整链条是:
arduino
用户
↓
kubectl apply TrainJob
↓
Kubernetes API Server
↓
Admission Webhook
↓
校验 runtimeRef 等配置
↓
保存 TrainJob
↓
Trainer Controller
↓
读取 ClusterTrainingRuntime
↓
合成训练执行规格
↓
创建 JobSet
↓
JobSet 创建底层 Job / Pod
↓
Kubernetes Scheduler
↓
选择 Node
↓
启动 Container
↓
执行 python train.py
↓
PyTorch
↓
Dataset → Forward → Loss
→ Backward → Optimizer
↓
训练完成
↓
Pod / JobSet 状态变化
↓
Trainer Controller 更新 TrainJob Status
↓
用户看到 Success
这已经非常接近一套训练平台的核心任务生命周期。
本节总结
这一节最重要的不是新的 Kubeflow 命令,而是建立这条映射:
TrainJob
↓
用户看到的"训练任务"
ClusterTrainingRuntime
↓
平台提供的运行模板
Trainer Controller
↓
把高级训练任务转换成底层 workload
JobSet
↓
组织实际分布式工作负载
Pod
↓
真正执行训练 Container
PyTorch
↓
真正完成模型学习
同时你现在应该有了一个很重要的排障原则:
训练任务失败,要先判断失败发生在 TrainJob、Controller、JobSet、Pod、Container,还是 PyTorch 训练代码这一层。
这比单纯知道怎么 kubectl apply 更接近真正的训练平台工程工作。