Skip to content

GitHub Actions 实用指南

GitHub Actions 是 GitHub 提供的强大持续集成/持续部署(CI/CD)工具,允许您直接在 GitHub 仓库中自动化软件开发工作流程。无需搭建专用的 CI/CD 服务器,您可以轻松实现代码构建、测试和部署等自动化流程。本文将全面介绍 GitHub Actions 的核心概念、配置方法和实际应用场景。

1. GitHub Actions 基础

1.1 核心概念

GitHub Actions 的主要组成部分:

  • 工作流(Workflow):自动化流程,由一个或多个作业组成,由事件触发
  • 事件(Event):触发工作流的特定活动,如 push、pull request、定时任务等
  • 作业(Job):工作流中的一系列步骤,在同一运行器上执行
  • 步骤(Step):可以运行命令或 action 的单个任务
  • 动作(Action):可重用的工作单元,是构建工作流的基本模块
  • 运行器(Runner):执行工作流的服务器,GitHub 提供托管的运行器,也支持自托管

1.2 工作流文件结构

GitHub Actions 使用 YAML 文件定义工作流,存储在仓库的 .github/workflows 目录下。基本结构如下:

yaml
name: CI/CD Pipeline

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test

1.3 事件触发器

GitHub Actions 可以由多种事件触发:

代码相关事件:

yaml
on:
  push:
    branches: [ main, dev ]
    paths-ignore:
      - '**.md'
  pull_request:
    types: [opened, synchronize, reopened]

计划事件:

yaml
on:
  schedule:
    - cron: '0 0 * * *'  # 每天午夜执行

手动触发:

yaml
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        default: 'staging'

其他事件触发:

yaml
on:
  release:
    types: [published]
  issues:
    types: [opened]
  repository_dispatch:
    types: [deploy]

2. 创建基本工作流

2.1 构建与测试

以下是一个基本的 Node.js 项目构建与测试工作流:

yaml
name: Node.js CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x, 16.x, 18.x]
    
    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test

2.2 多平台测试

使用矩阵策略在多个平台上测试:

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

2.3 构建与发布 Docker 镜像

yaml
name: Docker Build & Push

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    
    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v3
      with:
        context: .
        push: true
        tags: |
          user/app:latest
          user/app:${{ github.sha }}

3. 高级配置技巧

3.1 使用环境变量与密钥

在工作流中使用环境变量和密钥:

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      APP_ENV: production
      LOG_LEVEL: info
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Deploy to server
      env:
        NODE_ENV: production
      run: |
        echo "Using APP_ENV: $APP_ENV"
        echo "Using NODE_ENV: $NODE_ENV"
    
    - name: Access secrets
      run: |
        echo ${{ secrets.API_KEY }} | base64 --decode > api_key.json

3.2 作业依赖与并行执行

控制作业的执行顺序:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run build
  
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm test
  
  deploy:
    needs: [build, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run deploy

3.3 条件执行

根据条件决定是否执行步骤:

yaml
steps:
  - name: Deploy to production
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    run: npm run deploy:prod
  
  - name: Deploy to staging
    if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
    run: npm run deploy:staging
  
  - name: Only on pull requests
    if: github.event_name == 'pull_request'
    run: echo "This is a PR!"

3.4 工作流的复用

使用可重用工作流来减少重复代码:

yaml
# .github/workflows/reusable-workflow.yml
name: Reusable workflow

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      deploy_key:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to ${{ inputs.environment }}
        run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.deploy_key }}

调用可重用工作流:

yaml
# .github/workflows/caller-workflow.yml
name: Production deployment

on:
  push:
    branches: [ main ]

jobs:
  call-deploy-workflow:
    uses: ./.github/workflows/reusable-workflow.yml
    with:
      environment: production
    secrets:
      deploy_key: ${{ secrets.PROD_DEPLOY_KEY }}

4. 存储与共享数据

4.1 工件(Artifacts)

在作业之间共享数据:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: dist/
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: dist
      
      - name: Deploy
        run: ./deploy.sh

4.2 缓存依赖

加速工作流执行:

yaml
steps:
  - uses: actions/checkout@v3
  
  - name: Cache Node.js modules
    uses: actions/cache@v3
    with:
      path: ~/.npm
      key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-node-
  
  - name: Install dependencies
    run: npm ci

5. 实用工作流示例

5.1 前端项目工作流

一个完整的前端项目工作流:

yaml
name: Frontend CI/CD

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

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - 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: Lint
        run: npm run lint
      
      - name: Type check
        run: npm run typecheck
  
  test:
    runs-on: ubuntu-latest
    needs: validate
    steps:
      - 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: Upload coverage reports
        uses: codecov/codecov-action@v3
  
  build:
    runs-on: ubuntu-latest
    needs: test
    if: success() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
    steps:
      - 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: Build
        run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: dist/
  
  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v3
      
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: dist
      
      - name: Deploy to staging
        if: github.ref == 'refs/heads/dev'
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_STAGING }}
          projectId: my-app-staging
          channelId: live
      
      - name: Deploy to production
        if: github.ref == 'refs/heads/main'
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_PROD }}
          projectId: my-app-prod
          channelId: live

