# 动态 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 动态执行环境完成迁移后不应 部署。