前言:为什么需要CI/CD?

在项目开发过程中,每次修改代码都需要重复运行测试、构建和部署的过程。一个人开发时还好,但团队协作时这个过程会变得相当繁琐。如果你曾说过"在我的电脑上是正常的啊",那么你可能已经感受到了CI/CD的必要性。

今天是Git & GitHub协作精通系列的第5篇,我们将深入探讨使用GitHub Actions的CI/CD。从初学者也能理解的基础知识开始,到可以立即应用于项目的实战工作流程,我们将一起学习。

1. 什么是CI/CD

1.1 CI (Continuous Integration) - 持续集成

CI是一种开发方法,开发者频繁且定期地将代码变更集成到主分支。不仅仅是简单地合并代码,每次集成时都会自动构建和运行测试,确认没有问题。

举个例子。假设三个开发者各自在开发不同的功能。如果没有CI,他们各自开发几周后一次性合并时可能会发生巨大的冲突。但应用CI后,每天甚至一天多次集成代码并自动验证,可以早期发现和解决问题。

1.2 CD (Continuous Delivery/Deployment) - 持续交付/部署

CD有两种含义。Continuous Delivery是指代码变更自动经过测试后准备好可部署状态,Continuous Deployment则更进一步,指自动部署到生产环境。

总结如下:

  • Continuous Delivery:代码自动准备到随时可部署的状态(部署按钮由人按)
  • Continuous Deployment:通过所有测试后自动部署到生产环境

1.3 CI/CD的实际好处

我在实际引入CI/CD时感受到的最大变化是"对部署的恐惧消失了"。以前很不愿意在周五下午部署。因为如果出问题,周末就要加班。但有了自动化测试和部署流水线后,随时都可以自信地部署了。

2. GitHub Actions介绍

2.1 什么是GitHub Actions?

GitHub Actions是GitHub直接提供的CI/CD平台。自2019年正式发布以来快速增长,现在已成为最流行的CI/CD工具之一。最大的优点是与GitHub仓库完美集成,无需设置单独的外部服务即可直接使用。

GitHub Actions的核心特点:

  • GitHub原生:无需单独服务集成,直接在仓库中使用
  • 基于YAML配置:易于理解的声明式语法
  • 市场:数千个预制Action可复用
  • 免费额度:公共仓库无限制,私有仓库每月2000分钟免费
  • 多种运行器:支持Linux、Windows、macOS环境

2.2 与其他CI/CD工具的比较

还有Jenkins、CircleCI、Travis CI等其他CI/CD工具。各有优缺点,但如果主要使用GitHub,选择GitHub Actions在大多数情况下是好的选择。配置简单,不需要额外的基础设施建设,与GitHub的事件(Push、PR等)自然集成。

3. 理解Workflow文件结构

3.1 工作流文件位置

GitHub Actions的工作流文件位于仓库的.github/workflows/目录中。在此目录创建YAML文件后,GitHub会自动识别并执行。

my-project/
├── .github/
│   └── workflows/
│       ├── ci.yml
│       ├── deploy.yml
│       └── test.yml
├── src/
├── tests/
└── package.json

3.2 工作流的组成要素

工作流具有以下层次结构:

  • Workflow(工作流):自动化的整个流程,由一个YAML文件定义
  • Event(事件):触发工作流的触发器
  • Job(作业):在同一运行器上执行的步骤集合
  • Step(步骤):单个工作单位,执行shell命令或action
  • Action(动作):可复用的工作单位

4. 掌握基本语法

4.1 name - 工作流名称

指定工作流的名称。这是在GitHub Actions标签页中显示的名称,请写得易于识别。

name: CI Pipeline

4.2 on - 事件触发器

定义工作流何时执行。让我们看看最常用的事件。

# Push事件
on:
  push:
    branches: [ main, develop ]
    paths:
      - 'src/**'
      - '!src/**/*.md'

# Pull Request事件
on:
  pull_request:
    branches: [ main ]
    types: [ opened, synchronize, reopened ]

# 定时任务(cron语法)
on:
  schedule:
    - cron: '0 9 * * 1'  # 每周一上午9点(UTC)

# 手动执行
on:
  workflow_dispatch:
    inputs:
      environment:
        description: '选择部署环境'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

4.3 jobs - 作业定义

定义实际要执行的作业。可以定义多个job,默认情况下并行执行。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 设置Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: 安装依赖
        run: npm ci

      - name: 构建
        run: npm run build

4.4 runs-on - 指定运行器环境

指定作业运行的虚拟环境。可以使用GitHub托管的运行器,也可以使用自托管运行器。

# GitHub托管运行器
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
runs-on: windows-latest
runs-on: macos-latest

# 自托管运行器
runs-on: self-hosted

4.5 uses和run - 执行步骤

uses用于使用预制的action,run用于直接执行shell命令。

steps:
  # 使用action
  - name: 检出代码
    uses: actions/checkout@v4

  # 执行shell命令
  - name: 运行测试
    run: npm test

  # 多行命令
  - name: 构建和部署
    run: |
      npm run build
      npm run deploy

5. 常用Actions

5.1 actions/checkout

最基本的action,将仓库代码检出到工作流环境。几乎所有工作流都在第一步使用。

- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # 获取完整历史(需要标签、分支信息时)
    ref: ${{ github.head_ref }}  # 检出特定分支

5.2 actions/setup-node

设置Node.js环境。可以同时指定版本和缓存设置。

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # 自动缓存node_modules
    registry-url: 'https://registry.npmjs.org'  # npm发布时需要

5.3 actions/cache

缓存依赖项或构建产物以缩短工作流执行时间。

- name: 恢复缓存
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

5.4 其他有用的Actions

# Python设置
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'

# Java设置
- uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'

# Docker构建和推送
- uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: user/app:latest

6. 实现测试自动化

6.1 基本测试工作流

这是一个在创建或更新PR时自动运行测试的工作流。

name: Test

on:
  pull_request:
    branches: [ main, develop ]
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: 设置Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 安装依赖
        run: npm ci

      - name: Lint检查
        run: npm run lint

      - name: 类型检查
        run: npm run type-check

      - name: 单元测试
        run: npm test -- --coverage

      - name: 测试覆盖率报告
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

6.2 测试结果报告

将测试结果作为评论留在PR上,审查者可以快速确认。

- name: 测试结果报告
  uses: dorny/test-reporter@v1
  if: success() || failure()
  with:
    name: Jest Tests
    path: junit.xml
    reporter: jest-junit

7. 部署自动化

7.1 GitHub Pages部署

将静态站点自动部署到GitHub Pages的工作流。

name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 设置Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 构建
        run: |
          npm ci
          npm run build

      - name: 上传Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: 部署GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

7.2 Vercel部署

自动部署到Vercel的配置。可以区分Preview和Production环境进行部署。

name: Deploy to Vercel

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装Vercel CLI
        run: npm install -g vercel@latest

      - name: 获取Vercel项目信息
        run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}

      - name: 构建
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}

      - name: 生产环境部署
        if: github.ref == 'refs/heads/main'
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

7.3 AWS S3 + CloudFront部署

name: Deploy to AWS

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 设置Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: 构建
        run: |
          npm ci
          npm run build

      - name: 配置AWS凭证
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: 上传到S3
        run: aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }} --delete

      - name: 使CloudFront缓存失效
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

8. Secrets管理

8.1 Secrets设置方法

API密钥、令牌等敏感信息绝对不能直接写在代码中。使用GitHub Secrets安全管理。

设置方法:

  1. 进入仓库Settings
  2. 在左侧菜单选择"Secrets and variables" -> "Actions"
  3. 点击"New repository secret"
  4. 输入名称和值后保存

8.2 Secrets使用示例

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 部署
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          echo "API密钥已设置"
          npm run deploy

8.3 按环境管理Secrets

使用Environments功能可以按staging、production等环境管理不同的secrets。

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: 部署到Staging
        run: deploy --env staging
        env:
          API_URL: ${{ secrets.API_URL }}  # staging环境的API_URL

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: 部署到Production
        run: deploy --env production
        env:
          API_URL: ${{ secrets.API_URL }}  # production环境的API_URL

9. Matrix构建

9.1 什么是Matrix策略?

使用Matrix构建可以在多个环境(操作系统、语言版本等)同时运行测试。例如,如果想在Node.js 18、20、22版本上都进行测试,不需要创建单独的job,只需用matrix定义即可。

name: Cross-version Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        exclude:
          - os: windows-latest
            node-version: 18
      fail-fast: false  # 即使一个失败,其余继续执行

    steps:
      - uses: actions/checkout@v4

      - name: 设置Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci
      - run: npm test

9.2 动态Matrix

也可以使用JSON动态构建matrix。

jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - id: set-matrix
        run: echo "matrix={\"include\":[{\"project\":\"api\"},{\"project\":\"web\"}]}" >> $GITHUB_OUTPUT

  build:
    needs: setup
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
    steps:
      - run: echo "Building ${{ matrix.project }}"

10. 实战工作流示例

10.1 完整的CI流水线

name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    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 run lint

  type-check:
    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 run type-check

  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 -- --coverage
      - uses: codecov/codecov-action@v3

  build:
    needs: [lint, type-check, 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 run build
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

10.2 自动发布工作流

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: 生成Release Notes
        uses: softprops/action-gh-release@v1
        with:
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

10.3 PR自动标签

name: PR Labeler

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

10.4 定期安全检查

name: Security Check

on:
  schedule:
    - cron: '0 9 * * 1'  # 每周一上午9点(UTC)
  workflow_dispatch:

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: 依赖安全检查
        run: npm audit --audit-level=high

      - name: 检查依赖更新
        run: npm outdated || true

      - name: Snyk安全扫描
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

总结:用CI/CD提高开发生产力

本文我们学习了使用GitHub Actions构建CI/CD流水线。起初配置文件编写可能感觉复杂,但一旦搭建好,之后就可以持续享受自动化的好处。

重要的是不要从一开始就追求完美的流水线。从简单的测试自动化开始,逐步扩展。我一开始也只是创建一个只运行npm test的工作流,根据需要逐渐添加lint、类型检查、部署自动化等。

下一篇第6篇将介绍如何参与开源贡献。Fork、PR工作流、贡献指南等,将详细讲解实际参与开源项目的方法。