强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

24 - CI/CD

第 24 章:CI/CD

使用 GitHub Actions 构建自动化 CI/CD 流水线。


24.1 CI/CD 概述

概念含义阶段
CI (Continuous Integration)代码合并后自动构建和测试代码推送 → 测试
CD (Continuous Delivery)自动构建、测试并准备发布测试 → 部署就绪
CD (Continuous Deployment)自动部署到生产环境部署就绪 → 上线
代码推送 → Lint → 测试 → 构建 → 部署

24.2 GitHub Actions

24.2.1 基本配置

# .github/workflows/ci.yml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"

      - name: Lint with ruff
        run: |
          ruff check src/ tests/
          ruff format --check src/ tests/

      - name: Type check with mypy
        run: mypy src/

      - name: Test with pytest
        run: pytest tests/ -v --cov=src --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

24.2.2 触发条件

on:
  push:
    branches: [main, "release/*"]
    tags: ["v*"]
    paths:
      - "src/**"
      - "tests/**"
      - "pyproject.toml"
  pull_request:
    branches: [main]
  schedule:
    - cron: "0 0 * * 1"  # 每周一 UTC 0:00
  workflow_dispatch:  # 手动触发

24.3 代码质量工具

24.3.1 Ruff(Lint + Format)

# pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = [
    "E",    # pycodestyle errors
    "F",    # pyflakes
    "I",    # isort
    "N",    # pep8-naming
    "UP",   # pyupgrade
    "B",    # flake8-bugbear
    "SIM",  # flake8-simplify
    "TCH",  # type checking imports
    "RUF",  # ruff-specific
]
ignore = ["E501"]  # 行长度由 formatter 处理

[tool.ruff.lint.isort]
known-first-party = ["myproject"]
# 检查
$ ruff check src/ tests/

# 自动修复
$ ruff check --fix src/

# 格式化
$ ruff format src/

# 检查格式(不修改)
$ ruff format --check src/

24.3.2 Mypy(类型检查)

# pyproject.toml
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
$ mypy src/

24.3.3 Pre-commit

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.10.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]
# 安装
$ pip install pre-commit
$ pre-commit install

# 手动运行
$ pre-commit run --all-files

24.4 完整 CI/CD 流水线

# .github/workflows/ci-cd.yml
name: CI/CD

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install ruff mypy
      - run: ruff check src/ tests/
      - run: ruff format --check src/ tests/
      - run: mypy src/

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -e ".[dev]"
      - run: pytest tests/ -v --cov=src

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install build
      - run: python -m build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  publish:
    needs: build
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Trusted publishing
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: pypa/gh-action-pypi-publish@release/v1

24.5 自动化发布

# 自动创建 GitHub Release
  release:
    needs: build
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: softprops/action-gh-release@v2
        with:
          files: dist/*
          generate_release_notes: true

24.6 Docker CI

  docker:
    needs: test
    runs-on: ubuntu-latest
    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@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

24.7 缓存优化

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip  # 自动缓存 pip 包

      # 或手动缓存
      - uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}

24.8 注意事项

🔴 注意

  • 不要在 CI 中使用 pip install .(不带 -e),会破坏覆盖率
  • 使用 --check 模式运行 lint 和 format 检查
  • 敏感信息使用 GitHub Secrets
  • 使用 Trusted Publishing 发布到 PyPI

💡 提示

  • Ruff 替代 Black + isort + Flake8,速度快 100 倍
  • 使用 matrix 测试多个 Python 版本
  • 使用 actions/cache 加速依赖安装
  • 使用 pre-commit 在本地拦截问题

📌 业务场景

完整的 Python 项目 CI/CD 流程:

1. 开发者推送代码
2. GitHub Actions 触发 CI
3. Lint 检查 (Ruff)
4. 类型检查 (Mypy)
5. 单元测试 (pytest)
6. 构建分发包
7. 推送标签 → 自动发布到 PyPI
8. 推送标签 → 自动构建 Docker 镜像
9. 部署到生产环境

24.9 扩展阅读