K8s Controller 和 Temporal 都是"控制面"技术,但代表了两种截然不同的设计哲学。本文深度对比二者的核心差异,并探讨在 AI Infra 平台中如何结合运用。
一、两种控制面范式
1.1 设计哲学对比
| 维度 | K8s Controller | Temporal |
|---|
| 核心理念 | 声明式 (Declarative) | 编排式 (Imperative) |
| 关注点 | 期望状态 (Desired State) | 执行流程 (Workflow) |
| 触发机制 | Level-Triggered (水平触发) | Event Sourcing (事件溯源) |
| 表达方式 | “我想要 3 个 Pod” | “先做 A,再做 B,失败则做 C” |
1.2 水平触发 vs 边缘触发
K8s Controller 的水平触发 (Level-Triggered):
1
2
3
4
5
| 期望状态: replicas=3
当前状态: pods=2
→ Reconcile → 创建 1 个 Pod
→ 再次检查 → pods=3
→ 无需操作
|
不管触发 Reconcile 的原因是什么(Pod 被删、新建 Deployment、健康检查失败),Controller 只关心"当前状态 vs 期望状态"的差距。
Temporal 的事件溯源:
1
2
3
4
5
6
7
| Event 1: WorkflowStarted
Event 2: ActivityScheduled(扣款)
Event 3: ActivityCompleted(成功)
Event 4: ActivityScheduled(扣库存)
[崩溃恢复]
→ 重放 Event 1-4
→ 从 Event 4 继续执行
|
Temporal 需要完整的历史轨迹才能恢复状态。
1.3 适用场景分析
| 场景 | 更适合的方案 | 原因 |
|---|
| 管理 Pod/GPU 资源 | K8s Operator | 资源是"要维持的状态" |
| 训练任务流程 | Temporal | 是"有序的步骤" |
| 故障自愈 | K8s Controller | 周期性 Reconcile 自动修复 |
| 复杂分支逻辑 | Temporal | 代码表达更直观 |
| 无限期运行 | K8s Controller | 无状态、低开销 |
| 有明确结束 | Temporal | 天然支持完成/失败/取消 |
二、代码对比
2.1 K8s Controller 示例
管理一个 AI 训练任务的 Operator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| // reconciler.go
func (r *TrainingJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 获取 CR 资源
var job trainingv1.TrainingJob
if err := r.Get(ctx, req.NamespacedName, &job); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 获取当前状态
var pods corev1.PodList
r.List(ctx, &pods, client.MatchingLabels{"job": job.Name})
// 期望状态 vs 当前状态
desiredReplicas := job.Spec.Workers
currentReplicas := len(pods.Items)
if currentReplicas < desiredReplicas {
// 创建 Pod
for i := currentReplicas; i < desiredReplicas; i++ {
r.Create(ctx, r.buildWorkerPod(&job, i))
}
} else if currentReplicas > desiredReplicas {
// 删除多余 Pod
for i := desiredReplicas; i < currentReplicas; i++ {
r.Delete(ctx, &pods.Items[i])
}
}
// 更新状态
job.Status.ActiveWorkers = desiredReplicas
r.Status().Update(ctx, &job)
// 周期性重新检查
return ctrl.Result{RequeueAfter: time.Minute}, nil
}
|
特点:
- 每次 Reconcile 都从"当前状态"开始
- 不关心"如何到达这里",只关心"现在和期望差多少"
- 适合持续运行的资源管理
2.2 Temporal Workflow 示例
同样的 AI 训练任务用 Temporal 实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // workflow.go
func TrainingWorkflow(ctx workflow.Context, job TrainingJob) error {
// Step 1: 准备数据
var dataPath string
err := workflow.ExecuteActivity(ctx, PrepareData, job.DataSource).Get(ctx, &dataPath)
if err != nil {
return fmt.Errorf("数据准备失败: %w", err)
}
// Step 2: 启动训练(可能运行数小时)
var modelPath string
err = workflow.ExecuteActivity(ctx, StartTraining, TrainingParams{
DataPath: dataPath,
Epochs: job.Epochs,
Workers: job.Workers,
}).Get(ctx, &modelPath)
if err != nil {
// 清理中间数据
workflow.ExecuteActivity(ctx, CleanupData, dataPath)
return fmt.Errorf("训练失败: %w", err)
}
// Step 3: 模型评估
var metrics Metrics
err = workflow.ExecuteActivity(ctx, EvaluateModel, modelPath).Get(ctx, &metrics)
if err != nil {
return fmt.Errorf("评估失败: %w", err)
}
// Step 4: 条件分支 - 精度达标才部署
if metrics.Accuracy >= job.MinAccuracy {
workflow.ExecuteActivity(ctx, DeployModel, modelPath)
} else {
// 通知人工审核
workflow.ExecuteActivity(ctx, NotifyHumanReview, job.Owner, metrics)
}
return nil
}
|
特点:
- 代码就是流程,顺序执行、分支判断一目了然
- 步骤之间有依赖关系(dataPath → training → modelPath)
- 适合有明确开始和结束的任务
三、核心机制对比
3.1 故障恢复
K8s Controller:
1
2
3
4
5
| Pod 挂了
→ Informer 收到 Delete 事件
→ 触发 Reconcile
→ 发现 current=2, desired=3
→ 创建新 Pod
|
恢复靠的是周期性对比状态,不需要历史记录。
Temporal:
1
2
3
4
5
6
| Worker 挂了
→ Workflow 执行被中断
→ 启动新 Worker
→ 从 Event History 重放
→ 代码重新执行到断点
→ 继续向后执行
|
恢复靠的是完整的事件历史。
3.2 复杂分支
K8s Controller 处理复杂分支很痛苦:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 充斥着状态机逻辑
switch job.Status.Phase {
case "Pending":
if allPodsReady() {
job.Status.Phase = "Running"
}
case "Running":
if trainingComplete() {
job.Status.Phase = "Evaluating"
} else if hasFailed() {
job.Status.Phase = "Failed"
}
case "Evaluating":
// 更多分支...
}
|
Temporal 天然支持:
1
2
3
4
5
6
7
8
| // 直接用 if-else,清清楚楚
if metrics.Accuracy >= threshold {
deployModel()
} else if retryCount < 3 {
retrainWithMoreData()
} else {
notifyHuman()
}
|
3.3 资源占用
| 场景 | K8s Controller | Temporal |
|---|
| 1000 个任务 | 1 个 Controller Pod | 1000 个 Workflow 历史 |
| 内存占用 | O(1) - 无状态 | O(N) - 每个 Workflow 占内存 |
| 长期运行 | 低开销 | 历史膨胀风险 |
Temporal 的解法:ContinueAsNew 切分历史
1
2
3
4
| // 当历史太长时,"重生"为新 Workflow
if workflow.GetInfo(ctx).GetHistorySize() > 10000 {
return workflow.NewContinueAsNewError(ctx, TrainingWorkflow, job)
}
|
四、AI Infra 最佳实践
4.1 职责分离架构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| ┌─────────────────────────────────────┐
│ Temporal Workflow │
│ (任务编排: 训练 → 评估 → 部署) │
└───────────────┬─────────────────────┘
│ 调用
┌───────────────▼──────────────────────┐
│ K8s Operator │
│ (资源管理: Pod, GPU, PVC) │
└───────────────┬──────────────────────┘
│ 管理
┌───────────────▼────────────────────┐
│ Kubernetes 集群 │
│ (GPU 节点, 存储, 网络) │
└────────────────────────────────────┘
|
4.2 Temporal Activity 调用 K8s API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| func CreateTrainingPod(ctx context.Context, spec PodSpec) (string, error) {
clientset := getKubernetesClient()
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "training-",
Labels: map[string]string{"managed-by": "temporal"},
},
Spec: spec.ToK8sPodSpec(),
}
created, err := clientset.CoreV1().Pods("training").Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
return "", err
}
return created.Name, nil
}
func WaitForPodComplete(ctx context.Context, podName string) error {
clientset := getKubernetesClient()
watch, _ := clientset.CoreV1().Pods("training").Watch(ctx, metav1.ListOptions{
FieldSelector: "metadata.name=" + podName,
})
for event := range watch.ResultChan() {
pod := event.Object.(*corev1.Pod)
if pod.Status.Phase == corev1.PodSucceeded {
return nil
}
if pod.Status.Phase == corev1.PodFailed {
return fmt.Errorf("Pod 失败: %s", pod.Status.Message)
}
}
return fmt.Errorf("Watch 意外结束")
}
|
4.3 何时用哪个?
| 需求 | 选择 |
|---|
| “我需要 3 个 GPU Pod” | K8s Operator |
| “先预处理数据,再训练,失败重试 3 次” | Temporal |
| “某个 Pod 挂了自动拉起” | K8s (内置能力) |
| “训练成功后自动触发评估和部署” | Temporal |
| “管理 GPU 资源池” | K8s Operator + Device Plugin |
| “协调多个任务的执行顺序” | Temporal |
五、总结
| 维度 | K8s Controller 胜出 | Temporal 胜出 |
|---|
| 资源管理 | 是 | |
| 无限期运行 | 是 | |
| 故障自愈 | 是 | |
| 复杂流程编排 | | 是 |
| 长事务补偿 | | 是 |
| 代码即流程 | | 是 |
核心理解:两者不是替代关系,而是互补关系。在 AI Infra 控制面架构中:
- K8s Operator 是"地基"——管理底层资源
- Temporal 是"上层建筑"——编排业务流程
结合使用可以构建完整的控制面架构。