98 lines
4.2 KiB
Markdown
98 lines
4.2 KiB
Markdown
# 动态 Runner 设计原则
|
||
|
||
## 对 workflow 的接口
|
||
|
||
Runner 只向 workflow 暴露两个执行环境:
|
||
|
||
```yaml
|
||
runs-on: [self-hosted, pod]
|
||
```
|
||
|
||
```yaml
|
||
runs-on: [self-hosted, vm]
|
||
```
|
||
|
||
- `self-hosted` 是固定前缀。
|
||
- `pod` 表示一次性 Kubernetes Pod,承担常规 CI、镜像构建和 kind 等任务。
|
||
- `vm` 表示一次性 microVM,承担需要独立内核、KVM、systemd 或更强隔离的任务。
|
||
|
||
执行后端是基础设施选择,不是权限角色。workflow 不需要额外声明由 controller
|
||
维护的 role 或权限 label。
|
||
|
||
## 一个 job,一个环境
|
||
|
||
Controller 通过 Gitea RunnerService 原子领取具体 task,再把版本化 assignment 交给
|
||
一个一次性 Pod 或 VM。executor 直接执行已经领取的 task,不再注册临时 runner 去
|
||
二次竞争任务;任务结束并回报 Gitea 后删除计算环境及其全部本地状态。
|
||
|
||
`job_id` 仅用于消息去重、状态追踪、实例关联和失败清理,不进入 workload 身份,也
|
||
不参与资源授权。
|
||
|
||
assignment ID 只用于消息去重、状态追踪、实例关联和失败清理,不进入 workload 身份,
|
||
也不参与资源授权。worker 通过 assignment ID 从 Kubernetes labels 或 OpenSandbox
|
||
metadata 恢复 executor;JetStream 不保存 executor 生命周期状态。
|
||
|
||
Pod executor 创建后,controller 使用实际 Pod UID 创建幂等 `ClusterStaticEntry`,将
|
||
SPIFFE ID绑定到该 Pod 的 workload selector。executor 必须等目标 SVID 可用后才执行
|
||
workflow 的第一步。
|
||
|
||
## 环境只提供运行边界
|
||
|
||
基础镜像只提供启动 runner 和执行 workflow 所需的最小环境。Docker、BuildKit、
|
||
kind 等工具由 pipeline 按需安装和启动,而不是由 controller 预制成常驻服务。
|
||
|
||
例如 Pod job 可以在 Pod 内启动仅供本次任务使用的 Docker daemon。该 daemon 及其
|
||
镜像、容器和缓存属于当前 job 的临时状态,随 Pod 一起销毁。Docker 创建的容器不是
|
||
独立的身份边界;需要访问凭据的操作由 Pod 中的 workflow 进程完成,并通过环境变量
|
||
或标准输入把短期凭据交给具体工具。
|
||
|
||
## Workload 身份
|
||
|
||
动态 Pod 和 VM 都直接拥有自己的 SPIFFE 身份,不继承常驻 runner 的共享身份:
|
||
|
||
- Pod 通过 Kubernetes workload attestation 取得身份。
|
||
- VM 通过 VM 内的 SPIRE Agent 取得身份。
|
||
|
||
SPIFFE ID 由具有业务意义且稳定的 workflow 上下文派生:
|
||
|
||
```text
|
||
spiffe://ddupan.top/ci/<owner>/<repository>/<job-key>
|
||
```
|
||
|
||
同一种任务在不同运行中使用相同的逻辑 SPIFFE ID;每次运行取得独立、短期的 SVID。
|
||
Pod 与 VM 是可替换的执行实现,因此默认不写入 SPIFFE ID。
|
||
|
||
job key 必须满足 `[A-Za-z_][A-Za-z0-9_-]*`,展示名称 `name` 不参与身份计算。同一
|
||
仓库内需要不同权限的任务应使用不同的 job key;workflow 文件只是编排载体,不进入
|
||
权限身份。
|
||
|
||
## Self-service 与授权边界
|
||
|
||
新增 workflow 或 job 时,controller 自动为它派生身份,不维护第二份任务或角色
|
||
allowlist。能够修改仓库 CI 的主体本来就能修改该仓库已有任务,因此 controller 的
|
||
重复审批不能形成额外的安全边界,只会破坏 self-service。
|
||
|
||
身份不等于权限。新任务可以立即取得自己的 SPIFFE ID,但默认不会因此获得 Zot、
|
||
OpenBao 或其他资源的特殊权限。资源所有者在资源端按照有意义的 job 身份
|
||
配置授权策略。
|
||
|
||
## 非目标设计
|
||
|
||
目标架构不依赖以下机制:
|
||
|
||
- 多个 job 共享的常驻 Docker daemon。
|
||
- 常驻 runner Pod 的共享 SPIFFE 身份。
|
||
- 为嵌套 CI 容器转发共享身份的 JWT broker。
|
||
- 将 Gitea 数字 job ID 编入 SPIFFE ID。
|
||
- controller 维护的仓库任务权限 allowlist。
|
||
|
||
仓库中的 `jwt-broker` 是早期方案的实验实现,在 Pod/VM 动态执行环境完成迁移后不应
|
||
部署。
|
||
|
||
## 实现依赖原则
|
||
|
||
基础设施协议优先使用上游维护的成熟客户端,不在 controller 内重复实现认证、连接、
|
||
资源编码或错误语义。Kubernetes 使用 `client-go`,NATS JetStream 使用 `nats.go`,
|
||
Gitea RunnerService 使用 `actionslib`,OpenSandbox Lifecycle API 使用官方 Go SDK;
|
||
自定义代码只保留领域模型、reconcile 规则及上游客户端未覆盖的最小适配层。
|