412 lines
14 KiB
Markdown
412 lines
14 KiB
Markdown
# 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
|
||
allowInsecureHTTP: false
|
||
```
|
||
|
||
`endpoint` 默认必须使用 HTTPS。隔离的本地开发环境可以显式设置
|
||
`allowInsecureHTTP: true` 使用 HTTP;生产配置不得启用该开关。
|
||
|
||
## 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/)
|