介绍
# CI/CD Pipeline (GitHub Actions)
使用 GitHub Actions 设置和管理 CI/CD 流水线。涵盖工作流创建、测试、部署、发布自动化和调试。
## 适用场景
- 在推送/PR 时设置自动化测试 - 创建部署流水线(staging、production) - 使用变更日志和标签自动化发布 - 调试失败的 CI 工作流 - 设置矩阵构建以进行跨平台测试 - 管理 CI 中的密钥和环境变量 - 通过缓存和并行化优化 CI
## 快速开始:为项目添加 CI
### Node.js 项目
```yaml # .github/workflows/ci.yml name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - run: npm test - run: npm run lint ```
### Python 项目
```yaml # .github/workflows/ci.yml name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" cache: pip - run: pip install -r requirements.txt - run: pytest - run: ruff check . ```
### Go 项目
```yaml # .github/workflows/ci.yml name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "1.22" - run: go test ./... - run: go vet ./... ```
### Rust 项目
```yaml # .github/workflows/ci.yml name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test - run: cargo clippy -- -D warnings ```
## 常见模式
### 矩阵构建(跨版本/操作系统测试)
```yaml jobs: test: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] node-version: [18, 20, 22] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test ```
### 条件作业
```yaml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test
deploy: needs: test if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./deploy.sh ```
### 缓存依赖项
```yaml # Node.js (automatic with setup-node) - uses: actions/setup-node@v4 with: node-version: 20 cache: npm # or yarn, pnpm
# Generic caching - uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.cargo/registry node_modules key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps- ```
### 制品(保存构建输出)
```yaml - uses: actions/upload-artifact@v4 with: name: build-output path: dist/ retention-days: 7
# Download in another job - uses: actions/download-artifact@v4 with: name: build-output path: dist/ ```
### 按计划运行(cron)
```yaml on: schedule: - cron: "0 6 * * 1" # Every Monday at 6 AM UTC workflow_dispatch: # Also allow manual trigger ```
## 部署工作流
### 在打标签时部署到生产环境
```yaml name: Release
on: push: tags: - "v*"
jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - run: npm run build - run: npm test
# Create GitHub release - uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: | dist/*.js dist/*.css ```
### 部署到多个环境
```yaml name: Deploy
on: push: branches: [main, staging]
jobs: deploy: runs-on: ubuntu-latest environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} steps: - uses: actions/checkout@v4 - run: npm ci && npm run build - run: | if [ "${{ github.ref }}" = "refs/heads/main" ]; then ./deploy.sh production else ./deploy.sh staging fi env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} ```
### Docker 构建与推送
```yaml name: Docker
on: push: branches: [main] tags: ["v*"]
jobs: build: runs-on: ubuntu-latest permissions: packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: push: true tags: | ghcr.io/${{ github.repository }}:latest ghcr.io/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ```
### 在发布时发布到 npm
```yaml name: Publish
on: release: types: [published]
jobs: publish: runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org - run: npm ci - run: npm test - run: npm publish --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ```
## 密钥管理
### 通过 CLI 设置密钥
```bash # Set a repository secret gh secret set DEPLOY_TOKEN --body "my-secret-value"
# Set from a file gh secret set SSH_KEY < ~/.ssh/deploy_key
# Set for a specific environment gh secret set DB_PASSWORD --env production --body "p@ssw0rd"
# List secrets gh secret list
# Delete a secret gh secret delete OLD_SECRET ```
### 在工作流中使用密钥
```yaml env: # Available to all steps in this job DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps: - run: echo "Deploying..." env: # Available to this step only API_KEY: ${{ secrets.API_KEY }} ```
### 环境保护规则
通过 GitHub UI 或 API 进行设置: - 部署前需要的审核者 - 等待计时器 - 分支限制 - 自定义部署分支策略
```bash # View environments gh api repos/{owner}/{repo}/environments | jq '.environments[].name' ```
## 工作流调试
### 重新运行失败的作业
```bash # List recent workflow runs gh run list --limit 10
# View a specific run gh run view <run-id>
# View failed job logs gh run view <run-id> --log-failed
# Re-run failed jobs only gh run rerun <run-id> --failed
# Re-run entire workflow gh run rerun <run-id> ```
### 使用 SSH 调试(使用 tmate)
```yaml # Add this step before the failing step - uses: mxschmitt/action-tmate@v3 if: failure() with: limit-access-to-actor: true ```
### 常见失败与修复
**脚本出现“Permission denied”(权限被拒绝)** ```yaml - run: chmod +x ./scripts/deploy.sh && ./scripts/deploy.sh ```
**“Node modules not found”(找不到 Node 模块)** ```yaml # Make sure npm ci runs before npm test - run: npm ci # Install exact lockfile versions - run: npm test # Now node_modules exists ```
**“Resource not accessible by integration”(集成无法访问资源)** ```yaml # Add permissions block permissions: contents: write packages: write pull-requests: write ```
**缓存未恢复** ```yaml # Check cache key matches - use hashFiles for lockfile key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} # NOT: key: ${{ runner.os }}-node-${{ hashFiles('package.json') }} ```
**工作流未触发** - 检查:工作流文件是否在默认分支上? - 检查:触发事件是否匹配?(`push` 对比 `pull_request`) - 检查:分支筛选条件是否正确? ```bash # Manually trigger a workflow gh workflow run ci.yml --ref main ```
## 工作流验证
### 推送前在本地验证
```bash # Check YAML syntax python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" && echo "Valid"
# Use actionlint (if installed) actionlint .github/workflows/ci.yml
# Or via Docker docker run --rm -v "$(pwd):/repo" -w /repo rhysd/actionlint:latest ```
### 以图形方式查看工作流
```bash # List all workflows gh workflow list
# View workflow definition gh workflow view ci.yml
# Watch a running workflow gh run watch ```
## 高级模式
### 可重用工作流
```yaml # .github/workflows/reusable-test.yml name: Reusable Test on: workflow_call: inputs: node-version: required: false type: string default: "20" secrets: npm-token: required: false
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - run: npm ci - run: npm test ```
```yaml # .github/workflows/ci.yml - caller name: CI on: [push, pull_request] jobs: test: uses: ./.github/workflows/reusable-test.yml with: node-version: "20" ```
### 并发(防止重复运行)
```yaml concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # Cancel previous runs for same branch ```
### 路径筛选器(仅针对相关更改运行)
```yaml on: push: paths: - "src/**" - "package.json" - "package-lock.json" - ".github/workflows/ci.yml" paths-ignore: - "docs/**" - "*.md" ```
### Monorepo:仅测试发生变更的包
```yaml jobs: changes: runs-on: ubuntu-latest outputs: api: ${{ steps.filter.outputs.api }} web: ${{ steps.filter.outputs.web }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | api: - 'packages/api/**' web: - 'packages/web/**'
test-api: needs: changes if: needs.changes.outputs.api == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cd packages/api && npm ci && npm test
test-web: needs: changes if: needs.changes.outputs.web == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cd packages/web && npm ci && npm test ```
## 提示
- 在每个工作流上使用 `workflow_dispatch`,以便在调试期间手动触发 - 为了供应链安全,将 action 版本固定为 SHA:`uses: actions/checkout@b4ffde...` - 对非关键步骤(如 linting)使用 `continue-on-error: true` - 在作业上设置 `timeout-minutes` 以防止失控的构建(默认为 360 分钟) - 使用作业输出在作业之间传递数据:`outputs: result: ${{ steps.step-id.outputs.value }}` - 对于自托管运行器:使用 `runs-on: self-hosted` 并配合标签以定位特定机器