Python 编程教程 / 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. 部署到生产环境