为什么需要 CI/CD?
每次手动跑测试、手动构建、手动部署,不仅耗时还容易出错。CI/CD 流水线可以在你 git push 的那一刻自动完成所有重复性工作:代码检查、单元测试、构建产物、部署上线。
GitHub Actions 是 GitHub 内置的 CI/CD 平台,免费额度对开源项目完全够用,私有项目每月也有 2000 分钟免费额度。相比 Jenkins 需要自己维护服务器,GitHub Actions 零运维成本。
基础概念
GitHub Actions 的核心是 Workflow(工作流),由 YAML 文件定义,存放在 .github/workflows/ 目录下。一个 Workflow 包含:
- Trigger:什么时候触发(push、PR、定时、手动等)
- Job:一组步骤的集合,多个 Job 可并行执行
- Step:单个任务,可以运行命令或调用预置 Action
- Action:可复用的组件,社区有大量现成的 Action
实战:Node.js 项目 CI/CD 流水线
以一个 Node.js + TypeScript 项目为例,搭建从代码检查到自动部署的完整流水线。
1. 创建工作流文件
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
# 触发条件:push 到 main 或创建 PR
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# 设置权限(最小权限原则)
permissions:
contents: write
pull-requests: read
# 环境变量
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ============ 第一阶段:代码质量检查 ============
lint:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Type Check
run: npm run type-check
# ============ 第二阶段:测试(并行多个 Node 版本) ============
test:
name: Test (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
needs: lint # lint 通过才跑测试
strategy:
matrix:
node-version: [18, 20, 22] # 多版本兼容性测试
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage report
if: matrix.node-version == '20'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
# ============ 第三阶段:构建 Docker 镜像 ============
build:
name: Build & Push Image
runs-on: ubuntu-latest
needs: test # 测试通过才构建
if: github.ref == 'refs/heads/main' # 只在 main 分支构建
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ============ 第四阶段:部署 ============
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://your-app.example.com
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/your-app
docker compose pull
docker compose up -d
docker image prune -f
echo "Deployed successfully at $(date)"
|
2. 配置 Secrets
在 GitHub 仓库的 Settings → Secrets and variables → Actions 中添加敏感信息:
1
2
3
4
5
6
|
# 需要配置的 Secrets
SERVER_HOST=your-server-ip
SERVER_USER=deploy
SSH_PRIVATE_KEY=-----BEGIN OPENSSH PRIVATE KEY-----
...你的私钥内容...
-----END OPENSSH PRIVATE KEY-----
|
安全提示:永远不要把密钥提交到代码仓库。GitHub Secrets 会自动加密,日志中也会被遮蔽。
3. 使用矩阵策略加速测试
上面的例子用 strategy.matrix 同时在 Node 18、20、22 三个版本跑测试。GitHub 会并行启动三个 Job,大幅缩短反馈时间。实际效果:
1
2
|
传统方式:lint(2min) → test@18(3min) → test@20(3min) → test@22(3min) = 11分钟
矩阵策略:lint(2min) → test@18+20+22 并行(3min) = 5分钟
|
4. 缓存依赖加速构建
actions/setup-node@v4 的 cache: 'npm' 会自动缓存 ~/.npm 目录。首次运行后,后续构建的 npm ci 会快很多:
1
2
3
4
5
6
7
8
|
# 手动缓存示例(适用于非 npm 项目)
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
|
5. 环境保护规则
在 GitHub 仓库的 Settings → Environments → production 中配置:
- Required reviewers:部署前需要指定人员审批
- Wait timer:部署前等待 N 分钟(用于回滚窗口)
- Branch policies:只允许 main 分支部署到生产环境
这样即使代码合并到 main,也需要人工点击 Approve 才会真正部署。
进阶技巧
手动触发工作流
1
2
3
4
5
6
7
8
9
10
11
|
on:
workflow_dispatch:
inputs:
environment:
description: 'Deploy target'
required: true
default: 'staging'
type: choice
options:
- staging
- production
|
复用工作流(Reusable Workflow)
把通用的 CI 步骤抽成独立工作流,多个项目共享:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# .github/workflows/reusable-ci.yml
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci && npm test
|
其他项目直接引用:
1
2
3
4
5
|
jobs:
ci:
uses: your-org/repo/.github/workflows/reusable-ci.yml@main
with:
node-version: '22'
|
条件执行与错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
steps:
- name: Deploy
id: deploy
run: ./deploy.sh
- name: Notify on failure
if: failure() # 仅在前面步骤失败时执行
run: |
curl -X POST "${{ secrets.WEBHOOK_URL }}" \
-d '{"text": "部署失败!请检查 Actions 日志"}'
- name: Notify on success
if: success()
run: echo "部署成功!"
|
实际项目中的典型流程
把上面的片段组合起来,一个真实项目的 CI/CD 流程通常是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
开发者 push 代码
↓
┌─────────────────────────────────────┐
│ lint: ESLint + TypeScript 类型检查 │ ← 快速反馈(~1分钟)
└─────────────────────────────────────┘
↓ 通过
┌─────────────────────────────────────┐
│ test: 多版本并行测试 + 覆盖率 │ ← 并行执行(~3分钟)
└─────────────────────────────────────┘
↓ 通过
┌─────────────────────────────────────┐
│ build: Docker 镜像构建 + 推送 │ ← 仅 main 分支(~5分钟)
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ deploy: SSH 部署 + 健康检查 │ ← 需要审批
└─────────────────────────────────────┘
|
常见问题
Q:私有仓库的 Actions 免费额度够用吗?
免费额度是每月 2000 分钟(Linux runner),Ubuntu 是 1x 倍率,Windows 是 2x,macOS 是 10x。一个典型的 CI 流程(lint + test + build)大约消耗 10-15 分钟,2000 分钟大约能跑 130-200 次,对中小团队完全够用。
Q:如何调试失败的 workflow?
1
2
3
4
|
# 本地运行 act(Docker 模拟 GitHub Actions 环境)
curl -sf https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
act -j lint # 只运行 lint job
act -r # 使用 Linux 容器(默认用 Docker)
|
Q:如何避免 Secrets 泄露?
- 在 Workflow 中添加
permissions: {} 限制最小权限
- 使用
GITHUB_TOKEN 而非 PAT(自动过期)
- 在仓库 Settings 中限制哪些分支可以访问 Secrets
- 定期轮换密钥
总结
GitHub Actions 的核心优势在于零运维和生态丰富。你不需要维护 Jenkins 服务器,不需要配置 Webhook,push 代码就自动触发。社区有超过 20000 个预置 Action,覆盖了从代码检查到云部署的几乎所有场景。
对于中小团队来说,用好 GitHub Actions 就够了。把重复的测试、构建、部署工作交给流水线,把精力留给真正需要人工判断的事情。