本文是《Python工程化实践》专栏第十七章,讲解如何使用 GitHub Actions 实现 Python 项目的自动化测试、构建和部署。


1. 为什么需要 CI/CD

1.1 手动部署的痛苦

没有 CI/CD 之前:

# 每次发布都要手动执行一堆命令
git pull
pytest
docker build
docker push
docker pull 生产服务器
docker run
# 运气好,没问题
# 运气不好,凌晨两点修 bug

手动部署的问题:

  • 容易忘记步骤
  • 不同机器环境不一致
  • 回滚困难
  • 团队协作时谁都不敢部署

1.2 CI/CD 解决什么问题

CI = Continuous Integration(持续集成)
每次代码提交后自动运行测试,确保代码质量

CD = Continuous Deployment(持续部署)
测试通过后自动部署到服务器

代码提交 → 自动测试 → 自动构建 → 自动部署
    ↑                                      ↓
    └────────── 出问题自动回滚 ←───────────┘

2. GitHub Actions 基础

2.1 核心概念

概念说明
Workflow(工作流)整个自动化流程,定义在 .github/workflows/ 目录
Job(任务)一个 Workflow 包含多个 Job
Step(步骤)每个 Job 包含多个 Step
Action(动作)Step 中的具体操作,可复用
Runner执行 Job 的服务器

2.2 目录结构

myproject/
├── .github/
│   └── workflows/
│       ├── ci.yml      # 持续集成
│       └── release.yml  # 发布流程
├── app/
├── tests/
├── Dockerfile
└── requirements.txt

2.3 最小 workflow 示例

# .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.11'
      - run: pip install -r requirements.txt
      - run: pytest

3. 自动化测试

3.1 完整测试 workflow

name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置 Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'  # 自动缓存依赖
      
      - name: 安装依赖
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov
      
      - name: 运行测试
        run: pytest --cov=app --cov-report=xml
      
      - name: 上传覆盖率报告
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

3.2 多任务并行

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install ruff
      - run: ruff check .
  
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install mypy
      - run: mypy app
  
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest

3.3 任务依赖关系

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install ruff
      - run: ruff check .
  
  test:
    needs: lint  # 等 lint 完成后才运行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: pytest
  
  build:
    needs: test  # 等 test 完成后才运行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t myapp:${{ github.sha }} .

4. 自动发布

4.1 PyPI 自动发布

name: 发布到 PyPI

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置 Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      
      - name: 安装构建工具
        run: pip install build
      
      - name: 构建包
        run: python -m build
      
      - name: 发布到 PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}

获取 PyPI API Token:

  1. 登录 PyPI.org
  2. Account Settings → API tokens
  3. 创建 Token,复制到 GitHub 的 Secrets

4.2 Docker Hub 自动发布

name: 发布 Docker 镜像

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 提取版本号
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      - name: 登录 Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: 构建镜像
        run: |
          docker build \
            --tag myapp:${{ env.VERSION }} \
            --tag myapp:latest \
            .
      
      - name: 推送镜像
        run: |
          docker push myapp:${{ env.VERSION }}
          docker push myapp:latest

5. 版本管理策略

5.1 语义化版本

主版本.次版本.修订号
  1    .  2   .  3
变化示例说明
主版本1.x.x → 2.0.0不兼容的 API 变更
次版本1.2.x → 1.3.0新增功能(向后兼容)
修订号1.2.3 → 1.2.4Bug 修复

5.2 自动生成 Release

name: 创建 Release

on:
  push:
    tags:
      - 'v*'

