Files
homelab-infra/platform/dynamic-runner

Gitea dynamic runner

本目录部署单副本 Go controller,默认在同一进程运行 RunnerService scheduler、原生 Kubernetes Pod worker 和 SPIFFE mTLS facade。首轮生产 canary 只启用 scheduler,pod-worker,OpenSandbox VM worker 保持关闭:

Gitea RunnerService -> scheduler -> JetStream ci.runner.pod
                                      |
                                      v
                              homelab Kubernetes Pod
                                      |
                         SPIFFE mTLS RunnerService facade
                                      |
                                      v
                                     Gitea

Pod backend 不经过 OpenSandbox。VM backend 后续启用时才访问 VyOS 暴露的 OpenSandbox Lifecycle API;本目录不修改 sandbox 平台侧 ESO、Bao Terraform 或 OpenSandbox chart 所有权边界。

Canary 安全边界

  • Deployment 为单副本 Recreate,避免两个 scheduler 共享同一个 Gitea runner 身份。
  • controller 的 single-flight gate 只允许一个已领取 task 运行;Gitea 接受终态 UpdateTask 后才领取下一条。POD_CAPACITY=1 同时限制 worker 创建并发。
  • executor 镜像使用 digest;Pod 以 UID 2000 运行,SPIRE ClusterStaticEntry 同时绑定 具体 Pod UID 与 unix:uid:2000。
  • facade 只有 ClusterIP,executor 通过 dynamic-runner-controller.dynamic-runner.svc:8443 访问;双方使用 Workload API X509-SVID mTLS,不再保留公网 webhook/token endpoint。
  • controller 的 SPIFFE ID 固定为 spiffe://ddupan.top/ns/dynamic-runner/sa/dynamic-runner-controller。
  • NATS 保留既有最小权限分离:ci-producer 仅 publish,ci-worker 仅 pull/ACK。

当前 facade claim registry 与 single-flight gate 是进程内状态。异常重启时应先检查 现存 assignment Pod 和 Gitea task,再人工决定是否恢复 scheduler;不能通过扩大副本数 规避。完成 backend-driven recovery 前保持 replicas: 1。

Secret 边界

ExternalSecret/dynamic-runner 从既有 ClusterSecretStore/openbao 读取:

  • kv/k8s/nats:producer/worker 密码;
  • kv/k8s/opensandbox-api:api_key:保留给后续 VM worker;
  • kv/k8s/gitea-runner:token:保留的 runner registration token;
  • kv/k8s/dynamic-runner:scheduler UUID/token、facade HMAC key 和回滚所需 webhook secret。

scheduler credential 由官方 Gitea Runner v3.5.0 一次注册生成;它只挂载到 controller, 不会进入 executor。facade capability key 至少 32 字节,controller 为每个 assignment 确定性生成独立 capability。不要打印 Secret、创建静态 Bao token或把 credential 写入 Git。OpenBao 写入使用本机 SPIFFE JWT 换取的短期 local-development token。

首次验收

合并后先观察 Flux 与 controller,不要立即开启 VM:

flux reconcile kustomization dynamic-runner --with-source
kubectl -n dynamic-runner wait externalsecret/dynamic-runner \
  --for=condition=Ready --timeout=2m
kubectl -n dynamic-runner rollout status deploy/dynamic-runner-controller --timeout=5m
kubectl -n dynamic-runner logs deploy/dynamic-runner-controller -f

确认 scheduler 只领取一条 [self-hosted,pod] task,然后验证:

  1. assignment message 进入并离开 durable pod consumer;
  2. gitea-task-<task-id> Pod 创建,取得实际 Pod UID;
  3. 同名 ClusterStaticEntry 的 parent ID 包含该 UID,SPIFFE ID 使用 repository/job key;
  4. 官方 runner v3.5.0 经 facade claim 精确 task,Gitea 实时收到日志和终态;
  5. terminal gate 释放后才领取下一条任务。

当前代码尚未完成 ACK 后 backend lifecycle reconciler,因此 canary 成功后可能留下 Completed Pod/ClusterStaticEntry。首次测试要人工核对并删除已终态资源;在完整自动清理 通过前不得提高并发或启用 VM worker。

回滚

若 controller 在领取 task 前失败,回滚到前一 commit 的 Python controller/Pod worker manifests。若已经创建 gitea-task-* Pod,先保留现场并核对 Gitea task 状态,不能直接 重启 scheduler 造成重复执行。scheduler registration 和 capability key 保留在 Bao, 回滚不需要删除或打印它们。