返回资讯中心
外部精选
软件工程
#开发者实时流

基于 Cloudflare Containers 的临时 GitHub Actions 自托管运行器开源

来源标题:Show HN: Ephemeral GitHub Actions Runners on Cloudflare Containers

开发者发布了一个在 Cloudflare Containers 上运行临时 GitHub Actions 自托管 runner 的方案:Worker 收到 workflow_job webhook 后按标签启动容器,容器注册 JIT runner 跑完单个任务即退出,任务间隙闲置成本为 0。该版本已通过 104 个单元测试,但尚未在全新 Cloudflare 账号上完成端到端实测。

Hacker News 最新kelvindsmn2026/11/12 · 今天24 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

在 Cloudflare Containers 上运行临时的 GitHub Actions 自托管 runner。

Cloudflare Worker 接收 GitHub 的 workflow_job webhook。对于每个标签匹配的排队作业,它会启动一个容器。该容器注册一个即时(JIT)runner,只运行那一个作业,然后退出,计费随即停止。作业之间不运行任何内容,因此空闲成本为 $0。

状态(2026-11-12):从一个 runner 设计中提取出来,该设计在 2026 年 7 月至 10 月期间承载了一个生产仓库的 CI(事后分析数据)。这个独立版本通过了它的 104 个单元测试和密钥扫描。尚未在全新的 Cloudflare 账户上进行实时端到端运行;如果你部署了它,请开一个 issue 说明哪些有效、哪些无效。

是什么:在工作流中写 runs-on: [self-hosted, cf-runner] → 一个全新的 1 vCPU / 3 GiB 容器(或使用 cf-runner-heavy 时的 4 vCPU / 12 GiB)启动,运行作业然后消失。

面向谁:拥有 Cloudflare Workers Paid 计划的团队,希望为私有仓库获得廉价、更大的 runner,而无需自行运维基础设施。

GitHub ── workflow_job (queued) ──▶ Worker /webhook │ 验证 HMAC → 允许列表 → 路由标签 → 类别 ▼ Dispatcher(Durable Object,单例) │ 在 MAX_CONCURRENT 下准入,间隔启动 │ POST generate-jitconfig(一次性 runner) ▼ LightRunner / HeavyRunner(每个作业一个 DO + 容器) │ ./run.sh --jitconfig … → 一个作业 → 退出 │ 每 15 秒 alarm:存活检查 + 硬性截止时间 ▼ 容器退出 → 计费停止

成本模型 Cloudflare Containers 在容器运行期间按秒计费,计费项包括: Workers Paid 包含每项的月度配额。已停止的容器不产生费用,而这里的容器只在一个作业的持续时间内存在(加上约一分钟的启动时间,以及如果没有作业到达时的 90 秒空闲退出)。

每个作业成本 ≈ (作业分钟数 + ~1.5 分钟启动) × 60 × (vCPU × cpu_price × cpu_utilisation

  • GiB × mem_price
  • GB × disk_price) 每月 ≈ 对各个作业求和,减去包含的配额 最坏情况 = MAX_CONCURRENT × 60 分钟/小时 × 每容器每小时价格

按本文撰写时的标价($0.000020/vCPU-s、$0.0000025/GiB-s、$0.00000007/GB-s),一个完全繁忙的重型容器(4 vCPU、12 GiB、20 GB)每小时最多约 $0.40,轻型容器(1 vCPU、3 GiB、6 GB)约 $0.10/小时。CPU 按活跃使用计费,因此等待网络的真实作业成本更低。

并发度改变的是积压排空的速度,而不是成本:同样的作业在 4 宽或 40 宽下消耗相同的容器分钟数。

可选的 SOFT_DAILY_BUDGET_MINUTES 每天记录一次 BUDGET ALERT:当超出时。它从不拒绝作业。参见 docs/LESSONS.md:被拒绝的 spawn 会让作业静默排队,直到 GitHub 在 24 小时后使其过期,这比任何账单都糟糕。

你需要:一个 Workers Paid 计划上的 Cloudflare 账户(Containers 需要它)、本地运行 Docker(wrangler 构建镜像)、Node 20+ 和 pnpm。

GitHub → Settings → Developer settings → GitHub Apps → New GitHub App(如果仓库属于某个组织,则在组织下创建)。

权限(仅授予这些;不使用其他权限): RUNNER_SCOPE=repo(默认,runner 按仓库注册): RUNNER_SCOPE=org(runner 注册到组织):

每个调用使用哪个 token。每个安装 token 都以 repositories: [<作业的仓库>] 铸造,因此它只能触及其拥有作业的仓库:作业状态、排队的运行、运行取消,以及(仓库范围)runner 端点。唯一的例外是 org 范围的 /orgs/{org}/actions/runners/* 调用(generate-jitconfig、list、delete):组织权限不会搭载在仓库收窄的 token 上,因此这些调用使用安装范围的 token,且仅限这些。

仅当你设置 UNWEDGE_CANCEL=1 时:将 Repository → Actions 提升为 Read and write(它会取消固定卡住 runner 的运行)。

创建 App,记下其 App ID,生成私钥(.pem),然后在你要允许列表的仓库上 Install App(选择 "Only select repositories")。

没有 App?具有相同仓库权限的细粒度个人访问令牌也可以(secret GITHUB_PAT)。首选 App:其 token 是短期的且限定于安装范围。

git clone https://github.com/kelvindesman/cf-container-runners.git cf-container-runners && cd cf-container-runners pnpm install 编辑 wrangler.jsonc → vars: 不需要重型类别?删除其 containers[] 条目和 RUNNER_HEAVY 绑定,并从 RUNNER_LABELS 中移除它。 npx wrangler secret put GITHUB_WEBHOOK_SECRET # 第 1 步中的 webhook 密钥 npx wrangler secret put GITHUB_APP_ID # 数字 App ID npx wrangler secret put GITHUB_APP_PRIVATE_KEY < your-app.private-key.pem npx wrangler secret put ADMIN_TOKEN # 可选:启用 GET /stats

对于本地 wrangler dev,将 .dev.vars.example 复制为 .dev.vars。

npx wrangler deploy 这会使用你本地的 Docker 构建两个镜像,将它们推送到 Cloudflare 的 registry 并部署 Worker。首次部署需要几分钟。将打印的 URL + /webhook 填入 App 的 webhook 设置,并检查 Recent Deliveries 显示绿色的 ping。

jobs: build: runs-on: [self-hosted, cf-runner] # 轻型:1 vCPU / 3 GiB test: runs-on: [self-hosted, cf-runner-heavy] # 重型:4 vCPU / 12 GiB

检查它: curl -H "Authorization: Bearer $ADMIN_TOKEN" https://<your-worker>/stats npx wrangler tail # 实时日志

运维 /stats 显示活动容器、待处理队列(以及每个作业在等待什么:at-capacity、cf-capacity、spacing 或 github)、今天的容器分钟数,以及三个应为空的列表: 无需你干预的自愈:每 5 分钟一次的 cron tick 重新检查 GitHub 的排队作业,回收退出报告丢失的容器(两次读取 + GitHub 的确认后才宣布其死亡),并删除此服务铸造的过期 runner 注册(从不删除少于 10 分钟的,从不删除其他前缀的)。

两个 Dockerfile 都固定了三样东西;一起升级它们并重新部署: 基础镜像 ubuntu:24.04@sha256:…:多架构索引摘要,最后获取于 2026-11-11(UTC)。从 Docker Hub registry API 以只读方式获取当前的(无需 Docker):

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →