从零搭建 CI/CD:GitHub Actions 实战指南
GitHub Actions 是目前 GitHub 生态最主流的 CI/CD 工具。本文不只是罗列语法,而是从真实项目出发,搭建一套完整的 CI/CD 流水线——包含代码检查、测试、构建、部署全流程。
核心概念
| 概念 | 说明 |
|---|---|
| Workflow | 一个 .yml 文件,定义完整的自动化流程 |
| Job | Workflow 中的任务单元,并行执行(默认) |
| Step | Job 中的步骤,串行执行 |
| Action | 可复用的 Step,如 actions/checkout |
| Runner | 执行 Job 的机器(GitHub 托管或自建) |
| Artifact | 构建产物,可在 Job 间传递 |
基础:CI 流水线
代码检查 + 测试 + 构建
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
# 同一 PR 的新推送取消旧的运行
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm typecheck
- name: Test
run: pnpm test --coverage
- name: Build
run: pnpm build
- name: Upload coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
矩阵测试(多版本兼容性)
jobs:
test:
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
fail-fast: false # 一个失败不取消其他
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
进阶:CD 自动部署
Docker 镜像构建与推送
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
tags: ['v*']
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=commit-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
部署到 K8s
deploy:
needs: docker
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Deploy to K8s
uses: steebchen/kubectl@v2.1.1
with:
config: ${{ secrets.KUBE_CONFIG }}
command: set image deployment/myapp
app=ghcr.io/${{ github.repository }}:${{ github.sha }}
-n production
- name: Verify deployment
run: |
kubectl rollout status deployment/myapp -n production
kubectl get pods -n production -l app=myapp
部署到服务器(SSH)
deploy-ssh:
needs: docker
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /app/myapp
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
环境变量与 Secrets 管理
分环境配置
jobs:
deploy:
environment:
name: production # 需要在 GitHub 仓库 Settings → Environments 中配置
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
NODE_ENV: production
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
动态环境变量
jobs:
deploy:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- id: meta
run: echo "version=$(date +%Y%m%d)-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
notify:
needs: deploy
runs-on: ubuntu-latest
steps:
- run: echo "Deployed version ${{ needs.deploy.outputs.image-tag }}"
Monorepo CI/CD
变更检测
jobs:
changes:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.filter.outputs.web }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
web:
- 'apps/web/**'
- 'packages/ui/**'
api:
- 'apps/api/**'
- 'packages/utils/**'
deploy-web:
needs: changes
if: ${{ needs.changes.outputs.web == 'true' }}
runs-on: ubuntu-latest
steps:
- run: echo "Deploy web"
deploy-api:
needs: changes
if: ${{ needs.changes.outputs.api == 'true' }}
runs-on: ubuntu-latest
steps:
- run: echo "Deploy api"
Turborepo 增量构建
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # turbo 需要完整 git 历史
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# 只构建相比 main 分支有变化的包
- run: pnpm build --filter=...[origin/main]
- run: pnpm test --filter=...[origin/main]
实用技巧
缓存依赖
# setup-node 已经内置 pnpm 缓存
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm' # 自动缓存 pnpm store
并行 Job + 串行部署
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
build:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: [lint, test, build] # 三个并行完成后才执行
runs-on: ubuntu-latest
steps: [...]
通知(飞书/钉钉/Slack)
notify:
needs: [deploy]
if: always()
runs-on: ubuntu-latest
steps:
- name: Notify Feishu
if: ${{ needs.deploy.result == 'success' }}
uses: foxundermoon/feishu-action@v2
with:
url: ${{ secrets.FEISHU_WEBHOOK }}
msg_type: text
content: |
✅ 部署成功
仓库: ${{ github.repository }}
提交: ${{ github.event.head_commit.message }}
作者: ${{ github.actor }}
- name: Notify on failure
if: ${{ needs.deploy.result == 'failure' }}
uses: foxundermoon/feishu-action@v2
with:
url: ${{ secrets.FEISHU_WEBHOOK }}
msg_type: text
content: |
❌ 部署失败
仓库: ${{ github.repository }}
查看: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
自动创建 Release
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: softprops/action-gh-release@v2
with:
generate_release_notes: true # 自动从 PR 生成 changelog
draft: false
prerelease: ${{ contains(github.ref, '-rc') }}
自托管 Runner
如果 GitHub 托管 Runner 满足不了需求(如需要访问内网服务器、GPU 计算等):
# 在自建服务器上
mkdir actions-runner && cd actions-runner
curl -o actions-runner.tar.gz -L https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz
tar xzf actions-runner.tar.gz
./config.sh --url https://github.com/owner/repo --token TOKEN
./run.sh
# 注册为系统服务
sudo ./svc.sh install
sudo ./svc.sh start
# 使用自托管 Runner
jobs:
deploy:
runs-on: self-hosted # 或 ['self-hosted', 'linux', 'x64']
steps:
- run: ./deploy.sh
安全最佳实践
1. 最小权限原则
permissions:
contents: read # 默认只读
packages: write # 只有需要推送镜像的 Job 才给
pull-requests: write # 只有需要评论 PR 的 Job 才给
2. Secrets 不出现在日志中
- name: Deploy
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
# ✅ 使用环境变量
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/deploy
# ❌ 不要 echo secrets
# echo $TOKEN
3. 第三方 Action 固定版本
# ❌ 不安全(可能被篡改)
- uses: some/action@main
# ✅ 固定到 commit hash(最安全)
- uses: some/action@a1b2c3d4e5f6...
# ✅ 固定到 tag(可接受)
- uses: actions/checkout@v4.1.4
总结
一套完整的 CI/CD 流水线应该包含:
PR 提交 → Lint + TypeCheck + Test(并行)→ Build → 审核通过 → 合并 main
↓
Docker 镜像构建
↓
部署到 Staging
↓
手动确认 → 部署到 Production
关键原则:
- 快速反馈:CI 在 PR 时运行,5 分钟内完成
- 只构建变化:Monorepo 中用 paths-filter / turbo --filter
- 缓存一切:依赖缓存、Docker 层缓存、构建缓存
- 安全第一:最小权限、固定 Action 版本、Secrets 不暴露
- 可观测:部署通知、失败告警、日志可追溯
GitHub Actions 的免费额度(2000 分钟/月)对个人项目绰绰有余,团队建议直接用 Team plan。
Comments | 0条评论