前言
JD 里常见"熟悉 CI/CD"、"了解 GitHub Actions"。CI/CD 说的是一套工程做法:代码提交后,用脚本自动做检查、测试、部署,减少漏步骤等问题。
GitHub Actions 是 GitHub 提供的实现工具, 在仓库里放一份 YAML 配置,由 GitHub 在云端执行环境上按配置跑命令。
一、CI/CD 在解决什么问题
1.1 一次正常发布要经过哪些环节
以前端项目为例,从改代码到用户能打开新版本,通常要经过:
- 本地跑测试、lint,确认改动没有明显问题
git push,把 commit 推到 GitHub 远程仓库- 与主分支合并(多人协作时通过 Pull Request)
- 构建,例如
npm run build生成dist/ - 把产物部署到服务器或 Vercel、GitHub Pages 等平台
这些步骤每次发布都要做一遍 。CI/CD 要做的就是:把能写成脚本的步骤固定下来,在指定时机(例如每次 push、每次 PR)自动执行。
1.2 CI:Continuous Integration(持续集成)
先拆两个词。
集成(Integration)
在软件工程里指:把你的改动和仓库里已有代码合并成一个整体,并确认这个整体能构建、能跑测试。多人协作时,你改的是模块 A,同事改的是模块 B,集成就是验证 A 和 B 合在一起仍然正常。
持续(Continuous)
指每次 向主开发分支合入改动(push 或 merge 到 main),都跑一遍集成验证,而不是积累很多天、很多 PR 再一次合并。单次合并的变更越小,冲突越少,出问题也更容易定位是哪一次 commit 引入的。CI 选的是高频集成,用自动化换更低的合并风险。
CI 在自动化环境里通常做什么
下面这些是常见步骤,都在云端执行机(Runner)上跑:
- 安装依赖(
npm ci) - 静态检查(
npm run lint、类型检查) - 测试(
npm test) - 构建(
npm run build,确认能编过)
1.3 CD:Continuous Delivery / Deployment(持续交付 / 持续部署)
CI 通过之后,代码在逻辑上已经可发布。CD 负责把构建产物放到用户能访问的环境(测试服、预发、生产)。
两个英文词经常混用,区别如下:
- Continuous Delivery(持续交付): 构建和发布准备自动化,但上生产可能还要人工点确认。
- Continuous Deployment(持续部署): CI 通过后自动部署,没有人工审批环节。
很多团队对生产环境保留人工卡点,测试环境则全自动。下文里的 CD 泛指CI 之后的部署环节,不严格区分这两个词。
CD 要回答的问题:已经验证过的版本,如何稳定、重复地到达目标环境。
1.4 和工具的关系
CI/CD 是目标 ;GitHub Actions、GitLab CI、Jenkins 等是实现手段。阶段划分在各工具里大同小异:装依赖 → 检查 → 测试 → 构建 → 部署,差别主要在配置文件写法。
仓库在 GitHub 上时,用 GitHub Actions 最省事:配置放在仓库里,和代码一起版本管理。本文后面都用 Actions 举例。
二、代码提交后,谁在什么地方执行这些命令
理解这一节,后面所有配置才看得懂。很多人第一次学 CI/CD 会卡在这里:「拉代码」到底是谁在拉、拉的是什么。
2.1 Runner
Runner(执行机):GitHub Actions 为每次 Job 分配的执行环境;托管 Runner 通常是临时虚拟机;CI 里的 npm test 等命令在这里执行
GitHub 收到 push,读仓库里的 workflow 配置,在 Runner 上把命令跑一遍。
可以把它理解成:用 GitHub 托管 Runner 时,相当于借你一台用完就还的 Linux 环境,专门用来验证这次提交。
2.2 workflow 里的 "拉代码" 到底是什么
配置文件里几乎总有一步:
yaml
- uses: actions/checkout@v6
这不是你在本地 git pull,而是 Runner 从 GitHub 远程仓库克隆代码。
一次 push 触发 CI 时,过程大致是:
- GitHub 给这个 Job 分配一台空的 Runner
actions/checkout从 GitHub 下载本次触发所对应的那一个 commit 的文件,写到 Runner 的磁盘上- 后面的
npm ci、npm test都在 Runner 上、针对这份代码执行
2.3 Runner 用完就销毁
Job 结束后 Runner 被回收,上面的代码、node_modules、构建产物都会清掉。因此:
- 不能指望上一次 CI 装好的依赖留给下一次(除非用缓存,见第六节)
- 不能 把 Runner 当生产服务器;用户访问的网站要部署到 Vercel、自己的云主机等长期在线的环境
三、GitHub Actions 怎么知道要跑、跑什么
3.1 配置文件放哪
GitHub 只认仓库里的固定路径:
markdown
项目根目录/
└── .github/
└── workflows/
└── ci.yml
文件必须push 到 GitHub才生效。
3.2 从 push 到跑命令的完整链路
perl
你在本机 git push
→ GitHub 更新远程分支,并产生「push 事件」
→ Actions 读取该 commit 里的 .github/workflows/*.yml
→ 看 on: 是否匹配(例如:是否是 push 到 main)
→ 匹配则创建一次 Workflow Run,为每个 Job 分配 Runner
→ 每个 Job 内按 steps 顺序:checkout → 装 Node → npm ci → npm test ...
→ 成功或失败写回 GitHub(Actions 页、PR 上的检查项)
→ Runner 释放
有些外部 CI(例如自建 Jenkins)需要你在 GitHub 的 Webhooks 里填一个 URL:push 时 GitHub 再通知那台外部服务器去干活。GitHub Actions 是 GitHub 内置 功能:你 push 时,GitHub 在接收 push、更新仓库的过程中就会触发 Actions 去读 workflow。只要 push 成功,且仓库里已有 .github/workflows/ 下的 yml,就会按 on: 规则跑起来。
3.3 配置文件的三层结构
一个 YAML 文件是一个 Workflow 。里面有一个或多个 Job ;每个 Job 在一台 Runner 上跑,包含多个 Step。
bash
Workflow(整个 ci.yml)
└── Job(例如 test、deploy)
└── Step(例如 checkout、npm test)
| 关键字 | 管什么 |
|---|---|
on: |
什么情况下触发(push、PR、定时等) |
jobs: |
有哪些任务块 |
steps: |
每个任务里具体执行什么 |
多个 Job 默认并行 (各占一台 Runner)。如果要求 B 在 A 成功之后 才跑,写 needs: A。A 失败时,依赖它的 B 默认会被跳过(skipped)。
四、uses 和 run:Step 里两种写法
run: --- 在 Runner 的 shell 里执行你写的命令,和在本机终端里敲一样:
yaml
- run: npm ci
- run: npm test
uses: --- 调用写好的 Action,省得每个项目自己写 clone、装 Node 的脚本:
yaml
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
项目自己的测试、构建用 run;拉代码、装运行时这类通用操作用 uses。
五、示例:Vue 项目的 CI 配置
假设 Vue 3 + Vite 项目,有 package.json 和 package-lock.json,主分支叫 main。
5.1 一份可以直接用的配置
yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
5.2 配置解释
on: push / pull_request + branches: [main]
向 main push,或向 main 开/更新 PR 时,都会触发。
runs-on: ubuntu-latest
使用 GitHub 托管的 Ubuntu Runner(临时虚拟机)。
actions/checkout@v6
Runner 从 GitHub 检出本次触发对应的代码。@v6 是官方 Action 的主版本标签,可按官方文档升级。
actions/setup-node@v6 + node-version: '20'
在 Runner 上安装 Node.js 20。本地和 CI 的 Node 主版本不一致,是常见的「本地过、CI 挂」原因之一,所以建议在配置里写明版本。
npm ci
按 package-lock.json(或 npm-shrinkwrap.json)安装依赖。CI 里通常用 npm ci 而不是 npm install,结果更可复现。没有 lock 文件时 npm ci 会失败,需先生成 lock,或改用 npm install。
npm run lint / npm test / npm run build
对应 package.json 里 scripts 下的命令;项目里必须事先定义好这些脚本,否则 CI 会失败。
5.3 跑起来之后去哪看
push 后打开仓库的 Actions 标签页,每次运行一条记录,点进去能看到每个 Step 的完整终端输出。commit 和 PR 页面也会显示检查项是否通过。
六、常见增强:缓存、拆分 Job、部署条件
基础 CI 能跑通之后,通常会做几件事。
6.1 依赖缓存
每次 Job 用新的 Runner,默认要重新 npm ci。若 package-lock.json 没变,重复下载浪费时间。可以:
yaml
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
GitHub 会把 npm 缓存存到 Actions Cache 。lock 文件不变时,后续 Run 的 Install 步骤会快很多。日志里出现 Cache hit 表示命中。
6.2 拆分 Job,用 needs 控制顺序
例如先跑 lint,通过后再跑 test:
yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
- run: npm ci && npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
- run: npm ci && npm test
needs: lint 表示 lint 成功之后 test 才开始。lint 失败时,test 默认会被标记为 skipped。
每个 Job 使用不同的 Runner ,所以每个 Job 都要各自 checkout、各自安装依赖。若再增加一个不依赖 test、只依赖 lint 的 build Job,并同样写 needs: lint,则 test 与 build 可以并行。
6.3 CD:加一个 deploy Job
CI 只验证;deploy 负责上线。典型写法:
yaml
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
- run: npm ci && npm run build
# 在此接入 GitHub Pages、Vercel、SSH 等
needs: test --- 名为 test 的 Job 必须成功,deploy 才运行。若你把测试 Job 改成了别的名字,这里也要改成对应名字。
if: --- 两个条件同时满足才执行:
github.event_name == 'push': push 事件github.ref == 'refs/heads/main':当前引用是main分支。
Runner 用完会销毁,构建产物要推到托管平台、镜像仓库或目标服务器。
七、Secrets 与分支保护
7.1 密钥
API Token、SSH 私钥写在仓库 Secrets 里,workflow 中引用:
yaml
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
${{ secrets.XXX }} 在运行时注入;匹配到的 Secret 值在日志里会被打码。
配置入口:
sql
仓库顶栏 Settings → Secrets and variables → Actions → New repository secret
7.2 分支保护
在仓库 Settings → Branches 给 main 加规则(需要 Admin),例如:合并前必须通过 PR,且必须通过指定的 CI 检查。这样 CI 失败时无法合并,workflow 才真正起到门禁作用。