Files
gitea-dynamic-runner/docs/runner-protocol-roadmap.md
T
panxiao81 fadc93a0bf
test / python (pull_request) Successful in 13s
test / shell (pull_request) Successful in 20s
test / go (pull_request) Successful in 5m54s
feat: 为执行后端增加独立容量池
2026-09-21 07:21:38 +00:00

118 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea Runner 协议调度器路线
## 目标
长期形态不依赖 `workflow_job` webhook 发现工作。controller 本身作为 Gitea Runner
协议客户端注册,并声明 `self-hosted``pod``vm` labels;单个 registration 内按总
配置容量启动多个 `FetchTask` goroutine,再将 task 按 `runs-on` 交给 Pod 或 VM 的独立
容量池,由一次性 Pod 或 microVM 执行。
```text
Gitea RunnerService
│ Register / Declare / FetchTask
dynamic-runner scheduler
│ 已领取的 task + lease
├── Pod executor
└── microVM executor
│ logs / state / result
└──────────────────────► Gitea
```
controller 使用单一 Go 二进制;默认在同一进程启用 `scheduler``pod-worker`
`vm-worker`,也可通过 `--components` 只启用其中一部分。组件是独立应用服务边界,
共享进程不意味着共享后端状态或把 assignment 降级为内存 channel。
首轮集成的 facade pending/claimed registry 与三个组件同进程。虽然二进制保留组件选择
接口,但当前会拒绝“worker 不带 scheduler/facade”的拆分部署:普通 Kubernetes Service
无法保证 executor 回到持有其 assignment 的 replica。后续拆分必须增加按 assignment
路由或可重建的 claim 分发,不能新增一套生命周期数据库来掩盖该问题。
executor 直接运行固定版本的官方 Gitea Runner 二进制,不 fork workflow 执行引擎。
controller 暴露兼容 RunnerService 的 facade`FetchTask` 只返回已分配 assignment
`UpdateTask``UpdateLog` 转发真实 Gitea。facade 同时验证逻辑 SPIFFE ID、assignment
ID,以及由 controller 密钥确定性生成的 assignment HMAC capability;该 capability
只绑定执行实例,不参与 Zot/OpenBao 等业务授权。
executor 为官方 runner 生成与 v3.5.0 schema 一致的一次性 `.runner` 文件,并以
`daemon --once` 启动。runner 只访问 executor 内的 loopback HTTP proxyproxy 使用
`go-spiffe` 从 Workload API 持续取得和轮换 X509-SVID,再以 mTLS 连接 controller
facade,并严格校验 facade 的 SPIFFE ID。这样无需修改 runner 或把静态客户端证书写入
镜像。assignment capability 会进入一次性 executor 环境,但不会进入 label、annotation
或 OpenSandbox metadata;它只对该 assignment 有效,并且不能绕过 SPIFFE 身份校验。
这与“收到 webhook 后临时注册另一个 act_runner”不同。`FetchTask` 已经完成任务分配,
不能再期待 Gitea 把同一个 task 分配给随后启动的 runner。协议调度器必须让 executor
执行已经领取的 task,并继续完成日志、状态、心跳、取消和最终结果上报。
## 设计约束
- 对 workflow 的接口保持 `[self-hosted, pod]``[self-hosted, vm]` 不变。
- scheduler 使用单一 Gitea runner UUID/token 和一个 `Declare`,不为并发槽位重复注册;
`POD_CAPACITY + VM_CAPACITY` 决定并发 `FetchTask` goroutine 数量。
- task 领取并持久化后按 backend 进入独立 durable consumer;对应容量池已满时延迟 NAK,
assignment 保持 JetStream pending,且不得创建超出配置容量的 workload。
- scheduler Declare 后使用 RunnerService 长轮询;一旦 FetchTask 返回已分配 task,在
JetStream publish 成功前只重试该 assignment,不领取下一项。
- 每个 executor 只执行一个 task,完成后销毁。
- SPIFFE 身份从实际领取的 task 的 repository 和 workflow job key 派生,不需要 queued 与
in-progress webhook 的二阶段关联。
- 身份中的 task 段使用 workflow job key,而不是可带空格的展示名称;job key 必须满足
`[A-Za-z_][A-Za-z0-9_-]*`。slug + hash 只保留为旧名称的显式迁移后备方案。
- scheduler 只做确定性的身份派生与 executor 绑定,不维护业务授权 policy;Zot、
OpenBao 等资源服务继续是唯一授权决策点。
- scheduler 的 runner registration credential 不进入 executorexecutor 只得到执行
当前 task 所需的短期 lease/capability。
- JetStream 只持久化和投递 assignment,不保存 executor 生命周期状态。Pod labels/annotations
与 OpenSandbox metadata 是后端运行状态的权威来源,Gitea 是 task 终态的权威来源。
- assignment 使用版本化 envelope 保存完整 Gitea protobuf task,并从 workflow `runs-on`
严格选择 pod 或 vm subject;消费者解码后重新派生 backend 与身份,拒绝被篡改的冗余字段。
- JetStream 的 message ID 等于稳定 assignment ID `gitea-task-<task-id>`,仅用于发布去重,
不承担 executor 生命周期记录。
- worker 按稳定 assignment ID reconcile 后端资源,进程内只保留并发控制等可丢弃状态;
不新增数据库,也不依赖内存中的 runner-to-executor 映射。
- VM worker 使用 OpenSandbox 官方 Go SDK,并把 assignment ID、repository、job key 和
SPIFFE ID写入 sandbox metadata;通过 `extensions.poolRef=ci-vm` 使用既有 Kata Pool。
- executor 成功 claim 后 ACK assignment。Gitea 接受 terminal update 后,facade 在后端
metadata 写入持久 terminal markerbackend reconciler 仅在执行环境也进入终态后清理,
从而关闭进程重启窗口且避免删除尚未完成结果上报的环境。
- pod 与 vm 使用独立 durable consumer、进程内 admission pool 和并发上限。consumer 只负责将 assignment
幂等落到后端;executor 与身份恢复 metadata 持久化后立即 `DoubleAck`。尚未取得
Pod UID 等短暂未就绪状态以及临时后端错误使用延迟 NAK。
- admission pool 只保存可重建的并发状态:启动时从 Pod labels/annotations 或 OpenSandbox
metadata 恢复非终态 assignmentterminal update 持久化成功后释放槽位,不引入新存储。
- assignment ACK 后的运行、结果回报和清理由 backend reconciler 根据 Kubernetes、
OpenSandbox 与 Gitea 的事实状态驱动,不继续占用 JetStream delivery。
- consumer 在 executor 使用上述 facade 成功 claim task 后确认 assignment;无需把完整
task 写入 Pod annotation、OpenSandbox metadata 或环境变量。
- Pod 与 VM 共享 task/executor 协议,只有环境创建和销毁实现不同。
- scheduler 在 assignment 持久化到 JetStream 后即可继续领取;Pod 与 VM 分别由 durable
consumer 的 capacity 限制并发,不共享全局执行槽位。未知后端故障由对应 consumer 的
NAK/redelivery 收敛,不能阻塞另一种 backend。
- 两种 backend 都注入同一份 runner bootstrap 环境;Pod 仍由 homelab Kubernetes 原生
创建,只有 VM 经 OpenSandbox 创建,bootstrap 机制不改变 backend 边界。
## 实现顺序
1. 固定当前 Gitea 版本所使用的 RunnerService protobuf 与 act_runner 版本,记录兼容
范围并建立协议契约测试。
2. 实现只注册、Declare labels 和容量感知 FetchTask 的 scheduler spike,暂不执行
task。首次集成必须验证 FetchTask 后、JetStream publish 前进程崩溃时 Gitea 对同一
runner 的 task 恢复语义;该窗口未验证前不能声称 scheduler 可无损恢复。
3. 从 act_runner 提取或复用 task 执行与日志上报能力,定义 scheduler 到 executor 的
单任务协议。
4. 首先接入 Pod executor,验证成功、失败、取消、超时和 scheduler 重启。
5. 接入 microVM executor,并复用同一 task 协议和身份派生逻辑。
6. 双轨运行并验证后,移除 webhook receiver、临时 runner 注册和 identity binding
subject。
## Bootstrap 实现的退出条件
只有同时满足以下条件才能删除 webhook 路径:
- scheduler 能通过 RunnerService 稳定领取并执行 Pod/VM task
- Gitea UI 中的实时日志、取消、超时和结论与官方 runner 行为一致;
- scheduler 重启不会丢失已领取 task,也不会重复执行;
- SPIFFE 身份只来自实际领取 task;
- 同一套 workflow 无需修改 `runs-on` 即可从 bootstrap 迁移。