使用GitHub Actions实现CI/CD

GitHub Actions是GitHub内置的CI/CD工具,配置简单、与GitHub生态深度集成。对于托管在GitHub上的项目,基本上是CI/CD的首选方案。这篇文章介绍Actions的核心概念和常见配置模式。

基本概念

GitHub Actions的几个核心概念:

  • Workflow(工作流):一个自动化流程,定义在.github/workflows/目录下的YAML文件中
  • Event(事件):触发workflow的条件,比如push、pull_request、定时等
  • Job(作业):workflow中的一组步骤,运行在同一个runner上
  • Step(步骤):job中的单个任务,可以执行命令或调用action
  • Action(动作):可复用的步骤单元,社区有大量现成的action可用

第一个Workflow

在项目根目录创建.github/workflows/ci.yml

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

推送到GitHub后,每次push到main/develop分支或者提PR到main时,这个workflow就会自动运行。

触发事件

常用的触发事件:

on:
  # 代码推送
  push:
    branches: [ main ]
    tags: [ 'v*' ]            # tag推送时触发
    paths:
      - 'src/**'              # 只有src目录变更才触发
      - '!src/**/*.md'        # 排除md文件

  # Pull Request
  pull_request:
    types: [ opened, synchronize, reopened ]

  # 定时任务(cron语法)
  schedule:
    - cron: '0 2 * * 1'       # 每周一凌晨2点

  # 手动触发
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

Job配置

多Job并行

默认情况下,多个job是并行执行的:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm test

  build:
    needs: [lint, test]     # 等lint和test都通过后再build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run build

矩阵策略

同时在多个环境/版本上测试:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [14, 16, 18]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

这个配置会生成 3x3=9 个job并行跑。

环境变量和Secrets

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      NODE_ENV: production
    steps:
      - name: Deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          SERVER_HOST: ${{ secrets.SERVER_HOST }}
        run: |
          echo "Deploying to $SERVER_HOST"
          # 使用ssh部署等操作

Secrets在GitHub仓库的Settings > Secrets中配置,日志中会自动打码。

常用Actions

社区有大量现成的action,不用重复造轮子:

# 缓存依赖,加速构建
- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

# Docker构建并推送
- uses: docker/build-push-action@v4
  with:
    push: true
    tags: myapp:latest

# 发送通知
- uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {"text": "Deployment succeeded!"}

实际案例:前端项目CI/CD

一个比较完整的前端项目配置:

name: Frontend CI/CD

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

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage
      - run: npm run build

      - name: Upload coverage
        if: github.event_name == 'push'
        uses: codecov/codecov-action@v3

      - name: Upload build artifacts
        if: github.ref == 'refs/heads/main'
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/

  deploy:
    needs: ci
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: dist
          path: dist/

      - name: Deploy to server
        uses: appleboy/scp-action@v0.1.4
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: "dist/*"
          target: "/var/www/app"

小结

GitHub Actions的优势在于跟GitHub无缝集成,社区action生态丰富。配置上就是YAML文件,理解了event、job、step这几个概念,基本上就能覆盖大部分CI/CD需求。比起自建Jenkins,省心不少。