jobs:
  create-release:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 提取版本号
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      - name: 创建 Release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/*  # 上传构建产物
          generate_release_notes: true  # 自动生成更新日志
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

5.3 CHANGELOG 自动生成

推荐使用 git-cliff 自动生成更新日志:

# 安装
- name: 安装 git-cliff
  run: |
    FILE=git-cliff-2.1.0-x86_64-unknown-linux-musl.tar.gz
    wget https://github.com/orhun/git-cliff/releases/download/v2.1.0/$FILE
    tar -xzf $FILE && sudo mv cliff /usr/local/bin/

# 生成 CHANGELOG
- name: 生成更新日志
  run: git-cliff --output CHANGELOG.md

6. 秘密管理

6.1 GitHub Secrets

不要把敏感信息写进代码!

# 错误示例
env:
  DATABASE_URL: postgres://user:password@host/db  # ❌ 泄露!

# 正确示例
env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}  # ✅ 从 Secrets 读取

6.2 添加 Secrets

  1. 进入 GitHub 仓库 → Settings → Secrets and variables → Actions
  2. 点击 “New repository secret”
  3. 填写 Name 和 Value

6.3 环境级别的 Secrets

jobs:
  test:
    runs-on: ubuntu-latest
    environment: test  # 使用 test 环境的 Secrets
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
  
  deploy:
    runs-on: ubuntu-latest
    environment: production  # 使用 production 环境的 Secrets
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}

7. 缓存优化

7.1 pip 依赖缓存

- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'
    cache-dependency-path: requirements.txt

7.2 Docker 层缓存

- name: 构建镜像
  uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

7.3 完整优化示例

name: CI

on:
  push:
    branches: [main]

jobs:
  optimized-test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: 设置 Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
          cache-dependency-path: requirements.txt
      
      - name: 缓存 ruff
        uses: actions/cache@v4
        with:
          path: ~/.cache/ruff
          key: ruff-${{ hashFiles('requirements.txt') }}
      
      - name: 安装依赖
        run: pip install -r requirements.txt pytest
      
      - name: lint
        run: ruff check .
      
      - name: 测试
        run: pytest -v

8. 条件执行

8.1 根据分支条件触发

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

8.2 根据文件变化触发

on:
  push:
    paths:
      - '**.py'       # Python 文件变化才触发
      - 'requirements.txt'
      - '.github/workflows/**'

8.3 手动触发

on:
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
# 手动触发时使用输入参数
- name: 部署到 ${{ github.event.inputs.environment }}
  run: deploy.sh ${{ github.event.inputs.environment }}

9. 常见问题

9.1 超时问题

jobs:
  test:
    timeout-minutes: 30  # 设置超时时间
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ...

9.2 并发控制

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # 取消正在运行的同名 workflow

9.3 自定义 Runner

如果 GitHub 提供的 Ubuntu/Windows/macOS 不够用,可以搭建自托管 Runner:

jobs:
  test:
    runs-on: self-hosted  # 使用自托管 runner
    labels:
      - linux
      - large

10. 完整示例

一个完整的 Python 项目的 CI/CD workflow:

name: Python CI/CD

on:
  push:
    branches: [main]
    tags: ['v*']
  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-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      - run: pip install ruff
      - run: ruff check .

  test:
    needs: lint
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
      - run: pip install -r requirements.txt
      - run: pytest --cov=app

  build:
    needs: test
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4
      - name: 登录 Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: 构建并推送
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker tag myapp:${{ github.sha }} myapp:latest
          docker push myapp:latest

11. 总结

这一章我们介绍了 GitHub Actions CI/CD 的核心要素:

  • Workflow:定义自动化流程的 YAML 文件
  • 自动化测试:push/PR 时自动运行测试,多版本并行
  • 自动发布:发布时自动构建并推送到 PyPI/Docker Hub
  • 版本管理:语义化版本 + 自动 Release
  • 秘密管理:敏感信息存 Secrets,不写进代码
  • 缓存优化:减少构建时间
  • 条件执行:按需触发 workflow

GitHub Actions 让你的代码在每次提交后都经历完整的质量检查和部署流程,再也不用担心"在我的机器上能跑"的问题了。

预告:附录篇我们将整理 Python 工程化的工具速查清单,帮助大家快速检索常用命令和配置。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