From bfe9a1be58e55f011bf6fa62aeb52b045a2957a0 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Wed, 16 Sep 2026 14:10:01 +0000 Subject: [PATCH] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=8A=A8=E6=80=81=20Runner?= =?UTF-8?q?=20=E8=AE=BE=E8=AE=A1=E5=8E=9F=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++++---- docs/design-principles.md | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 docs/design-principles.md diff --git a/README.md b/README.md index ad241b1..b3f239c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,31 @@ -# Gitea microVM runner +# Gitea dynamic runner -为 Gitea Actions 按需启动 Cloud Hypervisor microVM。适合 kind、嵌套容器和其他不应 -在常驻 Kubernetes runner 中执行的 CI 工作负载。 +为 Gitea Actions 按需创建一次性执行环境。对 workflow 提供两种稳定的 runner +接口: + +```yaml +runs-on: [self-hosted, pod] +``` + +```yaml +runs-on: [self-hosted, vm] +``` + +`pod` 使用动态 Kubernetes Pod,`vm` 使用动态 Cloud Hypervisor microVM。每个环境 +只执行一个 job,并在 job 结束后连同本地状态一起销毁。完整的设计约束见 +[`docs/design-principles.md`](docs/design-principles.md)。 组件: - `controller`:接收 Gitea `workflow_job` webhook,将指定 label 的 queued job 发布到 NATS JetStream。 -- `worker`:在虚拟化宿主机领取任务,限制本机并发,并启动一次性 microVM。 +- `worker`:领取任务、限制并发,并通过 Pod 或 microVM backend 创建一次性环境。 - `microvm-runner-launch`:为每个任务创建 COW disk、NoCloud seed 和 TAP,运行 Cloud Hypervisor,退出后完整清理。 - `guest-runner`:在 guest 中领取一次性 runner registration token,注册 ephemeral runner,执行一个 job 后关机。 -- `jwt-broker`:运行在 Kubernetes runner 外层 Pod 中,以可被 SPIRE attestation - 的 PID 获取固定 `aud=zot` JWT-SVID;DinD job 通过受限 HTTP endpoint 获取短期 - token。broker 不记录响应、不缓存 token,也不接受调用方指定 audience。 +- `jwt-broker`:早期共享 Kubernetes runner 的过渡实验;目标架构不部署它,每个 + 动态 Pod 或 VM 直接取得自己的 SPIFFE 身份。 消息流使用一个 `WorkQueuePolicy` stream。相同 runner label 的所有 worker 共享同一 durable consumer;扩容只需要增加 worker 或提高单机 capacity。 diff --git a/docs/design-principles.md b/docs/design-principles.md new file mode 100644 index 0000000..cb5a143 --- /dev/null +++ b/docs/design-principles.md @@ -0,0 +1,81 @@ +# 动态 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 `workflow_job` webhook 创建执行环境。每个 Pod 或 VM 注册一个 +ephemeral runner,只执行一个 job;任务结束后注销 runner,并删除计算环境及其全部 +本地状态。 + +`job_id` 仅用于消息去重、状态追踪、实例关联和失败清理,不进入 workload 身份,也 +不参与资源授权。 + +## 环境只提供运行边界 + +基础镜像只提供启动 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//// +``` + +同一种任务在不同运行中使用相同的逻辑 SPIFFE ID;每次运行取得独立、短期的 SVID。 +Pod 与 VM 是可替换的执行实现,因此默认不写入 SPIFFE ID。 + +workflow 和 job 名称必须经过确定性的路径规范化。规范化结果必须保留仓库边界,并在 +发生冲突时拒绝创建环境,不能静默地让两个任务共享身份。 + +## Self-service 与授权边界 + +新增 workflow 或 job 时,controller 自动为它派生身份,不维护第二份任务或角色 +allowlist。能够修改仓库 CI 的主体本来就能修改该仓库已有任务,因此 controller 的 +重复审批不能形成额外的安全边界,只会破坏 self-service。 + +身份不等于权限。新任务可以立即取得自己的 SPIFFE ID,但默认不会因此获得 Zot、 +OpenBao 或其他资源的特殊权限。资源所有者在资源端按照有意义的 workflow/job 身份 +配置授权策略。 + +## 非目标设计 + +目标架构不依赖以下机制: + +- 多个 job 共享的常驻 Docker daemon。 +- 常驻 runner Pod 的共享 SPIFFE 身份。 +- 为嵌套 CI 容器转发共享身份的 JWT broker。 +- 将 Gitea 数字 job ID 编入 SPIFFE ID。 +- controller 维护的仓库任务权限 allowlist。 + +仓库中的 `jwt-broker` 是早期方案的实验实现,在 Pod/VM 动态执行环境完成迁移后不应 +部署。