5.2 后端项目工作流

Java Spring Boot项目的CI/CD工作流:

yaml
name: Java CI/CD

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

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: maven
    
    - name: Build with Maven
      run: mvn -B package --file pom.xml
    
    - name: Run tests
      run: mvn test
    
    - name: Build Docker image
      if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
      run: |
        docker build -t myapp:${{ github.sha }} .
        docker tag myapp:${{ github.sha }} myapp:latest
    
    - name: Login to Docker Registry
      if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
      uses: docker/login-action@v2
      with:
        registry: ${{ secrets.DOCKER_REGISTRY }}
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
    - name: Push Docker image
      if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
      run: |
        docker push myapp:${{ github.sha }}
        docker push myapp:latest
    
    - name: Deploy to Development
      if: github.ref == 'refs/heads/dev'
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.DEV_HOST }}
        username: ${{ secrets.SSH_USERNAME }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          docker pull myapp:latest
          docker-compose down
          docker-compose up -d
    
    - name: Deploy to Production
      if: github.ref == 'refs/heads/main'
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.PROD_HOST }}
        username: ${{ secrets.SSH_USERNAME }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          docker pull myapp:latest
          docker-compose down
          docker-compose up -d

5.3 发布流程

自动创建和发布新版本:

yaml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false
      
      - name: Publish to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

6. GitHub Actions 的高级特性

6.1 自托管运行器

对于需要特定环境或硬件资源的工作流,可以设置自托管运行器:

yaml
jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v3
      - name: Deploy on local server
        run: ./deploy.sh

设置自托管运行器的步骤:

  1. 在 GitHub 仓库中,转到 Settings > Actions > Runners
  2. 点击 "New self-hosted runner"
  3. 按照指示在服务器上设置运行器
  4. 启动运行器服务

6.2 工作流的限制与超时

设置工作流的超时时间:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v3
      - name: Long running task
        run: ./long-process.sh

6.3 环境与审批流程

为关键部署设置环境和审批流程:

yaml
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://production.example.com
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to production
        run: ./deploy-prod.sh

在 GitHub 中,可以为环境配置保护规则和所需的审批者。

7. 最佳实践

7.1 安全最佳实践

  • 使用最小权限原则:为 Actions 分配最小必要的权限
  • 谨慎使用第三方 Actions:使用官方或受信任的 Actions
  • 不要硬编码密钥:使用 GitHub Secrets 存储敏感信息
  • 对环境访问加限制:设置环境保护规则
  • 定期审核工作流文件:确保没有引入安全风险

7.2 性能优化

  • 减少依赖安装时间:使用缓存
  • 并行执行独立任务:使用多个作业
  • 减少不必要的步骤:使用条件执行
  • 使用矩阵构建高效测试:同时测试多个环境/版本
  • 自托管运行器适用场景:需要特定硬件或持久环境时

7.3 维护与调试

  • 使用具有描述性的名称:为工作流、作业和步骤提供清晰名称
  • 模块化工作流:使用可重用工作流拆分复杂流程
  • 适当的日志级别:输出足够的信息以便调试
  • 使用注释和文档:记录复杂的工作流配置
  • 版本控制工作流文件:将工作流文件视为代码管理

8. 常见问题与解决方案

8.1 故障排除技巧

  • 查看详细日志:在 GitHub Actions 页面查看完整日志
  • 使用调试环境变量:设置 ACTIONS_RUNNER_DEBUG=trueACTIONS_STEP_DEBUG=true
  • 本地测试:使用 act 工具在本地测试工作流
  • 增量修复:先获取最小工作流,然后逐步添加复杂性
  • 审核工作流语法:检查 YAML 格式错误

8.2 常见错误

  1. 权限问题

    解决方案:检查 token 权限,使用 GITHUB_TOKEN 或创建具有适当权限的 PAT
  2. 环境设置错误

    解决方案:确保所需环境变量已正确设置,检查 secrets 是否已配置
  3. 依赖问题

    解决方案:锁定依赖版本,使用缓存加速安装,检查依赖冲突
  4. 运行器资源限制

    解决方案:优化资源使用,考虑使用自托管运行器,将工作流拆分为多个作业

总结

GitHub Actions 提供了一个强大且灵活的自动化平台,可以满足从简单的 CI 到复杂的 CD 流程的各种需求。通过精心设计的工作流,您可以减少手动操作,提高代码质量,加快发布速度。

GitHub Actions 的优势在于与 GitHub 仓库的紧密集成,丰富的生态系统,以及易于使用的配置语法。无论是个人项目还是企业级应用,GitHub Actions 都可以帮助您实现高效的自动化工作流程。

随着持续集成和持续部署实践的普及,掌握 GitHub Actions 将成为开发者工具箱中的重要技能。通过应用本文介绍的概念和最佳实践,您可以充分利用 GitHub Actions 提升您的开发效率和产品质量。