触发器与事件:让流水线在正确时机“苏醒”

写 CI/CD 流水线,第一个要回答的问题就是:“什么时候跑?” 触发器就是答案。我个人把触发器比作流水线的“闹钟” —— 你设定好条件,它到点就叫醒作业。但闹钟响错了会烦人,响晚了会误事。GitHub Actions 提供了几种核心触发器,今天我把它们的脾气摸透了告诉你。

一句话总结: push 和 pull_request 是日常主力,schedule 做定时任务,workflow_dispatch 留一手手动触发。事件过滤是减少无效运行的“筛子”。

1. push 触发器:最直接的“代码提交即运行”

push 事件,说白了就是你往仓库里推了代码。这是最常用的触发器,没有之一。我在早期项目中全部依赖 push 触发,结果每次推个空提交都跑一遍流水线,浪费资源。后来学会了加过滤条件。

on:
  push:
    branches:
      - main
      - 'release/**'
    paths:
      - 'src/**'
      - '!*.md'
  • branches:只监听特定分支,支持通配符 **。我建议至少把 main 和 develop 分开,避免开发分支的临时提交触发生产部署。
  • paths:路径过滤。比如只关心 src/ 下的变更,忽略文档或图片。曾经我有一个项目,团队频繁改 README,每次触发 5 分钟的测试,后来用 !*.md 排除掉,省了一半运行时间。
  • tags:标签推送也能触发,适合版本打 tag 后自动构建。
小技巧: path 过滤支持 ! 取反,但要注意顺序——先包含后排除。我曾经踩过坑,把 !src/** 写在前面,结果所有路径都被忽略了。

2. pull_request 触发器:PR 生命周期中的“守护者”

PR 触发比 push 复杂一点。它会在 PR 打开、同步(新提交)、重新打开、甚至标记为 ready for review 时触发。我个人习惯在 PR 阶段运行完整的回归测试,而不是 push 阶段——因为 push 阶段可能代码还没合完,跑多了浪费。

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches:
      - main
  • types:默认只有 opened、synchronize、reopened,但你可以加 labeled、unlabeled 等。我用过 labeled 实现按标签触发特定测试,比如给 PR 打上 performance 标签才跑压力测试。
  • branches:同样支持分支过滤。注意这里的目标分支(base branch),不是源分支。
注意: 对于 fork 仓库的 PR,GitHub Actions 默认有安全限制——不会暴露 secrets。如果你的流水线需要访问敏感信息,记得用 pull_request_target,但务必小心权限泄露。我曾经因为没区分导致外部 PR 无法发布,花了一晚上排查。

3. schedule 触发器:定时任务的“时钟守护”

有些场景不需要代码变更也跑流水线:比如每天凌晨执行一次全量测试、每周日清理旧构建产物。这时候用 schedule 加 cron 表达式。

on:
  schedule:
    - cron: '0 2 * * *'   # 每天 UTC 2:00
    - cron: '30 8 * * 1'  # 每周一 8:30

Cron 语法和 Linux 一致,但注意时区是 UTC。我曾因为没换算时区,早晨 8 点起来发现流水线跑在凌晨 2 点,数据对不上。后来统一用 UTC 并在备注里写清楚。

经验: schedule 中的 cron 最小粒度是 5 分钟。如果你需要更细的间隔,考虑 workflow_dispatch 配合外部定时器(比如 Jenkins 触发)。另外,schedule 触发不会自动带版本信息,需要你自己从仓库获取。

4. workflow_dispatch 触发器:手动干预的“最后手段”

这个触发器的价值常常被低估。它允许你手动触发工作流,并且可以传入参数。比如你想在非 PR 阶段跑一次特殊测试、或者做一次临时部署。

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy target'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      run_tests:
        description: 'Run full test suite?'
        required: true
        default: 'true'
        type: boolean

你可以在 GitHub 仓库的 Actions 页面点击“Run workflow”,填入参数。我在做故障恢复时经常用到——线上出问题,手动触发一个带 debug=true 的流水线,跑一堆诊断脚本。

5. 事件过滤与条件组合:避免“无用工”

仅仅有触发器还不够。每个事件还带有一堆上下文数据,你可以用 if 条件进一步筛选。比如“只在 main 分支且路径包含 src 时才部署”,或者“PR 来自特定用户才执行”。

jobs:
  test:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main' || 
        (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci'))
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running important tests"
核心原则: 触发器决定“何时唤醒”,条件决定“是否干活”。把粗筛条件写在 on 层,细筛条件写在作业或步骤的 if 层。这样配置清晰,也方便维护。

记得有一次,我配置了 pull_request 触发,但忘记加 types 过滤,结果每次 PR 被评论时也触发了一遍——因为 labeled 类型默认没包含,但 assigned 类型把我坑了。后来我改成显式列出需要的类型,再没出过问题。

触发器对比一览

触发器类型 触发条件 我最常搭配的过滤 适用场景
push 代码推送 branches, paths, tags 持续构建、部署
pull_request PR 操作 types, branches, labels 代码审查前回归测试
schedule Cron 表达式 无(可直接在 step 加 if 判断日期) 定时全量测试、数据备份
workflow_dispatch 手动点击/API inputs 参数校验 故障恢复、按需部署
GitHub Actions 触发器体系 GitHub Actions 触发器 push 代码推送触发 pull_request PR 生命周期事件 schedule Cron 定时触发 workflow_dispatch 手动或 API 触发 事件过滤条件 branches | paths | types | tags | inputs 四种触发器配合事件过滤,覆盖 CI/CD 所有触发场景 → 触发条件通过 → 执行 CI/CD 作业

总结一下触发器选择的经验:如果你的团队还在用 on: push 直接跑所有分支,我建议你立刻加上分支过滤。我之前接手一个项目,CI 费用一半浪费在 dependabot 的自动 PR 上——因为没过滤路径,每次依赖更新都触发全量测试。加了个 paths-ignore: '**/package-lock.json',费用直接降了 30%。

最后说个容易忽略的点:多个触发器可以写在一起,用逗号分隔。比如同时监听 push 和 pull_request,但要注意可能触发两次同一次改动。我通常用 types 和 if 条件把 push 和 PR 的逻辑区分开,比如 push 跑构建,PR 跑测试,互不干扰。

检查清单:
  • ✔ push 触发器是否加了分支和路径过滤?
  • ✔ pull_request 是否显式指定了 types,避免无关操作触发?
  • ✔ schedule 的 cron 表达式是否写对了时区?
  • ✔ workflow_dispatch 的 inputs 是否都有默认值?