GitHub Actions 对开源项目免费,但对私有仓库按分钟计费。一个百人规模的工程团队,如果 CI/CD 设计不合理,每月的 Actions 账单可能轻松突破数千美元。本文从 GitHub Actions 的计费模型出发,系统讲解分钟数监控、并发优化、缓存复用、自托管 Runner 替代策略,以及团队级预算分配方法,帮助你在不牺牲构建质量的前提下显著降低成本。
一、GitHub Actions 计费模型详解
1.1 分钟数计费规则
| 操作系统 | Free 计划 | Pro/Team 计划 | Enterprise 计划 |
|---|---|---|---|
| Linux (ubuntu) | 2,000 分钟/月 | 3,000 分钟/月 | 50,000 分钟/月 |
| Windows | × 2 倍(1 分钟 = 2 Linux 分钟) | × 2 倍 | × 2 倍 |
| macOS | × 10 倍(1 分钟 = 10 Linux 分钟) | × 10 倍 | × 10 倍 |
| ARM64 (Linux) | 同 Linux | 同 Linux | 同 Linux |
关键洞察:
- macOS Runner 的成本是 Linux 的 10 倍。仅在必要时使用(如 iOS 构建)。
- Windows Runner 的成本是 Linux 的 2 倍。Win32 原生测试必须用,但可以考虑交叉编译替代。
- 超出免费额度后的单价:$0.008/分钟(Linux),$0.016/分钟(Windows),$0.08/分钟(macOS)。
1.2 并发配额与排队成本
| 计划 | 并发 Job 数 | 排队风险 |
|---|---|---|
| Free | 20 | 高(团队规模 > 5 人时) |
| Pro | 40 | 中 |
| Team | 60 | 低 |
| Enterprise | 500 | 极低 |
当并发 job 超过配额时,后续 job 进入排队状态。排队的隐藏成本:
- 开发者等待反馈的时间(效率损失)
- PR 合并延迟(流程阻塞)
- 开发者因等待而频繁手动重试(实际分钟数增加)
二、成本归因:定位账单大头
2.1 使用 GitHub API 获取账单数据
# 获取本月 Actions 使用量(Organization 级别)
curl -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/orgs/$ORG/settings/billing/actions"
# 返回示例:
# {
# "total_minutes_used": 45231,
# "total_paid_minutes_used": 15231,
# "included_minutes": 30000,
# "minutes_used_breakdown": {
# "UBUNTU": 38000,
# "MACOS": 1200,
# "WINDOWS": 6031
# }
# }
2.2 构建每周成本报告 workflow
name: Weekly Cost Report
on:
schedule:
- cron: '0 9 * * 1' # 每周一上午 9 点
jobs:
report:
runs-on: ubuntu-latest
steps:
- name: Get Actions usage
id: usage
run: |
RESPONSE=$(curl -s \
-H "Authorization: token ${{ secrets.GITHUB_ADMIN_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/orgs/${{ github.repository_owner }}/settings/billing/actions")
TOTAL=$(echo $RESPONSE | jq '.total_minutes_used')
PAID=$(echo $RESPONSE | jq '.total_paid_minutes_used')
UBUNTU=$(echo $RESPONSE | jq '.minutes_used_breakdown.UBUNTU')
MACOS=$(echo $RESPONSE | jq '.minutes_used_breakdown.MACOS')
WINDOWS=$(echo $RESPONSE | jq '.minutes_used_breakdown.WINDOWS')
# 估算成本(Team 计划 $0.008/分钟 Linux)
COST=$(echo "$PAID * 0.008" | bc)
echo "total=$TOTAL" >> $GITHUB_OUTPUT
echo "paid=$PAID" >> $GITHUB_OUTPUT
echo "cost=$COST" >> $GITHUB_OUTPUT
echo "ubuntu=$UBUNTU" >> $GITHUB_OUTPUT
echo "macos=$MACOS" >> $GITHUB_OUTPUT
echo "windows=$WINDOWS" >> $GITHUB_OUTPUT
- name: Post to Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "📊 GitHub Actions 周成本报告\n\n总计使用: ${{ steps.usage.outputs.total }} 分钟\n超出额度: ${{ steps.usage.outputs.paid }} 分钟\n预估成本: $${{ steps.usage.outputs.cost }}\n\n按平台:\n• Linux: ${{ steps.usage.outputs.ubuntu }} 分钟\n• macOS: ${{ steps.usage.outputs.macos }} 分钟 (${{ steps.usage.outputs.macos }}0 Linux 等效分钟)\n• Windows: ${{ steps.usage.outputs.windows }} 分钟 (${{ steps.usage.outputs.windows }} * 2 Linux 等效分钟)"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
三、核心优化策略
3.1 策略一:用 Linux 替代 macOS/Windows
场景:Go 项目需要在 Linux、macOS、Windows 上编译。
优化前:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
优化后(Go 支持交叉编译):
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
# 交叉编译所有平台
- run: |
GOOS=linux GOARCH=amd64 go build -o dist/app-linux-amd64
GOOS=darwin GOARCH=amd64 go build -o dist/app-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o dist/app-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o dist/app-windows-amd64.exe
- uses: actions/upload-artifact@v4
with:
name: binaries
path: dist/
# 仅在真正需要时进行平台原生测试
test-cross-platform:
needs: build
strategy:
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/download-artifact@v4
with: { name: binaries }
- run: ./dist/app-* # 运行快速冒烟测试
成本对比:
| 场景 | 原方案 | 优化后 | 节省 |
|---|---|---|---|
| 10 分钟构建 | Linux 10 + macOS 100 + Windows 20 = 130 分钟 | Linux 10 + 各平台 2 分钟测试 30 = 40 分钟 | 69% |
3.2 策略二:缓存命中率最大化
缓存的直接效果是减少依赖安装时间,间接效果是降低分钟数消耗。
优化前:
- run: npm ci # 每次 2 分钟
优化后:
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- run: npm ci # 缓存命中后 10 秒
成本影响:一个每天触发 50 次的 workflow,缓存命中后每次节省 2 分钟,每月节省 3,000 分钟,约合 $24(Team 计划)。
3.3 策略三:并发控制与 max-parallel
对于不需要全部并行的大型矩阵,限制并发数:
strategy:
max-parallel: 3 # 限制同时运行 3 个 job
matrix:
node: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]
# 3×3=9 个 job,max-parallel=3
# 如果每个 job 10 分钟:
# 完全并行总时间 = 10 分钟,成本 = 9 job × 10 min = 90 分钟
# 限制后总时间 = 30 分钟,成本 = 90 分钟(相同)
# 但如果是自托管或并发配额紧张,队列时间减少
注意:max-parallel 不改变总分钟数,但减少并发配额压力和排队等待。
3.4 策略四:条件执行与路径过滤
name: Backend CI
on:
push:
paths:
- 'backend/**' # 只有 backend 目录变更才触发
- '.github/workflows/backend-ci.yml'
pull_request:
paths:
- 'backend/**'
在 monorepo 中,为每个子系统配置独立 workflow 和 paths 过滤,可以避免无关变更触发全量构建。
3.5 策略五:定时任务错峰执行
on:
schedule:
# ❌ 不要选整点(竞争高峰)
# - cron: '0 0 * * *' # 午夜整点,大量 workflow 竞争
# ✅ 错峰执行
- cron: '23 1 * * *' # 凌晨 1:23,竞争较少
在非高峰时段执行,Runner 分配更快,减少排队等待时间。
四、自托管 Runner 的成本对比
4.1 何时自托管更便宜
自托管 Runner 的成本计算公式:
自托管成本 = 服务器租用成本 + 运维人力成本 + 网络流量成本
GitHub 成本 = 总分钟数 × 单价
盈亏平衡点分析:
假设使用 AWS EC2 m5.large(2 vCPU, 8 GB)作为 Runner,按需价格 $0.096/小时:
| 月使用量 | GitHub Team 成本 | AWS m5.large 成本 | 更优选择 |
|---|---|---|---|
| 5,000 分钟 | $40 | $77 (800 小时) | GitHub |
| 20,000 分钟 | $160 | $77 | 自托管 |
| 50,000 分钟 | $400 | $154 (2 台) | 自托管 |
| 100,000 分钟 | $800 | $231 (3 台) | 自托管 |
结论:当月使用量超过约 15,000 Linux 分钟后,自托管 Runner 开始具有成本优势。
4.2 自托管的隐藏成本
| 成本项 | 说明 | 估算 |
|---|---|---|
| 服务器租赁 | EC2/GCE/裸金属 | 见上表 |
| 运维人力 | 维护 Runner、更新安全补丁、排查问题 | 0.2-0.5 FTE |
| 网络流量 | 拉取 Docker 镜像、上传 artifact | $0.09/GB |
| 磁盘存储 | 缓存、日志保留 | $0.10/GB/月 |
| 机会成本 | 团队专注 CI 运维而非业务 | 难以量化 |
五、大型组织的预算分配
5.1 按仓库/团队分配预算
GitHub 目前不提供原生的「按仓库配额」功能,但可以通过以下方式实现:
方案 A:使用多个 Organization
每个团队一个 Organization,独立计算免费额度:
org-team-frontend → 3,000 分钟/月
org-team-backend → 3,000 分钟/月
org-team-mobile → 3,000 分钟/月
缺点:跨 org 的代码共享和权限管理复杂。
方案 B:监控 + 月度报告
使用 GitHub API 统计每个仓库的使用量,超限时通知:
# 获取某个仓库的 workflow 运行记录
curl -H "Authorization: token $TOKEN" \
"https://api.github.com/repos/$ORG/$REPO/actions/runs?per_page=100" | \
jq '.workflow_runs[] | {name, run_number, run_started_at, updated_at}'
5.2 成本优化 KPI
| 指标 | 目标值 | 监控方式 |
|---|---|---|
| 平均每 PR CI 时间 | < 5 分钟 | GitHub API 统计 |
| 缓存命中率 | > 85% | actions/cache 日志 |
| macOS 分钟占比 | < 10% | 账单 API |
| 排队时间占比 | < 15% | Runner 队列监控 |
| 失败重试率 | < 5% | Workflow 运行统计 |
六、常见问题解答(FAQ)
Q1: GitHub Actions 的免费额度是按月计算还是按仓库计算?
按账户级别(个人/组织)计算,不是按仓库。所有仓库的 usage 共享同一个额度池。
Q2: 如果取消 workflow run,还会计费吗?
如果 job 已经开始执行,已消耗的分钟数会计费。取消排队中的 job 不计费。
Q3: Self-hosted Runner 的 job 是否消耗 GitHub 的免费额度?
不消耗。Self-hosted Runner 的 job 完全免费(从 GitHub 计费角度),但你需承担服务器成本。
Q4: 如何设置账单预警?
GitHub 目前不原生支持额度预警。可以通过每周 API 拉取 + Slack 通知实现:
# 在 Weekly Cost Report workflow 中
- name: Alert on high usage
if: ${{ steps.usage.outputs.paid > 24000 }} # 超过 $200/周
run: |
curl -X POST $SLACK_WEBHOOK \
-d '{"text": "⚠️ GitHub Actions 账单预警:本周已超出 $200"}'
总结
GitHub Actions 的成本优化不是一次性任务,而是需要持续监控和调整的流程。核心策略可以总结为「三板斧」:
| 优先级 | 策略 | 预期节省 | 实施难度 |
|---|---|---|---|
| P0 | Linux 交叉编译替代 macOS/Windows | 50-80% | 低 |
| P0 | 缓存命中率优化 | 20-40% | 低 |
| P1 | 路径过滤 + 条件执行 | 15-30% | 中 |
| P1 | 错峰调度 | 减少排队 | 低 |
| P2 | 自托管 Runner 替代 | 30-60%(大规模) | 高 |
| P2 | 并发配额管理 | 减少排队 | 中 |
最后,成本优化的底线是不牺牲构建质量。一个因过度优化而导致测试遗漏、最终引发生产故障的「省钱策略」,其代价将远超节约的 CI 分钟数。
延伸阅读:
- GitHub Actions 自托管 Runner — 成本对比与大规模部署方案
- GitHub Actions 缓存优化 — 缓存键设计与命中率调优
- GitHub Actions 矩阵构建策略 — max-parallel 与并发控制
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。