用 GitHub Actions 搭建无人值守的自动化流水线:从零到稳定运行

想让重复任务每天自动跑?本文手把手讲解 GitHub Actions 的核心概念、定时触发配置、密钥管理、失败告警与成本控制,并给出一个可直接复制的完整工作流示例。

重复性的任务最消耗人,而它恰恰最适合交给机器。无论是每天抓取数据生成报表、定时发布内容,还是自动同步两个系统的数据,本质都是同一件事:在固定时间,按固定步骤,做一件不需要人做判断的事。

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

要特别注意两点:

  1. 密钥值在日志中会被自动脱敏,但如果你自己把它打印出来(例如 echo $API_KEY),仍可能以变形方式泄露,永远不要主动打印密钥。
  2. 公共仓库的 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 "bot@example.com"
      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 "bot@example.com"
          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 环境变量,平台会自动对已注册的密钥值做日志脱敏。

#自动化#GitHub Actions#CI/CD#效率提升

常见问题

GitHub Actions 免费额度够用吗?
公开仓库通常免费使用,私有仓库按月提供一定的免费分钟数,超出后按运行时长计费。Linux runner 的计费系数最低,除非必须,否则优先使用 Linux 运行器以控制成本。
定时任务(schedule)一定会准时执行吗?
不一定。cron 触发只是把任务加入队列,在平台负载较高时可能延迟数分钟甚至更久。对时间精度要求高的任务,建议提前几分钟触发或在流程内部自己判断时间窗口。
自动化任务失败了怎么知道?
默认情况下工作流失败会向仓库管理员发送邮件通知。更可靠的做法是再加一步失败通知,把告警推送到即时通讯工具,避免邮件被淹没。