feat: scaffold job execution API #1
@@ -0,0 +1,526 @@
|
||||
# Job API v1alpha1 草案
|
||||
|
||||
- 状态:Draft
|
||||
- 日期:2026-09-17
|
||||
- API group:`execution.ayatori.ddupan.top`
|
||||
- Kind:`Job`
|
||||
- Scope:Namespaced
|
||||
|
||||
## 目标
|
||||
|
||||
`Job` 表达一次有限时长、有明确退出结果的机器执行。调用者描述任务载荷和资源需求,平台
|
||||
选择 execution backend 并持续观察,直到任务成功、失败或取消。
|
||||
|
||||
首个 adapter 使用 Kubernetes `batch/v1 Job`,第二个 adapter 使用 OpenSandbox lifecycle
|
||||
与 execd API。API 不暴露 PodSpec、sandbox ID 创建参数或具体 adapter 配置,但允许表达两个
|
||||
真实后端共有的 OCI image、进程、环境变量和资源语义。
|
||||
|
||||
`Job` 是短生命周期控制对象。完成后依据 `ttlSecondsAfterFinished` 回收,长期业务状态由调用
|
||||
者保存,日志由 observability 平台保存。详细保留策略见 ADR-0005。
|
||||
|
||||
## 非目标
|
||||
|
||||
v1alpha1 不提供:
|
||||
|
||||
- DAG、workflow 或多步骤 task;
|
||||
- 定时执行与可复用 Job template;
|
||||
- 并行 completions、indexed job 或 gang scheduling;
|
||||
- 自动业务重试;
|
||||
- 暂停后恢复;
|
||||
- 交互式 shell、endpoint、snapshot 或长生命周期 sandbox;
|
||||
- workspace、cache、artifact 上传协议或结构化 task outputs;
|
||||
- 永久 Job history。
|
||||
|
||||
上述能力应由后续独立资源或经过真实需求验证的兼容字段提供,不能通过透传 PodSpec 或
|
||||
OpenSandbox extensions 提前进入 API。
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||
kind: Job
|
||||
metadata:
|
||||
generateName: hello-
|
||||
namespace: ci
|
||||
spec:
|
||||
jobClassName: default
|
||||
task:
|
||||
image: docker.io/library/alpine:3.22
|
||||
imagePullSecrets: []
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- echo "hello ${TARGET}"
|
||||
workingDir: /workspace
|
||||
env:
|
||||
- name: TARGET
|
||||
value: world
|
||||
- name: TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: example-token
|
||||
key: token
|
||||
- name: CONFIG_VALUE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: example-config
|
||||
key: value
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
activeDeadlineSeconds: 600
|
||||
ttlSecondsAfterFinished: 3600
|
||||
desiredState: Running
|
||||
```
|
||||
|
||||
## Spec
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
jobClassName: string
|
||||
task: TaskSpec
|
||||
resources: ResourceRequirements
|
||||
activeDeadlineSeconds: int64
|
||||
ttlSecondsAfterFinished: int32
|
||||
desiredState: Running | Cancelled
|
||||
```
|
||||
|
||||
### `jobClassName`
|
||||
|
||||
可选。引用平台管理员维护的 cluster-scoped `JobClass`。调用者选择服务等级或执行
|
||||
能力,而不是直接选择 adapter driver。省略时由 namespace policy 解析默认 class;不存在
|
||||
默认值时 Job 保持未接受状态,不得静默选择任意后端。
|
||||
|
||||
一旦 Job 被接受,该字段不可变。解析出的实际 class 写入 status,以便默认策略后来变化时仍
|
||||
能恢复原执行。
|
||||
|
||||
`JobClass` 及其强类型 parameters API 定义后端配置和调度策略。Job API 不暴露
|
||||
Kubernetes namespace、OpenSandbox endpoint、API key 或 backend raw configuration。
|
||||
|
||||
### `task`
|
||||
|
||||
必填,创建后不可变。
|
||||
|
||||
```yaml
|
||||
task:
|
||||
image: string
|
||||
imagePullSecrets: []LocalObjectReference
|
||||
command: []string
|
||||
args: []string
|
||||
workingDir: string
|
||||
env: []EnvVar
|
||||
```
|
||||
|
||||
- `image`:必填 OCI image reference。首版允许 tag;生产策略可以通过 admission 要求 digest。
|
||||
- `imagePullSecrets`:可选,同 namespace 的私有 registry 凭据引用。
|
||||
- `command`:可选,覆盖 image entrypoint;空值表示使用 image 默认值。
|
||||
- `args`:可选,传给 entrypoint/command。
|
||||
- `workingDir`:可选;为空时使用 image/backend 默认值。
|
||||
- `env`:可选,名称必须唯一。
|
||||
|
||||
`command` 和 `args` 使用 argv 语义,不隐式经过 shell。需要 shell 展开时,调用者必须显式
|
||||
指定 `/bin/sh -c` 等命令。
|
||||
|
||||
#### 环境变量
|
||||
|
||||
```yaml
|
||||
- name: EXAMPLE
|
||||
value: literal
|
||||
|
||||
- name: TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: example
|
||||
key: token
|
||||
optional: false
|
||||
```
|
||||
|
||||
`value` 与 `valueFrom` 必须且只能设置一个。v1alpha1 支持同 namespace 的 `SecretKeyRef` 和
|
||||
`ConfigMapKeyRef`,两者具有相同的引用、optional 和等待语义。Adapter 负责以适合后端且不
|
||||
写入 Job status 的方式传递值。引用对象或 key 缺失时 Job 保持未开始并通过 Condition 报告。
|
||||
Secret 内容不得复制到 Event、日志或 backend reference;ConfigMap 值虽然不视为机密,也不
|
||||
写入 status,避免状态膨胀和不同后端行为不一致。
|
||||
|
||||
Kubernetes adapter 保留原生 `SecretKeyRef`/`ConfigMapKeyRef`,由 kubelet 在执行节点解析,
|
||||
controller 不读取内容。OpenSandbox create API 只接收已经解析的环境变量值,因此该 adapter
|
||||
必须读取引用并把值放入 sandbox create request。JobClass 必须明确允许 Secret 的这种
|
||||
传递路径,且 controller 的日志、Event 和 status 不得记录请求正文。未来需要避免把真实凭据
|
||||
暴露给 sandbox 进程时,使用 OpenSandbox Credential Vault 或 Ayatori 独立 Credential 能力,
|
||||
而不是改变 `SecretKeyRef` 的既有语义。
|
||||
|
||||
私有镜像凭据采用 Kubernetes `kubernetes.io/dockerconfigjson` Secret。Kubernetes adapter
|
||||
直接传递引用;OpenSandbox adapter 选择与目标 image registry 匹配的条目,并映射到其
|
||||
`image.auth` create 参数。无法解析、没有匹配 registry 或所选 OpenSandbox runtime 不支持
|
||||
per-request image auth 时,Job 以明确 reason 失败,不得退回匿名拉取后隐藏真实原因。
|
||||
|
||||
### `resources`
|
||||
|
||||
可选,使用 Kubernetes `resource.Quantity` 表示数值,但不复用完整 Pod
|
||||
`ResourceRequirements` 行为。
|
||||
|
||||
v1alpha1 支持 `cpu` 和 `memory` 的 requests/limits。Requests 表达准入与调度需求,limits
|
||||
表达执行上限。JobClass 可以提供默认值和允许范围;解析后的实际资源写入 status。
|
||||
|
||||
Adapter 必须显式验证能否满足请求,不能无提示地忽略 limit。后端无法区分 request 与 limit
|
||||
时,其映射规则属于 JobClass,并在 Job 接受前确定。对 OpenSandbox,CPU 和内存
|
||||
limits 直接映射为 sandbox VM/container 的 `resourceLimits`;requests 用于 Ayatori 的准入与
|
||||
调度,并可由 JobClass 映射到后端 resource request 或 capacity profile。映射失败必须
|
||||
显式拒绝或失败,不能静默降低资源保证。
|
||||
|
||||
### `activeDeadlineSeconds`
|
||||
|
||||
可选,必须大于零。表示从实际执行开始到任务必须终止的最长时间,不包含排队、class 解析或
|
||||
后端 provisioning 时间。到期后 controller 请求终止后端,最终以 `Succeeded=False`、
|
||||
`reason=DeadlineExceeded` 结束。
|
||||
|
||||
后端自身的 timeout 可以作为执行机制,但 Ayatori controller 仍以 `status.startTime` 和观察
|
||||
结果维护领域语义。调度等待超时是不同概念,v1alpha1 不提供。
|
||||
|
||||
### `ttlSecondsAfterFinished`
|
||||
|
||||
可选,必须大于或等于零。语义与 Kubernetes Job 一致:从终态 transition time 起计算,零
|
||||
表示立即具备删除资格。该字段在任务完成前后均可修改,但不能保证在既有 TTL 已过期后通过
|
||||
延长 TTL 阻止并发删除。
|
||||
|
||||
平台应通过 schema、CEL 或 policy 设置最大值和推荐默认值。controller 本身不偷偷填充一个
|
||||
无法从 spec 观察到的永久策略。
|
||||
|
||||
### `desiredState`
|
||||
|
||||
可选,默认 `Running`。允许的状态迁移只有:
|
||||
|
||||
```text
|
||||
Running → Cancelled
|
||||
```
|
||||
|
||||
设置 `Cancelled` 表示请求终止当前执行并保留 Job 至 TTL 到期。取消是尽力而为的异步操作;
|
||||
只有 adapter 确认执行不会继续后,Job 才进入终态。字段不得从 `Cancelled` 改回 `Running`。
|
||||
重新执行必须创建新的 Job。
|
||||
|
||||
v1alpha1 不提供 suspend/resume。对任意后端可靠实现 checkpoint/resume 并非共同能力,且暂停
|
||||
不应被伪装为取消。
|
||||
|
||||
## 不可变性
|
||||
|
||||
创建后仅允许修改:
|
||||
|
||||
- `spec.desiredState`,且只能单向变为 `Cancelled`;
|
||||
- `spec.ttlSecondsAfterFinished`。
|
||||
|
||||
`task`、`resources`、`activeDeadlineSeconds` 和 `jobClassName` 均不可变。首选 CRD CEL
|
||||
validation 表达这些约束;只有 schema/CEL 无法正确表达时才引入 admission webhook。
|
||||
|
||||
Controller reconcile 的技术重试不表示任务重跑。v1alpha1 每个 Job 最多启动一个逻辑执行;
|
||||
adapter 必须使用 Job UID 作为幂等键。若请求结果未知,controller 必须先 Observe,不能因为
|
||||
网络超时重新创建可能已经开始的执行。
|
||||
|
||||
首个 Kubernetes adapter 创建 `backoffLimit: 0`、`restartPolicy: Never` 的原生 Job,避免继承
|
||||
Kubernetes 默认的多次业务执行语义。需要重新执行时创建新的 Ayatori Job。
|
||||
|
||||
## Status
|
||||
|
||||
```yaml
|
||||
status:
|
||||
observedGeneration: 1
|
||||
conditions:
|
||||
- type: Accepted
|
||||
status: "True"
|
||||
reason: Valid
|
||||
observedGeneration: 1
|
||||
lastTransitionTime: ...
|
||||
- type: Scheduled
|
||||
status: "True"
|
||||
reason: BackendCreated
|
||||
observedGeneration: 1
|
||||
lastTransitionTime: ...
|
||||
- type: Succeeded
|
||||
status: "Unknown"
|
||||
reason: Running
|
||||
observedGeneration: 1
|
||||
lastTransitionTime: ...
|
||||
resolvedJobClass:
|
||||
name: default
|
||||
uid: 8aa4...
|
||||
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||
parametersRef:
|
||||
group: execution.ayatori.ddupan.top
|
||||
kind: KubernetesExecutionParameters
|
||||
name: default
|
||||
uid: c413...
|
||||
effectiveResources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
execution:
|
||||
adapter: kubernetes
|
||||
references:
|
||||
- type: Job
|
||||
id: 5cb0...
|
||||
startTime: ...
|
||||
completionTime: ...
|
||||
result:
|
||||
exitCode: 0
|
||||
reason: Completed
|
||||
```
|
||||
|
||||
### Conditions
|
||||
|
||||
使用标准 `metav1.Condition`。v1alpha1 定义三个核心 Condition:
|
||||
|
||||
- `Accepted`:spec、引用、policy 和 JobClass 已解析,可进入调度;
|
||||
- `Scheduled`:后端已确定并存在可观察的逻辑执行;
|
||||
- `Succeeded`:任务结果。`Unknown` 表示尚未结束,`True` 表示成功,`False` 表示已经失败或
|
||||
取消。
|
||||
|
||||
失败不使用单独的 `Failed` Condition。`Succeeded=True` 与 `Failed=True` 会形成需要额外维护的
|
||||
互斥状态,而标准三态 Condition 已能完整表达一次执行:运行中为 `Unknown`、成功为 `True`、
|
||||
失败为 `False`。失败类型由稳定 reason 区分;这与 Tekton `TaskRun` 的状态约定一致。
|
||||
|
||||
不增加与 Conditions 重复的 `phase` 字段。面向 CLI 的阶段摘要由 printer columns 或客户端从
|
||||
Conditions 推导,避免两个状态源发生漂移。
|
||||
|
||||
常用 `Succeeded` reason 初始包括:
|
||||
|
||||
- `Pending`、`Scheduling`、`Running`;
|
||||
- `Completed`;
|
||||
- `ProcessFailed`;
|
||||
- `DeadlineExceeded`;
|
||||
- `Cancelled`;
|
||||
- `BackendLost`;
|
||||
- `ResultUnknown`。
|
||||
|
||||
Reason 是稳定、机器可读的 PascalCase 标识;message 面向人类且不得承载程序逻辑。
|
||||
|
||||
## 状态机
|
||||
|
||||
状态机名称用于设计、测试和 metrics,不增加持久化 `status.phase`。当前状态必须能够从 spec、
|
||||
deletionTimestamp、Conditions、时间和 execution references 唯一推导。
|
||||
|
||||
### 状态定义
|
||||
|
||||
| 状态 | 判定摘要 | 含义 |
|
||||
|---|---|---|
|
||||
| `Resolving` | `Accepted!=True`,非终态 | 等待 JobClass、Secret、ConfigMap 或 policy 解析 |
|
||||
| `Scheduling` | `Accepted=True`、`Scheduled!=True` | 选择 adapter 并幂等创建后端执行 |
|
||||
| `Starting` | `Scheduled=True`、无 `startTime` | 后端已存在,任务主体尚未确认开始 |
|
||||
| `Running` | 有 `startTime`、`Succeeded=Unknown` | 任务主体正在执行 |
|
||||
| `Cancelling` | `desiredState=Cancelled`、非终态 | 正在确认后端已经停止 |
|
||||
| `ResultUnknown` | `Succeeded=Unknown/ResultUnknown` | 无法证明任务仍在运行或已经停止 |
|
||||
| `Succeeded` | `Succeeded=True` | 成功终态 |
|
||||
| `Failed` | `Succeeded=False`,reason 非 `Cancelled` | 失败终态 |
|
||||
| `Cancelled` | `Succeeded=False/Cancelled` | 取消终态 |
|
||||
| `Deleting` | 存在 `deletionTimestamp` | finalizer 正在停止并清理后端,覆盖其他状态 |
|
||||
|
||||
存在多个判定条件时按 `Deleting → terminal → Cancelling → ResultUnknown → Running → Starting →
|
||||
Scheduling → Resolving` 的优先级推导,保证状态唯一。
|
||||
|
||||
`ResultUnknown` 不是终态,不设置 `completionTime`,也不启动 TTL。只有确认任务已经停止,才能
|
||||
转为成功、失败或取消。暂时无法联系后端不等于后端执行失败。
|
||||
|
||||
### 正常转移
|
||||
|
||||
```text
|
||||
Resolving
|
||||
│ 引用与策略解析完成
|
||||
▼
|
||||
Scheduling
|
||||
│ 后端逻辑执行已建立并持久化引用
|
||||
▼
|
||||
Starting
|
||||
│ adapter 确认任务主体开始
|
||||
▼
|
||||
Running ───────────────→ Succeeded
|
||||
└──────────────────→ Failed
|
||||
```
|
||||
|
||||
后端在任务主体开始前就确定失败,例如 image pull、runtime 不兼容或 provisioning 失败,可以从
|
||||
`Scheduling` 或 `Starting` 直接进入 `Failed`,此时 `startTime` 允许为空。
|
||||
|
||||
### 取消转移
|
||||
|
||||
```text
|
||||
Resolving ─┐
|
||||
Scheduling ─┤
|
||||
Starting ─┼→ Cancelling → Cancelled
|
||||
Running ─┤
|
||||
ResultUnknown ─┘
|
||||
```
|
||||
|
||||
尚未创建后端执行时,取消可以立即确认。已经存在或可能存在后端执行时,必须反复执行
|
||||
Cancel/Observe,确认不会继续运行后才能进入 `Cancelled`。取消请求与成功完成并发时,以先从
|
||||
后端确认到的不可逆事实为准:已经成功完成的任务保持 `Succeeded`,不能改写成 `Cancelled`。
|
||||
|
||||
### 不确定结果与恢复
|
||||
|
||||
```text
|
||||
Ensure/Observe 返回歧义
|
||||
↓
|
||||
ResultUnknown
|
||||
├── 找回执行 → Starting / Running
|
||||
├── 找到终态 → Succeeded / Failed / Cancelled
|
||||
└── 管理员确认无法继续 → Failed(BackendLost)
|
||||
```
|
||||
|
||||
在 `Ensure` 请求超时且尚未成功写入 external reference 时,adapter 必须使用 Job UID 查询后端,
|
||||
不能直接再次创建。Controller 重启后遵循相同规则。
|
||||
|
||||
### 删除与 TTL 转移
|
||||
|
||||
任意状态收到 deletionTimestamp 后进入 `Deleting`。若执行可能存在,先 Cancel/Observe,再
|
||||
Delete 后端资源并移除 finalizer。TTL controller 只对具有 `completionTime` 的三个终态发起
|
||||
删除;`Resolving`、`Scheduling`、`Starting`、`Running`、`Cancelling` 和 `ResultUnknown` 均不
|
||||
具备 TTL 删除资格。
|
||||
|
||||
### 状态不变量
|
||||
|
||||
- `Succeeded=True/False` 是不可逆终态;终态 reason、`completionTime` 和 result 不再改变。
|
||||
- `startTime` 和 `completionTime` 一旦设置不可改变;两者都存在时 completionTime 不早于
|
||||
startTime。
|
||||
- `Succeeded=True` 必须具有 `completionTime`,可以没有 exit code,但 adapter 应说明原因。
|
||||
- `Succeeded=False` 必须具有 `completionTime`;进程失败且能取得退出码时必须保存 exit code。
|
||||
- 非终态的 `Succeeded` 必须为 `Unknown`,不得省略为具有歧义的空状态。
|
||||
- `Scheduled=True` 前不得设置 `startTime`;一旦为 True 不再回退。
|
||||
- execution references 只能由 controller 写入;已有引用不能静默替换成新的逻辑执行。
|
||||
- Job UID 是执行幂等键;同名但不同 UID 的 Job 必须被视为不同执行。
|
||||
- `desiredState=Cancelled` 后不得创建新的后端执行。
|
||||
- reconcile 错误和退避不得修改任务的业务结果。
|
||||
|
||||
### 状态机测试矩阵
|
||||
|
||||
实现必须至少覆盖以下 table-driven unit tests,并为关键恢复路径提供 envtest:
|
||||
|
||||
| 类别 | 场景 | 必要断言 |
|
||||
|---|---|---|
|
||||
| 正常 | 创建、开始、退出 0 | 单次 Ensure,时间与成功终态正确 |
|
||||
| 正常 | 主进程非零退出 | `Succeeded=False/ProcessFailed` 与 exit code |
|
||||
| 解析 | JobClass 后创建 | 不提前 Ensure,引用出现后继续 |
|
||||
| 解析 | Secret/ConfigMap 或 key 后创建 | 不泄露值,解析后只启动一次 |
|
||||
| 后端 | image pull 或 provisioning 失败 | 未设置 startTime 的失败终态合法 |
|
||||
| 幂等 | Ensure 成功但 status 写入前崩溃 | 通过 UID 找回,不能创建第二次执行 |
|
||||
| 幂等 | 重复 reconcile 与重复事件 | 不产生额外执行,不改变终态时间 |
|
||||
| 恢复 | controller 在各非终态重启 | 从持久 status/reference 恢复正确状态 |
|
||||
| 未知 | Ensure/Observe 超时且结果不明 | 保持非终态,不设 completionTime,不触发 TTL |
|
||||
| 未知 | 后端恢复后找回运行任务 | 从 ResultUnknown 返回 Running |
|
||||
| 未知 | 管理员确认执行丢失 | 只在确认后进入 `Failed/BackendLost` |
|
||||
| 取消 | 在解析、调度、启动、运行阶段取消 | 不再创建或确认停止后才进入 Cancelled |
|
||||
| 竞态 | 取消与成功完成并发 | 已确认成功不被取消覆盖 |
|
||||
| 超时 | active deadline 到期 | 请求取消,确认停止后 `DeadlineExceeded` |
|
||||
| 删除 | 每个非终态阶段删除 | finalizer 清理完成前对象不消失 |
|
||||
| 删除 | 后端暂时不可达 | finalizer 保留并重试,不误报已清理 |
|
||||
| TTL | 三种终态到期 | 到期前不删,到期后带 UID precondition 删除 |
|
||||
| TTL | controller 在等待 TTL 时重启 | informer 恢复计时,最终删除一次 |
|
||||
| TTL | 到期附近延长 TTL | 最终 GET 重新核对最新 TTL |
|
||||
| 隔离 | 同名 Job 删除并以新 UID 重建 | 旧队列项和旧后端不得影响新 Job |
|
||||
| 校验 | 修改不可变字段或取消后恢复 Running | schema/CEL 拒绝请求 |
|
||||
| 引用 | OpenSandbox 保存 Sandbox 与 Command 引用 | 顺序重试后引用稳定且无凭据 |
|
||||
|
||||
### 时间
|
||||
|
||||
- `startTime`:adapter 确认任务主体开始执行的时间,而不是 CR 创建或 backend provisioning
|
||||
时间;设置后不可改变。
|
||||
- `completionTime`:进入最终成功、失败或取消状态的时间;设置后不可改变。
|
||||
|
||||
TTL 以 `completionTime` 为基准。若后端已经完成但结果暂时无法确认,不得猜测 completionTime。
|
||||
|
||||
### Execution reference
|
||||
|
||||
`status.execution` 是 execution 领域定义的正式 API 字段,保存 controller 重启后重新 Observe
|
||||
所需的最小稳定引用:
|
||||
|
||||
- `adapter`:实际 adapter 类型;
|
||||
- `references`:一个或多个由 adapter 定义的不透明外部引用。
|
||||
|
||||
```yaml
|
||||
execution:
|
||||
adapter: opensandbox
|
||||
references:
|
||||
- type: Sandbox
|
||||
id: sandbox-123
|
||||
- type: Command
|
||||
id: command-456
|
||||
```
|
||||
|
||||
单个 `externalID` 不足以表达 OpenSandbox 的 sandbox 与 command 两级资源。`type` 和 `id` 的
|
||||
值由对应 adapter 定义,调用者只能用于诊断和关联,不能据此实现领域逻辑。execution 领域将
|
||||
每个引用限制为 `type` 与 `id` 两个非空、有长度上限的字符串,不提供任意 metadata map 或
|
||||
raw JSON。引用不包含 endpoint、凭据或 Secret 内容。Kubernetes adapter 可以另外通过 owner
|
||||
reference 管理原生 Job,但仍需把恢复所需引用持久化,并保证同名重建安全。
|
||||
|
||||
该结构不提升为跨领域共享的万能 ExternalReference。VM、数据库和 LB 等领域根据真实后端
|
||||
需要定义自己的受限引用 schema,只有多个领域出现语义完全一致的实际重复后才考虑共享。
|
||||
|
||||
### Result
|
||||
|
||||
`result.exitCode` 只在后端能够确定主进程退出码时设置。调度失败、取消、后端丢失等情况可以
|
||||
没有退出码。`result.reason` 提供简短分类;详细诊断写入 Condition message 和 observability,
|
||||
不得把完整日志写入 status。
|
||||
|
||||
Job UID 是跨后端日志、metrics 和 traces 的主要 correlation identity。Adapter 必须将
|
||||
namespace、name 和 UID 传入执行环境或后端 metadata;高基数字段如何索引由 observability
|
||||
平台决定,API 不要求把 UID 配置为日志 label。
|
||||
|
||||
## 删除与 finalizer
|
||||
|
||||
Execution controller 在可能创建外部执行前添加
|
||||
`execution.ayatori.ddupan.top/job-cleanup` finalizer。
|
||||
|
||||
删除一个活动 Job 表示取消并清理,而不是 orphan:
|
||||
|
||||
1. 请求 adapter 终止执行;
|
||||
2. Observe,确认执行不会继续;
|
||||
3. 删除后端临时资源与短期凭据;
|
||||
4. 移除 finalizer。
|
||||
|
||||
首版不提供用户可选 orphan policy。让一次性任务脱离控制面继续运行既难以观察,也可能产生
|
||||
副作用。后端长期不可达时由管理员根据 runbook 判断并强制移除 finalizer,该操作必须可审计。
|
||||
|
||||
TTL controller 只发起 Job 删除,所有手工删除和 TTL 删除都经过相同 finalizer 路径。
|
||||
|
||||
## Adapter contract 对 API 的保证
|
||||
|
||||
每个 execution adapter 必须提供以下语义,而非暴露自身 SDK 类型:
|
||||
|
||||
```text
|
||||
Ensure 幂等地建立以 Job UID 标识的一个逻辑执行
|
||||
Observe 返回尚未开始、运行、成功、失败、取消或结果未知
|
||||
Cancel 请求停止且可被重复调用
|
||||
Delete 清理后端临时资源且可被重复调用
|
||||
```
|
||||
|
||||
`Ensure` 的网络超时不能直接触发第二次执行。Adapter 必须能够通过 UID/metadata 查找已创建的
|
||||
后端对象,或返回 `ResultUnknown` 交由人工处理。
|
||||
|
||||
Kubernetes adapter 与 OpenSandbox adapter 实现后,应复审 contract 和 API。只有两个真实
|
||||
实现都需要且语义相同的字段才提升为通用能力;后端特有功能优先进入 JobClass 或独立
|
||||
资源,不增加 `rawConfig`。
|
||||
|
||||
OpenSandbox 支持从 OCI image 创建 sandbox,但不保证每个 image 都能在所选 runtime、架构或
|
||||
安全 profile 下成功启动。Adapter 对已知不支持的组合应尽早报告;image pull、进程启动或
|
||||
运行时不兼容等实际后端失败最终统一表现为 `Succeeded=False`,并以 reason/message 保留可
|
||||
诊断原因。这不要求 Ayatori 在提交前证明任意 OCI image 一定可运行。
|
||||
|
||||
## 待后续设计
|
||||
|
||||
- capability-based class 自动选择;
|
||||
- 私有 image registry 的凭据和统一 workload identity;
|
||||
- artifact、workspace 与 cache 的独立 API;
|
||||
- Job 创建速率、并发、quota、公平调度以及是否集成 Kueue;
|
||||
- observability correlation 的具体 OpenTelemetry/Loki 字段约定;
|
||||
- 调用者错过 TTL 时是否需要可选的最小审计记录。
|
||||
|
||||
这些问题不阻塞首个 Kubernetes adapter 的 API review;image pull 的最小凭据路径必须在实现
|
||||
前通过 Kubernetes 与 OpenSandbox adapter 测试验证。
|
||||
|
||||
## 成熟实现参考
|
||||
|
||||
- [Kubernetes Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/)
|
||||
- [Kubernetes Job API](https://kubernetes.io/docs/reference/kubernetes-api/batch/job-v1/)
|
||||
- [Tekton Pipeline API](https://tekton.dev/docs/pipelines/pipeline-api/)
|
||||
- [Kueue Workload](https://kueue.sigs.k8s.io/docs/concepts/workload/)
|
||||
- [OpenSandbox API specifications](https://github.com/opensandbox-group/OpenSandbox/blob/main/docs/api/index.md)
|
||||
@@ -0,0 +1,407 @@
|
||||
# JobClass API v1alpha1 草案
|
||||
|
||||
- 状态:Draft
|
||||
- 日期:2026-09-17
|
||||
- API group:`execution.ayatori.ddupan.top`
|
||||
- Kind:`JobClass`
|
||||
- Scope:Cluster
|
||||
|
||||
## 目标
|
||||
|
||||
`JobClass` 是平台管理员提供给 Job 调用者的执行服务等级。名称表达稳定的用户语义,
|
||||
例如 `default`、`rootless`、`microvm` 或 `trusted-infra`;调用者不需要知道它当前由 Kubernetes
|
||||
还是 OpenSandbox 实现。
|
||||
|
||||
JobClass 负责:
|
||||
|
||||
- 选择拥有该 class 的 adapter/controller;
|
||||
- 引用 adapter 自己的强类型参数对象;
|
||||
- 限制允许使用该 class 的 namespace;
|
||||
- 提供跨后端一致的资源默认值与范围;
|
||||
- 向 Job controller 报告配置是否被接受、后端是否可用。
|
||||
|
||||
它不负责保存队列状态、并发配额、Job history 或任意后端 raw config。
|
||||
|
||||
## 设计依据
|
||||
|
||||
- Kubernetes `RuntimeClass` 使用 cluster-scoped class 将调用者与具体 runtime handler、调度约束
|
||||
和 overhead 隔离。
|
||||
- `StorageClass` 允许管理员用稳定名称提供不同服务等级,并由调用者显式或默认选择。
|
||||
- Gateway API `GatewayClass` 使用 `controllerName + parametersRef` 将稳定 class API 与实现专用
|
||||
参数分离,并通过 `Accepted` Condition 报告配置有效性。
|
||||
- Kueue `ResourceFlavor` 将资源规格与具体节点标签、taint 等 placement 细节分开。
|
||||
|
||||
Ayatori 采用 GatewayClass 风格的参数引用,不在 JobClass 中建立随 adapter 数量膨胀的
|
||||
union,也不使用 `map[string]any`。
|
||||
|
||||
## 示例
|
||||
|
||||
### Kubernetes execution
|
||||
|
||||
```yaml
|
||||
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||
kind: JobClass
|
||||
metadata:
|
||||
name: rootless
|
||||
annotations:
|
||||
execution.ayatori.ddupan.top/is-default-job-class: "true"
|
||||
spec:
|
||||
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||
parametersRef:
|
||||
group: execution.ayatori.ddupan.top
|
||||
kind: KubernetesExecutionParameters
|
||||
name: rootless
|
||||
allowedNamespaces:
|
||||
matchLabels:
|
||||
ayatori.ddupan.top/execution: enabled
|
||||
resources:
|
||||
defaults:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
maximum:
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 16Gi
|
||||
---
|
||||
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||
kind: KubernetesExecutionParameters
|
||||
metadata:
|
||||
name: rootless
|
||||
spec:
|
||||
serviceAccountName: ayatori-job
|
||||
runtimeClassName: runc
|
||||
scheduling:
|
||||
nodeSelector:
|
||||
ayatori.ddupan.top/node-role: execution
|
||||
tolerations:
|
||||
- key: ayatori.ddupan.top/execution
|
||||
operator: Equal
|
||||
value: "true"
|
||||
effect: NoSchedule
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
```
|
||||
|
||||
### OpenSandbox execution
|
||||
|
||||
```yaml
|
||||
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||
kind: JobClass
|
||||
metadata:
|
||||
name: microvm
|
||||
spec:
|
||||
controllerName: execution.ayatori.ddupan.top/opensandbox
|
||||
parametersRef:
|
||||
group: execution.ayatori.ddupan.top
|
||||
kind: OpenSandboxExecutionParameters
|
||||
name: microvm
|
||||
allowedNamespaces:
|
||||
matchLabels:
|
||||
ayatori.ddupan.top/microvm-access: "true"
|
||||
resources:
|
||||
defaults:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
maximum:
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 16Gi
|
||||
---
|
||||
apiVersion: execution.ayatori.ddupan.top/v1alpha1
|
||||
kind: OpenSandboxExecutionParameters
|
||||
metadata:
|
||||
name: microvm
|
||||
spec:
|
||||
endpoint: https://opensandbox-api.example.internal
|
||||
apiKeySecretRef:
|
||||
namespace: ayatori-system
|
||||
name: opensandbox-api
|
||||
key: api-key
|
||||
poolRef: microvm
|
||||
requestMapping: AdmissionOnly
|
||||
allowSecretEnv: true
|
||||
allowImageAuth: true
|
||||
```
|
||||
|
||||
## JobClass spec
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
controllerName: string
|
||||
parametersRef: ParametersReference
|
||||
allowedNamespaces: LabelSelector
|
||||
resources: ExecutionResourcePolicy
|
||||
```
|
||||
|
||||
### `controllerName`
|
||||
|
||||
必填、创建后不可变。使用 domain-prefixed path 标识负责处理该 class 和 Job 的 controller,例如:
|
||||
|
||||
```text
|
||||
execution.ayatori.ddupan.top/kubernetes
|
||||
execution.ayatori.ddupan.top/opensandbox
|
||||
```
|
||||
|
||||
它是 controller 所有权标识,不是任意可执行插件名称。一个 controller 只能处理自己明确支持
|
||||
的名称。未来 adapter 拆成独立 Deployment 时,class 和 Job API 无需改变。
|
||||
|
||||
### `parametersRef`
|
||||
|
||||
必填、创建后不可变:
|
||||
|
||||
```yaml
|
||||
parametersRef:
|
||||
group: execution.ayatori.ddupan.top
|
||||
kind: KubernetesExecutionParameters
|
||||
name: rootless
|
||||
```
|
||||
|
||||
v1alpha1 只允许引用 cluster-scoped 参数对象,并限制 `group`、`kind`、`name` 的长度与格式。
|
||||
每个 controller 明确列出支持的 kind;引用 ConfigMap、Secret 或未知 CRD 不被接受。
|
||||
|
||||
参数引用只有一层,参数 CRD 可以进一步引用 Secret 等运行配置。禁止嵌套通用参数链和 raw
|
||||
JSON,避免 class 成为无法校验的配置转发器。
|
||||
|
||||
### `allowedNamespaces`
|
||||
|
||||
可选 Kubernetes `LabelSelector`。Job 所在 namespace 必须匹配才可使用该 class。省略表示允许
|
||||
所有 namespace;这是显式的管理员选择,而不是用户能力。
|
||||
|
||||
该检查由 Job controller 执行,并应尽可能增加 CEL/admission policy 作为快速反馈。用户即使
|
||||
知道 privileged class 名称,也不能仅靠设置 `jobClassName` 绕过授权。
|
||||
|
||||
Namespace label 在 Job 被接受后发生变化,不中断已经运行的 Job,但影响新的 Job。紧急终止
|
||||
使用独立管理员操作,不通过修改 selector 隐式杀死任务。
|
||||
|
||||
### `resources`
|
||||
|
||||
可选,定义后端无关的 CPU、内存策略:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
defaults:
|
||||
requests: {cpu, memory}
|
||||
limits: {cpu, memory}
|
||||
minimum:
|
||||
requests: {cpu, memory}
|
||||
limits: {cpu, memory}
|
||||
maximum:
|
||||
requests: {cpu, memory}
|
||||
limits: {cpu, memory}
|
||||
```
|
||||
|
||||
规则为:
|
||||
|
||||
1. Job 未设置的值由 defaults 补齐;
|
||||
2. 解析结果必须满足 minimum/maximum;
|
||||
3. CPU 与内存 request 不得大于对应 limit;
|
||||
4. 解析后的 effective resources 写入 Job status;
|
||||
5. 后续修改 class 不改变已经接受的 Job;
|
||||
6. adapter 不能静默降低 effective resources。
|
||||
|
||||
只支持 CPU 和内存。GPU、临时磁盘等资源在出现真实后端需求后增加,不先复制完整 Kubernetes
|
||||
ResourceList。
|
||||
|
||||
Runtime/VM overhead 是 adapter 参数或后端调度实现,不计入用户请求的 task resources。
|
||||
Kubernetes adapter 应优先利用 RuntimeClass Pod overhead;OpenSandbox adapter 在其 capacity
|
||||
profile 中计算 microVM overhead。
|
||||
|
||||
## 默认 class 选择
|
||||
|
||||
Job 显式设置 `spec.jobClassName` 时始终优先使用该值。省略时按以下顺序解析:
|
||||
|
||||
1. Job namespace annotation
|
||||
`execution.ayatori.ddupan.top/default-job-class`;
|
||||
2. 唯一带有
|
||||
`execution.ayatori.ddupan.top/is-default-job-class: "true"` annotation 的 JobClass。
|
||||
|
||||
若不存在默认 class,Job 保持 `Accepted=False/NoDefaultJobClass`。若存在多个全局默认值,
|
||||
Job 保持 `Accepted=False/AmbiguousDefaultJobClass`,同时产生平台告警;不得模仿
|
||||
StorageClass 选择最新创建对象,因为执行隔离与权限不应随创建时间变化。
|
||||
|
||||
解析后 Job status 保存:
|
||||
|
||||
```yaml
|
||||
resolvedJobClass:
|
||||
name: rootless
|
||||
uid: 8aa4...
|
||||
controllerName: execution.ayatori.ddupan.top/kubernetes
|
||||
parametersRef:
|
||||
group: execution.ayatori.ddupan.top
|
||||
kind: KubernetesExecutionParameters
|
||||
name: rootless
|
||||
uid: c413...
|
||||
```
|
||||
|
||||
Job 后续 reconcile 使用已解析引用,不能因 namespace 默认值或全局默认 class 改变而切换
|
||||
adapter。若同名 class 被删除并重建,UID 不匹配,现存 Job 不得自动采用新对象。
|
||||
|
||||
## Status
|
||||
|
||||
```yaml
|
||||
status:
|
||||
observedGeneration: 1
|
||||
conditions:
|
||||
- type: Accepted
|
||||
status: "True"
|
||||
reason: Accepted
|
||||
observedGeneration: 1
|
||||
lastTransitionTime: ...
|
||||
- type: Ready
|
||||
status: "True"
|
||||
reason: BackendReachable
|
||||
observedGeneration: 1
|
||||
lastTransitionTime: ...
|
||||
```
|
||||
|
||||
### `Accepted`
|
||||
|
||||
表示 controller 已识别 controllerName,parametersRef 指向受支持且 schema 有效的对象,通用
|
||||
resource policy 自洽。无效 class 使用 `Accepted=False` 和稳定 reason,例如:
|
||||
|
||||
- `UnsupportedController`;
|
||||
- `InvalidParametersReference`;
|
||||
- `ParametersNotFound`;
|
||||
- `InvalidResourcePolicy`。
|
||||
|
||||
### `Ready`
|
||||
|
||||
表示该 class 当前具备接受新执行的基本条件。Kubernetes adapter 检查 RuntimeClass 等集群级
|
||||
依赖;具体 namespace 中的 ServiceAccount 在 Job 调度时检查。OpenSandbox adapter 检查参数
|
||||
引用、认证材料和后端健康端点。
|
||||
|
||||
`Ready=False` 阻止创建新的后端执行,但不改变已经开始 Job 的终态。Controller 仍必须尝试
|
||||
Observe、Cancel 和 Delete 已存在执行,不能因 class 不 Ready 而停止清理。
|
||||
|
||||
Ready 是观测值,不是容量预留。容量不足、排队和并发配额属于调度系统,不通过 Ready 频繁
|
||||
抖动。
|
||||
|
||||
## 生命周期与修改
|
||||
|
||||
- `controllerName` 和 `parametersRef` 不可变;切换后端必须创建新 class 名称。
|
||||
- `allowedNamespaces` 与 resource policy 可以修改,只影响尚未接受的新 Job。
|
||||
- adapter 参数对象允许更新 endpoint、Secret 引用和其他运维配置,以支持凭据轮换与故障切换。
|
||||
- 参数更新不得使 adapter 为现存 Job 创建新的逻辑执行;Job 中已持久化的 execution reference
|
||||
始终优先。
|
||||
|
||||
JobClass controller 添加保护 finalizer。删除 class 前必须确认不存在引用其 UID 的非终态
|
||||
Job。终态 Job 已完成后端清理,不阻塞 class 删除;其 TTL 回收不再需要 class 后端配置。
|
||||
|
||||
参数对象删除保护由各 adapter controller 负责。在仍有 class 引用时,参数对象不得被无提示
|
||||
删除。强制移除 finalizer 是管理员恢复操作,必须有 runbook 和审计记录。
|
||||
|
||||
## KubernetesExecutionParameters
|
||||
|
||||
这是 Kubernetes adapter 自己拥有的 cluster-scoped 管理员 API,不是 Job 用户 API。首版字段:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
serviceAccountName: string
|
||||
runtimeClassName: string
|
||||
scheduling:
|
||||
nodeSelector: map[string]string
|
||||
tolerations: []Toleration
|
||||
podSecurityContext: PodSecurityContext
|
||||
imagePullPolicy: Always | IfNotPresent | Never
|
||||
```
|
||||
|
||||
首版原生 `batch/v1 Job` 与 Ayatori Job 位于同一 namespace,因此 Secret/ConfigMap、ResourceQuota、
|
||||
NetworkPolicy、日志和 owner reference 都保持原生语义。普通调用者只拥有 Ayatori Job 权限,
|
||||
不应拥有修改生成的 batch Job/Pod 的权限。
|
||||
|
||||
`serviceAccountName` 是每个允许 namespace 中预先提供的同名 ServiceAccount。缺失时 Job 保持
|
||||
未调度并报告原因,不回退到 `default` ServiceAccount。
|
||||
|
||||
参数允许使用 Kubernetes 强类型的 Toleration 和 PodSecurityContext,因为这是明确属于
|
||||
Kubernetes adapter 的管理员 API;这不构成向 Job API 透传 PodSpec。
|
||||
|
||||
## OpenSandboxExecutionParameters
|
||||
|
||||
这是 OpenSandbox adapter 自己拥有的 cluster-scoped 管理员 API。首版字段:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
endpoint: string
|
||||
apiKeySecretRef:
|
||||
namespace: string
|
||||
name: string
|
||||
key: string
|
||||
poolRef: string
|
||||
requestMapping: AdmissionOnly | Native
|
||||
allowSecretEnv: bool
|
||||
allowImageAuth: bool
|
||||
```
|
||||
|
||||
- endpoint 必须为 HTTPS,Dev 显式允许的本地配置除外;不得包含认证信息。
|
||||
- API key 只通过 namespaced Secret 引用,status/Event 不显示内容。
|
||||
- poolRef 映射为 OpenSandbox 支持的 pool/profile 选择,不允许 Job 覆盖。
|
||||
- `requestMapping=Native` 要求后端忠实接受 requests 与 limits;`AdmissionOnly` 表示 requests
|
||||
只参与 Ayatori 准入,limits 映射为 OpenSandbox resourceLimits。
|
||||
- Secret env 与 per-request image auth 都会使 adapter 读取 Kubernetes Secret 并把解析值发送
|
||||
到 OpenSandbox API,必须由管理员分别显式启用。
|
||||
|
||||
OpenSandbox 参数不暴露任意 `extensions` map。未来确需使用某项 extension 时,将其提升为该
|
||||
参数 CRD 中经过校验的命名字段。
|
||||
|
||||
## Condition 与 Job 状态机交互
|
||||
|
||||
Job 只有在以下条件同时满足时进入 `Accepted=True`:
|
||||
|
||||
- class 已按默认或显式名称解析;
|
||||
- class UID 与已解析引用一致;
|
||||
- class `Accepted=True`;
|
||||
- namespace 符合 allowedNamespaces;
|
||||
- Job resources 成功解析并处于允许范围;
|
||||
- Job 使用的 Secret/ConfigMap 存在且可以按 optional 语义解析。
|
||||
|
||||
`JobClass Ready=False` 时,Job 保持 `Accepted=True`、`Scheduled=False`,等待后端恢复。
|
||||
这样 class 配置合法性与当前可用性不会混成同一状态。
|
||||
|
||||
若 Job 已经 Scheduled,后续 class Ready 或 namespace label 变化不撤销执行。若 class 或参数
|
||||
对象意外消失,controller 仍以 Job status 中的 controllerName、参数 UID 和 execution
|
||||
references 尝试恢复;无法安全观察时进入非终态 `ResultUnknown`,不能切换 class 重跑。
|
||||
|
||||
## 测试矩阵
|
||||
|
||||
实现至少覆盖:
|
||||
|
||||
- 显式 class、namespace 默认和全局默认的优先级;
|
||||
- 零个与多个全局默认 class;
|
||||
- allowedNamespaces 允许、拒绝及接受后 label 变化;
|
||||
- unsupported controllerName 和错误 parameters kind;
|
||||
- parameters 不存在、稍后出现、UID 删除重建;
|
||||
- resource defaults、min/max、request 大于 limit 和 quantity 边界;
|
||||
- class policy 更新不改变已接受 Job 的 effective resources;
|
||||
- class Ready=False 阻止新 Ensure,但不阻止现存执行 Observe/Cancel/Delete;
|
||||
- Kubernetes ServiceAccount/runtime 配置缺失且不回退;
|
||||
- OpenSandbox API key Secret 缺失、轮换及后端健康恢复;
|
||||
- allowSecretEnv/allowImageAuth 拒绝不允许的 Job;
|
||||
- class 删除被非终态 Job 阻止,终态清理后允许删除;
|
||||
- 同名 class 或参数对象以新 UID 重建时不劫持现存 Job。
|
||||
|
||||
## 延后事项
|
||||
|
||||
- class 级并发和速率限制;
|
||||
- Kueue LocalQueue/ClusterQueue 映射;
|
||||
- capability-based 自动 class 选择;
|
||||
- 多集群 Kubernetes executor;
|
||||
- GPU、临时磁盘和其他扩展资源;
|
||||
- workload identity 与 OpenSandbox Credential Vault;
|
||||
- class 成本、优先级与抢占策略。
|
||||
|
||||
## 成熟实现参考
|
||||
|
||||
- [Kubernetes RuntimeClass](https://kubernetes.io/docs/concepts/containers/runtime-class/)
|
||||
- [Kubernetes StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/)
|
||||
- [Gateway API GatewayClass](https://gateway-api.sigs.k8s.io/reference/api-types/gatewayclass/)
|
||||
- [Kueue ResourceFlavor](https://kueue.sigs.k8s.io/docs/concepts/resource_flavor/)
|
||||
Reference in New Issue
Block a user