# OpenSandbox Gitea runner Pools 本目录只部署零预热的 `ci-vm` Pool、sandbox 内的 SPIFFE identity controller,以及仅供 内网 VyOS 转发的 OpenSandbox NodePort。`ci-vm` 使用 `kata-clh-runtime-rs`,每个 VM 执行单一任务并在结束后删除。普通 Pod job 直接运行在 homelab,不进入 OpenSandbox。 Pool 中 task-executor 接收 Lifecycle API 下发的进程环境。guest-local SPIRE Agent 用 Pod-bound PSAT 向中央 SPIRE 注册;identity controller 从 BatchSandbox allocation 取得真实 Pod UID,再创建精确的 `ClusterStaticEntry`。runner 只有拿到请求中的完整 repository/task SVID 后才领取一次性 Gitea registration token。 VM 与 Pod backend 使用同一个 `gitea-dynamic-runner-runner` executor 镜像。Pool 不运行 常驻 `docker:dind` sidecar;需要 Docker 的 workflow 在 privileged executor 内按任务启动 daemon。Kata VM 中的 workflow 必须先把稀疏 ext4 镜像 loop-mount 到 `/var/lib/docker`,并完成 cgroup v2 nesting 初始化。 `ci-vm` executor 固定请求并限制为 2 CPU/3GiB。真实 kind canary 表明 1 CPU/约 2GiB 虽然能最终启动全部 control-plane 容器,但无法在 kubeadm 超时前提供可用的 API server; 该规格是 nested Kubernetes 任务的容量下限,不是用资源掩盖存储阻塞。 这些 `ClusterStaticEntry` 位于 sandbox 集群,由 central SPIRE Server 内的 `spire-controller-manager-sandbox` 通过受限 external kubeconfig reconcile。必须在 `platform/spire/values.yaml` 显式启用 external controller-manager 的 `reconcile.clusterStaticEntries`(chart 默认关闭);仅看到 CR 存在但没有 status,不算 身份链路就绪。 ## 部署依赖 - OpenSandbox chart 和 CRD 已 Ready; - RuntimeClass `kata-clh-runtime-rs` 已存在; - 中央 SPIRE 已发布 `ConfigMap/opensandbox/spire-bundle-pem`; - VyOS `10.60.0.13:8080` 转发 sandbox1/2 的 NodePort `30080`; - runner/controller 镜像均使用 Zot digest,而不是可变 tag。 本目录不读取 OpenBao,也不修改 OpenSandbox 平台侧 ExternalSecret、ClusterSecretStore 或 Bao policy。OpenSandbox API key 只存在于平台 server Secret 和 homelab controller Secret,两边由各自身份读取同一 Bao 资源。 ## 上线验收 先确认 Secret 和 API 认证,命令不得输出 key: ```bash kubectl -n dynamic-runner wait externalsecret/dynamic-runner --for=condition=Ready --timeout=2m kubectl -n dynamic-runner exec deploy/dynamic-runner-controller -- \ wget -qO- http://10.60.0.13:8080/health ``` 然后触发一个 `runs-on: [self-hosted, vm]` 的最小 workflow,并同时观察: ```bash kubectl -n dynamic-runner logs deploy/dynamic-runner-controller -f kubectl -n opensandbox get pool,batchsandbox,pod -w kubectl get clusterstaticentry -l app.kubernetes.io/component=opensandbox-identity -w kubectl -n opensandbox logs deploy/opensandbox-identity -f ``` 合格证据必须同时包含: 1. Lifecycle create 成功并选择 `ci-vm`; 2. 分配 Pod 的 `runtimeClassName` 为 `kata-clh-runtime-rs`; 3. entry 的 parent ID 包含该 Pod 的 UID,SPIFFE ID 使用 repository/task; 4. Gitea 显示临时 runner 带 `self-hosted,vm` labels 并完成真实任务; 5. 任务后 BatchSandbox、Pod、ClusterStaticEntry 和临时 runner 均消失。 清理超时对象时只调用 Lifecycle DELETE,不直接删除 Pool Pod。若 controller 已不可用, 可从 OpenSandbox API 按 metadata 定位 sandbox 后执行 DELETE;不要绕过 API 伪造状态。 ## 故障定位 - `401`:检查 homelab ExternalSecret Ready 和文件挂载,不打印 Secret; - `PoolCapacityExhausted`:检查 `ci-vm` 的 `poolMax` 及残留 BatchSandbox; - runner 等待 SVID:核对 allocation Pod UID、ClusterStaticEntry parentID、guest Agent 日志; - runner 等待 token:核对 `192.168.10.127:8787` 的 sandbox 到 homelab 路由; - Docker 任务失败:检查 workflow 的 job-local dockerd 日志及 `/var/run/docker.sock`;Pool 不提供共享或常驻 daemon。 - kind node 的 systemd 报 `Failed to create /init.scope` 或 `Structure needs cleaning`:确认 workflow 启动 dockerd 前完成 cgroup v2 nesting 初始化。否则 子容器的私有 cgroup namespace 根会变成 `threaded`,systemd 无法创建 domain cgroup。 - kind 的 kubeadm 卡在 `CreateContainer`,而 nested containerd goroutine 停在 `bbolt` 的 `fdatasync`:不要直接把 Kata shared mount 用作 Docker 数据目录。 workflow 必须在 guest 内创建稀疏 ext4 镜像并 loop-mount 到 `/var/lib/docker`;该镜像随 Sandbox 删除,不得在节点侧遗留。