重构为直接 OpenSandbox 生命周期调度
test / python (pull_request) Successful in 13s
test / shell (pull_request) Failing after 21s

This commit is contained in:
2026-09-18 18:12:10 +00:00
parent 6a58c95c5c
commit f3a199e7ba
10 changed files with 556 additions and 465 deletions
+39 -42
View File
@@ -1,55 +1,52 @@
# OpenSandbox Kata runner
# OpenSandbox runner
The OpenSandbox worker replaces the legacy direct Cloud Hypervisor launcher for
jobs labelled `self-hosted, vm`. It runs in the sandbox Kubernetes cluster and
uses two local control planes:
Gitea 的 `workflow_job` webhook 只负责发现带 `self-hosted,pod` 或
`self-hosted,vm` label 的 queued job。controller 不再把请求写入 NATS,而是携带由
homelab ExternalSecret 挂载的 API key 直接调用 OpenSandbox Lifecycle API:
1. OpenSandbox Lifecycle API creates a sandbox from the `ci-vm` Pool.
2. Kubernetes API exposes the concrete BatchSandbox allocation and manages its
short-lived `ClusterStaticEntry`.
```text
http://10.60.0.13:8080/v1/sandboxes
```
## Identity ordering
请求通过 `extensions.poolRef` 选择 `ci-pod` 或 `ci-vm`。地址是 VyOS HAProxy 的
内网 TCP frontend,backend 为 sandbox 两个节点上的固定 NodePort;不通过公网或
Cloudflare Tunnel。OpenSandbox key 只从 OpenBao `kv/k8s/opensandbox-api.api_key`
进入 homelab Secret,进程通过 `OPENSANDBOX_API_KEY_FILE` 读取。
The worker sends the stable repository/job identity and a generated runner name
as task environment. Once OpenSandbox has allocated a Pool Pod, the worker reads
the Pod UID and creates an entry with:
## 身份顺序
- parent: `spiffe://ddupan.top/spire/agent/k8s_psat/sandbox-kata/pod/<pod-uid>`;
- workload: `spiffe://ddupan.top/ci/<owner>/<repository>/<job>`;
- selector: `unix:uid:2000`.
Lifecycle 请求把稳定的 repository/task SPIFFE ID 放入 task environment。sandbox
集群内的 `opensandbox-identity` controller 读取 BatchSandbox allocation 得到实际
Pod UID,然后创建:
The Pool must run the runner task as UID 2000 and set
`shareProcessNamespace: true`. Its guest-local SPIRE Agent uses a Pod-bound PSAT
and exposes the Workload API through the shared `spire-agent-socket` emptyDir.
The runner image starts through `gitea-opensandbox-runner`, which waits until the
exact expected SVID is available before it registers with Gitea. This prevents a
job from starting between Pod allocation and entry reconciliation.
- parent:`spiffe://ddupan.top/spire/agent/k8s_psat/sandbox-kata/pod/<pod-uid>`;
- workload:`spiffe://ddupan.top/ci/<owner>/<repository>/<task>`;
- selector:`unix:uid:2000`。
The UID selector is the boundary between containers in the same Kata Pod. The
SPIRE Agent and privileged Docker daemon must not run as UID 2000. The runner may
access Docker only through a group-owned Unix socket.
job ID 只进入诊断 label,不进入业务身份。Pool 内 runner 进程固定使用 UID 2000,
Pod 设置 `shareProcessNamespace: true`;guest-local SPIRE Agent 使用 Pod-bound PSAT,
通过内存 emptyDir 暴露 Workload API。`gitea-opensandbox-runner` 等待精确 SVID,随后
通过一次性 nonce URL 领取 Gitea registration token。token 不进入 Lifecycle 请求、
BatchSandbox、镜像或 sandbox Secret。
## Required Pool contract
identity controller 只能读取 opensandbox namespace 的 BatchSandbox/Pod,并维护带
自身 label 的 ClusterStaticEntry。它不持有 OpenSandbox API key、Gitea token 或 Bao
凭据。BatchSandbox 消失后,对应 entry 在下一次 reconcile 删除;Pod 删除后,按 Pod
UID attestation 的临时 Agent 失去父级。
The `ci-vm` Pool template owns infrastructure that callers cannot override in
Pool mode:
## Pool 契约
- `runtimeClassName: kata-clh-runtime-rs` with block-backed emptyDir storage;
- runner image containing `gitea-opensandbox-runner` and SPIRE CLI;
- guest-local SPIRE Agent sidecar and projected audience `spire-server` token;
- `shareProcessNamespace: true`;
- runner UID 2000 and a distinct UID for every sidecar;
- ephemeral Gitea registration token delivery;
- Docker/BuildKit storage and socket entirely inside the Kata guest.
`ci-vm` 使用 `kata-clh-runtime-rs`;`ci-pod` 使用默认 runc。两者都要求:
The worker ServiceAccount needs read access to BatchSandboxes and Pods and
create/get/delete access to ClusterStaticEntries. OpenSandbox API credentials,
when enabled, are mounted from a Secret and read from
`OPENSANDBOX_API_KEY_FILE`.
- runner 镜像包含 Gitea Runner、Node.js action userspace、SPIRE CLI 和 identity gate;
- runner UID 2000,SPIRE Agent 与 privileged dockerd 使用不同 UID;
- Docker socket 通过 group 2000 共享,Docker 数据仅存在于 sandbox emptyDir;
- `self-hosted` 必须是所有 runner labels 的前缀;
- ephemeral/once runner 完成一项任务后退出。
## Cleanup
## 清理与恢复
On success, failure, timeout, or cancellation the worker deletes the
ClusterStaticEntry before deleting the sandbox. Both deletes accept `404`, so a
JetStream redelivery can safely repeat cleanup. The SPIRE Agent registration is
bound to the Pod UID and is removed by SPIRE after the Pod disappears.
controller 监控 Lifecycle 状态,在终止、失败、超时或取消时调用 DELETE。API delete、
identity entry delete 均接受对象已不存在。controller 重启时,OpenSandbox timeout
仍是最终回收边界;后续可基于 metadata list 恢复主动监控,但不得为此重新引入消息
队列。