一句话总结
GitHub Actions 用一个 YAML 文件,把第 01 篇讲的"流水线 / 阶段 / 质量门禁"变成真实可跑的自动化;
掌握它的核心就是理清事件触发 → Job → Step这条主线,再叠加缓存,矩阵,Secret,审批等加速与安全机制.
前置回顾
第 01 篇我们建立了词汇:流水线由阶段(Stage)串成,阶段间有质量门禁,设计原则是快速失败.本篇就是把这些抽象概念,落到一个具体工具上跑起来.读的时候不妨随时对照:这段 YAML 对应的是 01 里的哪个概念?
为什么从 GitHub Actions 入手
CI/CD 工具有很多:Jenkins,GitLab CI,CircleCI,GitHub Actions...选 GitHub Actions 作为入门,理由很实际:
- 零搭建:不用自己装服务器(对比 Jenkins 要维护一台机器),代码托管在 GitHub 上就能直接用
- 声明即配置:整条流水线就是仓库里一个 YAML 文件,跟代码一起版本化,一起 review
- 生态丰富:别人写好的步骤(Action)可以直接复用,像搭积木
更重要的是,它的核心模型(事件触发,Job,Step,缓存,矩阵)在所有 CI 工具里都是相通的.学会它,换到 GitLab CI 也只是语法差异.
核心模型:四层结构
GitHub Actions 的概念有四层,从大到小嵌套.先建立这个骨架,后面所有语法都往里填:
|
| 概念 | 是什么 | 对应 01 篇 |
|---|---|---|
| Event 事件 | 触发流水线的动作:push,提 PR,定时,手动点按钮 | "自动触发" |
| Workflow 工作流 | 一条完整流水线,一个 YAML 文件,放在 .github/workflows/ |
流水线(Pipeline) |
| Job 作业 | 一组 Step,跑在一台** 全新的**虚拟机(Runner)上;Job 间默认并行 | 阶段的载体 |
| Step 步骤 | Job 里的一条具体命令或一个复用的 Action | 阶段内的动作 |
最反直觉的一点:每个 Job 都是一台全新的空白机器
Job A 装好的依赖,编译出的文件,Job B 看不到--因为它们在不同的虚拟机上.这解释了后面两件事:
- 为什么需要缓存(每次都从零装依赖太慢)
- 为什么 Job 之间传文件要用专门的artifacts 上传/下载机制
记住"Job = 一次性的干净环境",很多设计就讲得通了.
第一个 Workflow:看懂每一行
下面是一个最小但完整的 CI 工作流,假设是个 Node.js 项目.逐行注释:
|
把这个文件放到仓库的 .github/workflows/ci.yml,push 一下,GitHub 就会自动跑起来.两种 Step 写法值得记住:
uses:-- 复用别人写好的 Action.actions/checkout@v4表示用官方的 checkout,版本锁 v4run:-- 直接执行 shell 命令,跟你在终端敲的一样
uses 像调用现成的库函数(别人封装好的能力),run 像自己写一行代码.一个 Job 就是这两种 Step 的混搭.
控制 Job 的顺序与依赖
默认情况下,一个 Workflow 里的多个 Job 并行 跑(为了快).但有时有先后依赖--比如"必须先测试通过,再构建镜像".用 needs 声明依赖:
|
这正好对应第 01 篇的"快速失败":test 和 lint 这种又快又能拦问题的检查并行先跑,build 这种耗时的放在它们后面,前面挂了就根本不浪费时间构建.
缓存:别每次都从零装依赖
前面说过每个 Job 是全新机器,意味着每次都要重新 npm ci / mvn dependency 下载一堆依赖,慢且浪费.缓存 把这些不常变的东西存起来,下次直接复用:
|
缓存的工作原理是按 key 命中:用依赖清单文件(如 package-lock.json)的哈希当 key,清单没变就命中缓存,跳过下载;清单一变(加了新依赖)key 就变,自动重新下载并更新缓存.
缓存 vs 制品:别搞混
两者都是"存东西",但用途相反:
缓存(cache)是跨工作流运行复用的,可重建的中间产物(依赖包),目的是提速;
制品(artifacts)是本次运行要保留或在 Job 间传递的产出(测试报告,构建好的二进制),目的是留存与交付.
一句话:缓存是"为了快,丢了也能重建",artifacts 是"这是成果,不能丢".
矩阵构建:一份配置,多种组合
假设你的库要同时支持 Node 18,20,22 三个版本,还要在 Linux 和 Windows 上都验证.难道写 6 个几乎一样的 Job?矩阵(Matrix) 让你一份配置自动展开成多个并行 Job:
|
${{ ... }} 是 GitHub Actions 的表达式语法,用来引用变量.matrix.os 在每个展开的 Job 里取到不同的值.矩阵是"用并行换覆盖面"的典型手段--6 个组合同时跑,总耗时约等于跑一个.
Secret 与 OIDC:不要把密钥写进代码
流水线常常需要敏感信息:推镜像要登录凭证,部署要云账号密钥.绝对不能 把这些明文写进 YAML(YAML 是进仓库的,等于公开).
用 Secret 注入密钥
在 GitHub 仓库的 Settings → Secrets 里配置好密钥后,在 YAML 里用 secrets 上下文引用:
|
更好的方式:OIDC 免密钥
长期存放的密钥本身就是风险:会泄露,要轮换,忘了删.更现代的做法是 OIDC(OpenID Connect)--GitHub Actions 运行时,临时向云厂商出示一张"我确实是某仓库某分支触发的"的短期身份令牌,云厂商验证后发放临时凭证,用完即失效.
优先用 OIDC 而非长期密钥
OIDC 的本质是从"持有长期密码"转向"按需换取临时通行证".没有长期密钥躺在配置里,自然就没有泄露和轮换的烦恼.各大云厂商(AWS / GCP / Azure)和 GitHub Actions 都原生支持.这与第 04 篇的"供应链安全"是同一种思路:减少静态机密的存在面.
环境与审批:守住生产那道门
回到第 01 篇的关键区分:持续交付和持续部署的差别,就在生产发布前那道人工审批.GitHub Actions 用 Environment(环境) 来实现这道门:
|
在 GitHub 后台给 production 环境配置保护规则(protection rules)后,这个 Job 跑到时会暂停并等待指定的人点"Approve",批准后才继续.这就把抽象的"人工审批关卡"变成了一个真实的按钮.
同一条流水线,加不加这道门决定了你是"交付"还是"部署":
|
可复用 Workflow:别在每个仓库复制粘贴
当你有十几个微服务,每个都需要几乎一样的"测试 + 构建 + 推镜像"流水线,复制粘贴会变成维护噩梦--改一处要改十几个文件.可复用 Workflow(Reusable Workflow) 把公共流程抽出来,各仓库调用它:
|
|
这本质上是把"流水线"也做了函数封装--定义一次,处处调用,改一处生效全部.和第 06 篇 Helm 模板化配置的动机如出一辙:消除重复,集中维护.
快速回顾
- 四层模型:Event 触发 → Workflow(一个 YAML)→ Job(全新虚拟机,默认并行)→ Step(
uses复用 /run命令) - needs 声明 Job 依赖,把快检查放前面实现"快速失败"
- 缓存按依赖清单哈希命中,加速;它与**制品(artifacts)**用途不同,前者求快,后者求留存
- 矩阵一份配置展开成多组合并行,用并行换覆盖面
- Secret 注入密钥不进代码;更优解是 OIDC 临时凭证,免长期密钥
- Environment + 审批规则实现生产发布前的人工门--决定了你是持续交付还是持续部署
- 可复用 Workflow 把公共流水线封装成"函数",消除多仓库重复
Action 后面的 @v4 一定要写吗?能用 @main 吗?
强烈建议锁版本(@v4 甚至锁到具体 commit SHA).用 @main 意味着别人一更新,你的流水线行为就可能悄悄变化甚至被注入恶意代码--这是真实存在的供应链攻击面(第 04 篇详谈).锁版本让构建可复现,这正是第 01 篇"确定,可复现"的要求.
Runner 是什么?要自己准备机器吗?
Runner 就是真正执行 Job 的机器.GitHub 提供托管 Runner(runs-on: ubuntu-latest),用完即销毁,无需你维护.如果有特殊需求(内网访问,特定硬件,更强算力),也可以挂自托管 Runner(自己的机器),但要自己负责安全和维护.入门用托管的即可.
动手练习
- 创建首个 Workflow:给一个自己的 GitHub 仓库新建
.github/workflows/ci.yml,实现 push 时自动跑测试,观察 Actions 页面的执行过程. - 验证质量门禁:故意写一个会失败的测试,push,确认流水线变红并阻止--亲眼看到"质量门禁"生效.
- 体验矩阵构建:加一个矩阵,让测试在两个语言版本上并行跑,观察是否真的展开成多个并行 Job.
- 验证快速失败:把
test和build用needs串起来,故意让 test 失败,确认 build 被跳过而非照常执行. - 体验审批门(进阶):配一个
environment: production的部署 Job 并加审批规则,体验"流水线停下来等你点批准"的感觉.