CI/CD 流水线:GitHub Actions 实战
1. CI / CD 到底是什么
你可能在公司用过 Jenkins、GitLab CI、飞书流水线。CI/CD 两个词分开理解:
- CI (Continuous Integration) 持续集成 —— 每次 push 自动跑测试 + 构建,保证主干不崩
- CD (Continuous Delivery / Deployment) 持续交付/部署 —— 通过的代码自动发到某个环境(预发 / 生产)
为什么要:
- 本地跑测试容易忘,机器跑就绝不漏
- 每次改动独立验证,问题定位清晰
- 人工部署累、易出错(上错文件、漏跑迁移)
Java 对照:
- Jenkins/GitLab CI ≈ GitHub Actions
- Maven profile ≈ Action 的
env/if - nexus 私有仓库 ≈ Docker Hub / GHCR
- 生产部署脚本 ≈ Actions 里的 deploy step
2. GitHub Actions 基础模型
一个 Action 工作流(Workflow)放在仓库的 .github/workflows/xxx.yml:
name: CI # 工作流名字
on: # 触发条件
push:
branches: [main]
pull_request:
jobs: # 并行的任务
test:
runs-on: ubuntu-latest # 跑在什么机器上
steps: # 每个任务的步骤
- uses: actions/checkout@v4
- run: echo "hello"
核心概念:
| 概念 | 含义 | Jenkins 对照 |
|---|---|---|
| Workflow | 一整套流程(.yml 文件) | Pipeline |
| Job | 一个独立任务(能并行) | Stage(但 Actions 的 job 默认并行) |
| Step | 任务内的单条命令 | Step |
| Runner | 执行环境 | Jenkins Agent |
| Action | 可复用的 step 包(社区共享) | Shared Library |
3. 第一个实用 CI:push 自动跑 lint + test + build
in4vue 的 .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm exec vue-tsc --noEmit
- name: Test
run: pnpm test:run
- name: Build
run: pnpm build
push 到 main 或 PR 打开时,GitHub 自动跑这套流程。任一 step 失败 → workflow 标红 → PR 上显示 ❌ 阻止合并(配合仓库的"保护分支规则")。
关键点:
actions/checkout@v4拉代码pnpm/action-setup装 pnpmcache: pnpm缓存node_modules,二次跑快 5 倍--frozen-lockfile锁死版本,CI 和本地完全一致
4. 缓存策略:CI 快不快看缓存
no cache:每次 pnpm install 从 registry 下载所有依赖,3-5 分钟。
有 cache:几秒钟命中缓存。
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm # 自动根据 pnpm-lock.yaml 缓存
高级场景还能缓存 Vite 的 node_modules/.vite(开发服务器预构建),但 build 产物一般不缓存(hash 变化频繁)。
5. 矩阵构建:一次测多个环境
想在 Node 20 / 22 两个版本下都跑测试:
jobs:
test:
strategy:
matrix:
node: [20, 22]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test:run
效果:会并行跑 4 个组合(2 Node × 2 OS)。个人项目不用开矩阵,公共库发布前再开。
6. 部署到 Cloudflare Pages
in4vue 的生产部署。两种方式:
6.1 Cloudflare 官方的 Git 集成(最简单)
- Cloudflare Dashboard → Pages → Create a project → 连接 GitHub
- 配构建命令
pnpm build,输出目录dist - 每次 push main → 自动部署
- 每个 PR → 自动预览站点
这是 in4vue 当前的部署方式。Actions 都不用写。
6.2 用 Action 主动推
适合"想自己控制部署时机"的场景:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
projectName: in4vue
directory: dist
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
秘密配置:
secrets.CF_API_TOKEN—— Cloudflare 里生成的 API Tokensecrets.CF_ACCOUNT_ID—— Cloudflare 账户 IDsecrets.GITHUB_TOKEN—— GitHub 自动生成的 token
在仓库的 Settings → Secrets and variables → Actions 里加。
7. Secrets 的正确姿势
永远不要把密钥、token 写在代码里或 yml 里:
# ❌ 灾难
- run: curl -H "Authorization: Bearer abc123def" ...
# ✅ 从 Secrets 拿
- run: curl -H "Authorization: Bearer $TOKEN" ...
env:
TOKEN: ${{ secrets.API_TOKEN }}
7.1 Secrets 的层级
- Repository secrets —— 单仓库使用
- Environment secrets —— 绑定到特定环境(如 production),配合环境保护
- Organization secrets —— 组织下所有仓库共享
分环境 secrets 示例:
jobs:
deploy-staging:
environment: staging # 用 staging 环境的 secrets
steps:
- env:
API_KEY: ${{ secrets.API_KEY }} # 自动从 staging env 取
deploy-prod:
environment: production # 用 production 环境的 secrets(可配需审批)
needs: deploy-staging # 等 staging 完成
steps:
- env:
API_KEY: ${{ secrets.API_KEY }}
8. PR 预览:每个 PR 一个独立环境
Cloudflare Pages 默认带:每个 PR 自动部预览站点 pr-42.in4vue.pages.dev。PR 关闭后自动清理。
如果用其他平台,可以手动实现:
name: PR Preview
on:
pull_request:
jobs:
preview:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Deploy to preview
id: deploy
run: |
# 部署到子域名,如 pr-${{ github.event.number }}.preview.example.com
URL="https://pr-${{ github.event.number }}.preview.example.com"
echo "url=$URL" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: marocchino/sticky-pull-request-comment@v2
with:
message: |
✅ 预览站点: ${{ steps.deploy.outputs.url }}
效果:PR 下面出现机器人评论贴预览链接。Reviewer 点进去直接看效果。
9. 版本发布:tag 触发
想做"打 tag → 自动发 release"?
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: dist/**/*
generate_release_notes: true
- name: Notify
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text":"in4vue ${{ github.ref_name }} 发布成功 🎉"}'
发布流程:
git tag v0.2.0
git push origin v0.2.0
# → GitHub Actions 自动跑 release workflow
# → 生成 GitHub Release + 附件是 dist/
# → Slack 通知
10. 分阶段部署:staging → prod
典型企业流程:
push main → 自动部 staging
手动点按钮 → 部 prod (需要审批)
name: Deploy
on:
push:
branches: [main]
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.in4vue.com
steps:
- uses: actions/checkout@v4
# ... 构建 + 部署到 staging
deploy-prod:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production # 这个 environment 在仓库设置里配"需要审批"
url: https://in4vue.com
steps:
- uses: actions/checkout@v4
# ... 构建 + 部署到 prod
审批流程:在 Settings → Environments → production 里勾选 "Required reviewers",指定 1-3 个人。deploy-prod job 到这里会暂停,等审批人点"Approve"才继续。
Java 对照:类似 Jenkins 的 input 步骤 —— 需要人工确认才往下。
11. 失败时自动化
11.1 失败 → 钉钉 / Slack 通知
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ in4vue CI 失败: ${{ github.workflow }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
11.2 失败 → 自动开 issue
- name: Open issue on failure
if: failure() && github.ref == 'refs/heads/main'
uses: JasonEtco/create-an-issue@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
filename: .github/workflows/ci-failure-issue.md
12. 手动触发 workflow
偶尔想不 push 代码也能跑一下部署?用 workflow_dispatch:
on:
workflow_dispatch:
inputs:
env:
type: choice
options: [staging, production]
dry_run:
type: boolean
default: false
GitHub 仓库的 Actions 标签页就有个"Run workflow"按钮,填参数跑。
用途:紧急补救、重跑某次失败的部署、手动触发缓存清理。
13. 定时任务
on:
schedule:
- cron: '0 3 * * *' # 每天 UTC 3 点(北京 11 点)
用途:
- 定期跑全量测试(如果 CI 跑的是快速测试子集)
- 定期刷依赖漏洞扫描(
npm audit、Snyk) - 定期清 Cloudflare 缓存
14. 并行 vs 串行
默认所有 job 并行。需要串行时用 needs:
jobs:
test: ...
build:
needs: test # test 通过才跑
...
deploy:
needs: [test, build]
...
技巧:lint + type-check + test + build 能并行的全并行,CI 时间缩半。
15. 依赖审计:自动发现漏洞
- name: Audit
run: pnpm audit --prod
continue-on-error: true # 发现漏洞不直接失败,只报警
更强的工具:
- Dependabot —— GitHub 内置,依赖有新版或漏洞时自动开 PR
- Snyk —— 商业工具,支持更多维度
- npm audit-ci-lite —— 按严重级别配置阈值
Dependabot 最简配置(.github/dependabot.yml):
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
all:
patterns: ['*']
每周一自动检查依赖,有更新打包在一个 PR 里。
16. 分支保护:让 CI 真有用
光跑 CI 没用——关键是不通过不让合。仓库 Settings → Branches → Add rule:
- Branch name pattern:
main - ✅ Require a pull request before merging
- ✅ Require status checks to pass before merging
- ✅ Require branches to be up to date
- 选中 CI workflow 里的 check 名
- ✅ Require conversation resolution before merging
- ✅ Do not allow bypassing the above settings
效果:
- main 不能直接 push
- PR 必须绿灯才能合
- review 评论必须处理
- 管理员也不能绕过
Java 对照:类似 GitLab 的"保护分支" + "流水线状态必须 success"才合并。
17. in4vue 的完整工作流集合
推荐一套:
.github/
└── workflows/
├── ci.yml # push/PR 触发的测试构建
├── preview.yml # PR 预览(Cloudflare 集成的话不用写)
├── release.yml # tag 触发发 release
└── audit.yml # 定时依赖审计
.github/dependabot.yml+ 分支保护规则。
小项目可以精简到一个 ci.yml,其他按需加。
18. 常见坑点
| 现象 | 原因 | 解法 |
|---|---|---|
| Actions 跑不起来,说找不到 workflow | 文件名不对或不在 .github/workflows/ |
检查路径 |
| 每次 install 超慢 | 没配 cache | actions/setup-node 加 cache: pnpm |
| secrets 在 PR 里是空的 | fork 的 PR 默认拿不到 secrets(安全考虑) | 单独的 workflow 处理或用 pull_request_target |
| 部署成功但访问 404 | 构建输出目录不对 | 确认 dist 路径和 Cloudflare 设置一致 |
| CI 通过但本地跑失败 | 环境差异(Node 版本、lockfile 过期) | 本地用 nvm use 22 对齐 |
| Secrets 泄漏 | commit 里写了 | 立刻轮换 token + git filter-repo 清历史 |
| 矩阵构建某个 OS 挂了 | 平台差异(CRLF、路径) | continue-on-error: true 或单独处理 |
19. 心智模型
代码进仓库的三道关:
1. 本地 pre-commit(husky + lint-staged)
↓ 挡住 commit 时的格式问题
2. CI (GitHub Actions)
↓ 挡住 PR 合并前的测试失败
3. CD (自动部署 + 环境保护)
↓ 挡住坏版本到生产
每道关失败都应该:
- 阻止往下走
- 给出清晰反馈
- 不需要靠人盯着
一旦 CI/CD 建立起来,交付节奏从"每周发一次,紧张半天"变成"每个 PR 合并即发,无感"——这就是现代工程化的基本盘。
20. in4vue 今天就能加的 3 件事
按收益排:
- ✅ CI workflow:push/PR 跑 lint + test + build(10 分钟上手)
- ✅ Dependabot:依赖漏洞自动开 PR(2 分钟配置)
- ✅ 分支保护:main 必须 PR + CI 通过(点几下设置)
做完这三件,项目工程化水平就到业界及格线了。
小练习
- 建
.github/workflows/ci.yml,按本篇第 3 节的内容 - 给仓库加
dependabot.yml,等一周看它开的 PR - 在 Settings → Branches 给 main 加保护规则
- 故意推一个 lint 不通过的 PR,看 CI 是否拦下
- 试试
workflow_dispatch手动触发
延伸阅读
- GitHub Actions 官方文档
- Awesome Actions(社区 action 集合)
- act(本地跑 Actions 调试工具)
- Cloudflare Pages GitHub Action
- GitHub Actions - 秘密安全最佳实践