diff --git a/README.md b/README.md index e292367..55e0c87 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ runs-on: [self-hosted, vm] - `controller`:接收 Gitea `workflow_job` webhook,将指定 label 的 queued job 发布到 NATS JetStream。 - `worker`:领取任务、限制并发,并通过 Pod 或 microVM backend 创建一次性环境。 -- `microvm-runner-launch`:为每个任务创建 COW disk、NoCloud seed 和 TAP,运行 +- `microvm-runner-launch`:为每个任务复制 flat qcow2 root disk(支持时使用 reflink)、创建 NoCloud seed 和 TAP,运行 Cloud Hypervisor,退出后完整清理。 - `guest-runner`:在 guest 中领取一次性 runner registration token,注册 ephemeral runner,执行一个 job 后关机。 @@ -51,6 +51,10 @@ pytest - NATS 密码、webhook secret 和 Gitea registration token 只从文件读取。 - registration token 不写入 seed image;worker 通过单次 nonce endpoint 交给 guest。 +- guest 启动时从仅监听 microVM bridge 的 worker endpoint 获取固定版本 Runner 和配置 + 资产;基础镜像无需为 Runner 发布而重做。 +- `runner-vm-bootstrap.yaml` 暂时只验证 VM 调度和生命周期,不提供 SPIFFE + identity;VM agent attestation 完成前不得将它当作身份链路验证结果。 - guest runner 使用 `--ephemeral`,每台 VM 只执行一个 job。 - launcher 只接受 UUID instance ID 和 URL-safe nonce,所有临时文件都位于独立目录。 - base image 不得包含 runner identity、registration token、SSH 密码或 host key。 diff --git a/config/runner-vm-bootstrap.yaml b/config/runner-vm-bootstrap.yaml new file mode 100644 index 0000000..93333fe --- /dev/null +++ b/config/runner-vm-bootstrap.yaml @@ -0,0 +1,11 @@ +runner: + capacity: 1 + timeout: 3h + shutdown_timeout: 1m + +host: + workdir_parent: /workspace + +container: + require_docker: false + valid_volumes: [] diff --git a/scripts/gitea-microvm-guest-runner b/scripts/gitea-microvm-guest-runner index 7d8d7ba..cee427f 100755 --- a/scripts/gitea-microvm-guest-runner +++ b/scripts/gitea-microvm-guest-runner @@ -6,6 +6,7 @@ instance=${2:?Gitea instance is required} runner_name=${3:?runner name is required} runner_labels=${4:?runner labels are required} token_file=/run/gitea-runner-registration-token +config_file=${GITEA_RUNNER_CONFIG_FILE:-/etc/gitea-runner/config-vm-bootstrap.yaml} cleanup() { rm -f -- "$token_file" @@ -25,4 +26,4 @@ gitea-runner register \ --labels "$runner_labels" \ --token-file "$token_file" rm -f -- "$token_file" -gitea-runner daemon +gitea-runner daemon --config "$config_file" --once diff --git a/scripts/microvm-runner-launch b/scripts/microvm-runner-launch index b84d0ce..558f232 100755 --- a/scripts/microvm-runner-launch +++ b/scripts/microvm-runner-launch @@ -39,7 +39,9 @@ trap cleanup EXIT INT TERM test -r "$base_image" test -r "$firmware" install -d -m 0700 "$state_root/instances" "$vm_dir" -qemu-img create -q -f qcow2 -F qcow2 -b "$base_image" "$overlay" +# Cloud Hypervisor cannot open qcow2 backing chains. Keep each disposable +# root disk flat; reflink-capable storage still makes this copy cheap. +cp --reflink=auto --sparse=always "$base_image" "$overlay" cat >"$vm_dir/meta-data" <"$vm_dir/user-data" < web.Response: return web.Response(body=value, headers={"Cache-Control": "no-store"}) +async def guest_assets(_: web.Request) -> web.FileResponse: + return web.FileResponse( + GUEST_ASSETS, + headers={"Cache-Control": "public, immutable"}, + ) + + async def heartbeat(message: object, stop: asyncio.Event) -> None: while True: try: @@ -162,6 +172,7 @@ async def consume() -> None: async def main() -> None: app = web.Application() app.router.add_get("/token/{nonce}", token) + app.router.add_get("/assets/guest-assets.tar.gz", guest_assets) runner = web.AppRunner(app) await runner.setup() await web.TCPSite(runner, TOKEN_LISTEN, TOKEN_PORT).start()