CI/CD流程深度解析:从手动部署到全自动流水线
CI/CD 是从代码提交到生产部署的自动化管道:CI 保证每次变更自动构建测试,CD 保证通过验证的产物可靠发布。 本质是化发布日地狱为无数次安全小发布,面试常考流水线阶段设计、缓存策略与部署回滚机制。
一句话概括
CI/CD 是一套从”代码提交”到”生产部署”的自动化管道——持续集成(CI)确保每次变更都能自动构建测试,持续交付/部署(CD)确保通过验证的产物能可靠地发布到目标环境。本质上是把”发布日地狱”拆解为无数次安全的小发布。
核心知识点
1. CI/CD 三阶段模型
| 阶段 | 做什么 | 人工介入 | 典型时长 |
|---|---|---|---|
| CI(持续集成) | Lint → Test → Build | 无 | 3-15 分钟 |
| 持续交付 | 制品管理 → 自动部署到测试环境 | 审批是否发布 | 10-30 分钟 |
| 持续部署 | 全自动发布到生产 | 无 | 取决于策略 |
核心洞察:软件交付的风险往往不来自代码本身,而来自”最后一次集成”的不可预测性。把大爆炸式发布拆散为高频小发布,是 CI/CD 的灵魂。
2. GitHub Actions 核心模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '${{ env.NODE_VERSION }}', cache: 'npm' }
- run: npm ci
- run: npx eslint src/ --max-warnings 0
test:
needs: lint # 依赖 lint 完成后才执行
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '${{ env.NODE_VERSION }}', cache: 'npm' }
- run: npm ci
- run: npx vitest run --coverage
- name: Check coverage threshold
run: |
npx istanbul check-coverage --statements 80 --branches 75 \
--functions 80 --lines 80 coverage/coverage-final.json
build:
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '${{ env.NODE_VERSION }}', cache: 'npm' }
- run: npm ci
- run: npm run build
env:
NODE_ENV: production
needs 依赖形成流水线:lint → test → build,前一步失败则后续不执行,避免浪费资源。
3. 缓存策略:让 CI 快 10 倍
1
2
3
4
5
6
7
8
9
10
11
12
13
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
- uses: actions/cache@v4
with:
path: node_modules/.cache
key: build-cache-${{ runner.os }}-${{ github.sha }}
restore-keys: build-cache-${{ runner.os }}-
命中逻辑:hashFiles('package-lock.json') 不变 → key 不变 → 直接命中缓存(~5 秒)。未命中时用 restore-keys 前缀匹配最近的可用缓存。一个 200+ 依赖的项目,缓存命中后 CI 从 8 分钟降到 1.5 分钟。
4. 前端部署缓存策略
前端资源的缓存策略是面试高频题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 带哈希的文件(内容变更 = 文件名变更)—— 永久缓存
- run: |
aws s3 sync dist/assets/ s3://bucket/assets/ \
--cache-control "public, max-age=31536000, immutable"
# index.html —— 完全不缓存
- run: |
aws s3 cp dist/index.html s3://bucket/index.html \
--cache-control "no-cache, no-store, must-revalidate"
# Service Worker —— 不缓存
- run: |
aws s3 cp dist/sw.js s3://bucket/sw.js \
--cache-control "no-cache"
原理:main.a1b2c3d.js 中的 a1b2c3d 是内容哈希,内容变了哈希就变 → 新文件名 → 浏览器自动下载新文件。immutable 告诉浏览器”此 URL 的内容永不改变,不要发 304 验证请求”。index.html 不缓存是因为它引用了带哈希的资源,必须确保用户拿到最新版本。
5. Monorepo 智能流水线
不希望在每次提交时构建和测试所有包——只检测变更文件所属的包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.changed.outputs.packages }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- id: changed
run: |
CHANGED=$(git diff --name-only origin/main...HEAD | \
grep -oE 'packages/[^/]+' | sort -u | \
jq -R -s -c 'split("\n")[:-1]')
echo "packages=$CHANGED" >> $GITHUB_OUTPUT
build:
needs: [detect-changes]
if: needs.detect-changes.outputs.packages != '[]'
strategy:
matrix:
package: ${{ fromJSON(needs.detect-changes.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx turbo run build --filter=${{ matrix.package }}
核心价值:30 个包的 Monorepo 只改了 2 个包 → CI 只构建这 2 个包,而非全量 30 个。
其实你每天都在用
- 每次
git push后 GitHub Actions 自动跑 CI:.github/workflows/目录下的 YAML 就是触发条件 - PR 页面的绿色✅或红色❌:那是 CI 的 lint/test/build 结果,红色表示不能合并
npm install在 CI 中用npm ci而非npm install:npm ci严格按 lock 文件安装、删除node_modules重来,保证 CI 环境确定性- 生产环境回滚只需要改 symlink:
current → v1.0.0切换为current → v1.0.1,切回来就是current → v1.0.0——原子操作,秒级回滚 - GitHub Pages 的自动部署:push 到
main(或gh-pages分支),GitHub Actions 自动构建并发布到<username>.github.io
常见误解(FAQ)
❌ 误区 1:「CI 和 CD 是一回事」
CI 关注开发阶段的反馈循环(代码能不能编译通过、测试能不能跑通),CD 关注发布阶段的自动化(能不能自动上线)。CI 失败意味着”当前代码不可用”,CD 失败意味着”无法上线”。两者解决的问题不同。
❌ 误区 2:「CD 的环境变量把 .env 也传上去就行」
.env 文件通常包含开发环境变量,不适合生产。正确做法是用 CI 平台的 Secrets + Environment 机制:$ { { secrets.PRODUCTION_API_KEY } },按环境隔离(environment: staging vs environment: production),密钥加密存储且不会出现在日志中。
❌ 误区 3:「部署就是 ftp 上传」
前端部署不是”把文件丢到服务器上”,而是一个包含 CDN 缓存策略、版本化目录、健康检查、回滚机制的完整流程。aws s3 sync 只是其中一步,前面有构建验证,后面有 cloudfront create-invalidation 刷新缓存。
❌ 误区 4:「CI 失败就一定是 CI 配置有问题」
很多时候 CI 失败的原因是你的代码确实是错的(测试挂了、类型不对、lint 不合格),而不是 CI 配错了。先排查代码质量,再排查 CI 配置。另外,确保本地环境和 CI 环境一致(npm ci 而非 npm install,锁定 Node 版本)。
一句话总结
CI/CD 的终极目标是让部署变得「无聊」——每次发布都走相同的流水线、产生相同的结果、不需要任何”紧急操作”。越无聊越可靠,越可靠越敢频繁发布。