训练工程与训练平台入门33 追踪一次 TrainJob 的完整生命周期

这一节继续固定在 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 更接近真正的训练平台工程工作。

相关推荐
无糖可可果41 分钟前
NestJS 学习分享:从工厂模式到企业级后端开发
后端
vipbic1 小时前
网站升级了,我却有点舍不得
前端·vue.js·后端
IT_陈寒1 小时前
Java Stream并行处理让我数据库崩了两次
前端·人工智能·后端
2601_963870221 小时前
【计算机毕业设计】基于Spring Boot的考研信息交流平台的设计与实现
java·spring boot·后端
大模型丫丫2 小时前
Spring Boot 入门指南:从零开始构建你的第一个应用
java·spring boot·后端
卷无止境2 小时前
FastAPI的测试事件在测什么?
后端·python·fastapi
gis开发之家2 小时前
日志怎么打才专业?Spring Boot 4 日志体系与 Logback 配置(生产级实战)
java·spring boot·后端·logback
卷无止境2 小时前
FastAPI CLI 你需要认识这把命令行利器
后端·python·fastapi
AINative软件工程2 小时前
LLM 请求合并工程实践:用 Single-Flight 把并发重复调用从 N 次砍成 1 次
后端·llm·ai编程