重复性的任务最消耗人,而它恰恰最适合交给机器。无论是每天抓取数据生成报表、定时发布内容,还是自动同步两个系统的数据,本质都是同一件事:在固定时间,按固定步骤,做一件不需要人做判断的事。
GitHub Actions 是目前性价比最高的自动化方案之一:与代码仓库天然集成、配置即代码、有完整的运行日志与重试机制。这篇文章从零讲清楚怎么把它用起来。
先理解四个核心概念
配置文件放在仓库的 .github/workflows/ 目录下,一个 YAML 文件就是一个工作流。理解这四个概念就能读懂九成的配置:
| 概念 | 含义 | 常见写法 |
|---|---|---|
| workflow | 一个完整的自动化流程 | 一个 .yml 文件 |
| trigger | 什么时候触发 | schedule、push、workflow_dispatch |
| job | 一组在同一个运行器上执行的步骤 | jobs.build |
| step | 具体执行的命令或复用他人封装的动作 | run 或 uses |
一个最小可用的配置长这样:
name: daily-job
on:
schedule:
- cron: '0 1 * * *' # 每天 UTC 01:00,即北京时间 09:00
workflow_dispatch: # 允许手动在网页上点一下就运行
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "开始执行任务"
几个容易踩的坑先说明:
- cron 用的是 UTC 时间,写北京时间需要减 8 小时。
- cron 不保证准时,只是把任务排进队列,高峰期可能延迟。
workflow_dispatch一定要加,否则调试时只能靠改代码触发。
配置密钥:不要把密码写进代码
任何需要调用外部服务的自动化,都会涉及 API Key。正确做法是存在仓库的 Settings → Secrets and variables → Actions 里,在配置中用 ${{ secrets.XXX }} 引用。
- name: 运行脚本
env:
API_KEY: ${{ secrets.API_KEY }}
run: node scripts/run.mjs
要特别注意两点:
- 密钥值在日志中会被自动脱敏,但如果你自己把它打印出来(例如
echo $API_KEY),仍可能以变形方式泄露,永远不要主动打印密钥。 - 公共仓库的 fork 流程拿不到密钥,这是平台的安全设计,不是配置错误。
让任务结果回写到仓库
很多自动化任务的价值在于产出内容。标准做法是:脚本把结果写成文件,工作流再用官方的提交动作推回去。
permissions:
contents: write # 关键:默认权限是只读,不显式声明会推送失败
steps:
- uses: actions/checkout@v4
- run: node scripts/generate.mjs
- name: 提交变更
run: |
git config user.name "bot"
git config user.email "[email protected]"
git add -A
git diff --cached --quiet || git commit -m "chore: 自动更新内容 $(date +'%Y-%m-%d')"
git push
这里有三处工程细节值得注意:
permissions: contents: write是推送成功的前提。- 提交前用
git diff --cached --quiet判断是否有变更,避免无改动时空提交导致流程报错。 - 提交信息里带上日期,方便日后回溯是哪一次自动运行出了问题。
失败告警与重试
无人值守的系统,最重要的不是”能跑”,而是”跑坏了你知道”。
重试:对网络类任务加一层自动重试,能消掉绝大多数偶发失败。
- uses: nick-fields/retry@v3
with:
max_attempts: 3
retry_wait_seconds: 30
command: node scripts/generate.mjs
告警:失败时推送到即时通讯工具。下面用一个 curl 直接调用 Webhook 的例子,配置最轻:
- name: 失败告警
if: failure()
run: |
curl -sS -X POST -H 'Content-Type: application/json' \
-d '{"text":"工作流失败:'$GITHUB_WORKFLOW' 仓库:'$GITHUB_REPOSITORY'"}' \
${{ secrets.ALERT_WEBHOOK }}
if: failure() 保证这一步只在前面失败时执行。告警内容里带上工作流名和仓库名,能让你在几十个仓库里一眼定位问题。
控制成本与运行时间
私有仓库按运行时长计费,几个习惯能显著降低消耗:
- 优先用
ubuntu-latest,其他系统的计费系数高数倍。 - 加超时限制,避免脚本死循环烧掉大量时长:
timeout-minutes: 20。 - 并发控制,防止多个定时任务叠加执行:
concurrency: { group: daily, cancel-in-progress: false }。 - 缓存依赖,Node 项目用
actions/setup-node的cache: npm,通常能省掉一半以上的安装时间。
一个可直接复用的完整模板
把上面的要点组合起来,就是一套能长期稳定运行的流水线骨架:
name: daily-automation
on:
schedule:
- cron: '0 1 * * *'
workflow_dispatch:
permissions:
contents: write
concurrency:
group: daily-automation
cancel-in-progress: false
jobs:
run:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: 执行任务
env:
API_KEY: ${{ secrets.API_KEY }}
run: node scripts/run.mjs
- name: 提交结果
run: |
git config user.name "bot"
git config user.email "[email protected]"
git add -A
git diff --cached --quiet || git commit -m "chore: 自动更新 $(date +'%Y-%m-%d')"
git push
- name: 失败告警
if: failure()
run: curl -sS -X POST -H 'Content-Type: application/json' -d '{"text":"自动化任务失败"}' ${{ secrets.ALERT_WEBHOOK }}
上线前的检查清单
自动化一旦上线就会长期运行,交付前建议逐项确认:
- 手动触发过一次,完整跑通(用
workflow_dispatch) - 密钥全部走 Secrets,代码中无硬编码
- 设置了
timeout-minutes与并发控制 - 有变更时才提交,不会产生空提交
- 失败时有告警,并且告警真的发到了你会看的地方
- 幂等:同一天重复运行不会产生重复内容或脏数据
最后一条”幂等”最容易被忽略,也最容易在长期运行中出问题。 任务运行前先检查目标产物是否已存在,是成本最低的保护措施。
常见问题
定时任务为什么没有按时执行?
首先确认 cron 用的是 UTC 时间;其次平台在负载高峰期会延迟入队;另外仓库长期无活动可能导致定时触发器被停用。建议先用 workflow_dispatch 验证流程本身没问题,再排查时间问题。
如何在本地调试工作流?
推荐用社区工具(如 act)在本地模拟运行,或者把核心逻辑写进独立脚本,在本地直接用 node scripts/run.mjs 调试,让工作流只负责调用脚本。后者是更稳妥的工程实践。
自动化脚本推送代码会不会造成冲突?
如果只有自动化一个写入方,通常不会。若同时有人工提交,建议在运行开始时先 git pull --rebase,并在提交失败时重试一次。
运行日志里的敏感信息怎么保护?
永远不要把密钥拼进命令行参数或打印出来。必须传递时走 env 环境变量,平台会自动对已注册的密钥值做日志脱敏。