触发器与事件:让流水线在正确时机“苏醒”
写 CI/CD 流水线,第一个要回答的问题就是:“什么时候跑?” 触发器就是答案。我个人把触发器比作流水线的“闹钟” —— 你设定好条件,它到点就叫醒作业。但闹钟响错了会烦人,响晚了会误事。GitHub Actions 提供了几种核心触发器,今天我把它们的脾气摸透了告诉你。
1. push 触发器:最直接的“代码提交即运行”
push 事件,说白了就是你往仓库里推了代码。这是最常用的触发器,没有之一。我在早期项目中全部依赖 push 触发,结果每次推个空提交都跑一遍流水线,浪费资源。后来学会了加过滤条件。
on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**'
- '!*.md'
- branches:只监听特定分支,支持通配符
**。我建议至少把main和develop分开,避免开发分支的临时提交触发生产部署。 - paths:路径过滤。比如只关心
src/下的变更,忽略文档或图片。曾经我有一个项目,团队频繁改 README,每次触发 5 分钟的测试,后来用!*.md排除掉,省了一半运行时间。 - tags:标签推送也能触发,适合版本打 tag 后自动构建。
! 取反,但要注意顺序——先包含后排除。我曾经踩过坑,把 !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),不是源分支。
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 并在备注里写清楚。
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 参数校验 | 故障恢复、按需部署 |
总结一下触发器选择的经验:如果你的团队还在用 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 是否都有默认值?