FastAPI: CI/CD — GitHub Actions 自动化交付流水线

最后更新:2026-08-26

CI/CD 就像汽车工厂的自动化流水线——零件(代码)进来,经过焊接(lint)、质检(test)、喷涂(安全扫描),合格的成品(Docker 镜像)自动送到 4S 店(生产环境)。

1. 你将学到


2. Alice 的真实故事

(1) 痛点:手动部署经常出错

Bob 提交代码后,Alice 手动在服务器拉取代码、运行测试、构建镜像、重启容器。这个过程每次 30 分钟,而且经常遗漏测试步骤——有一次 Alice 忘了运行迁移,新端点在线上报 500 错误。Charlie 说他可以在 3 个环境(dev/staging/prod)分别手动操作,但风险太高。

(2) GitHub Actions 的解法

GitHub Actions 在每次 push/PR 时自动运行完整流水线:lint → test → build → deploy。所有步骤由代码定义,不可能遗漏,失败自动阻止合并。

(3) 收益

部署从手动 30 分钟变成自动 5 分钟,遗漏测试的问题彻底消失(PR 不通过测试无法合并),三个环境部署完全一致。


3. GitHub Actions 基础

(1) 核心概念

100%
flowchart LR
    Trigger[Trigger: push/PR] --> Workflow[Workflow]
    Workflow --> Job1[Job 1: Lint+Test]
    Workflow --> Job2[Job 2: Build]
    Job1 --> Step1[Step: ruff check]
    Job1 --> Step2[Step: pytest]
    Job2 --> Step3[Step: docker build]
    Job2 --> Step4[Step: docker push]
概念 说明 示例
Workflow 自动化流程定义 .github/workflows/ci.yml
Trigger 触发条件 push, pull_request
Job 一组步骤的执行单元 test, build, deploy
Step 单个操作 run: pytest
Action 可复用的操作 actions/checkout@v4
Runner 执行环境 ubuntu-latest

(2) 分支与环境映射

100%
graph TD
    Feature[feature/*] -->|PR| Develop[develop]
    Develop -->|Deploy| DevEnv[Dev Environment]
    Develop -->|PR| Main[main]
    Main -->|Deploy| Staging[Staging Environment]
    Main -->|Tag v*| Prod[Production Environment]
分支 环境 触发 部署方式
feature/* - PR 自动测试 不部署
develop Dev push 自动 Docker Compose
main Staging push 自动 Docker Compose
v* tag Production tag 手动 Docker Compose / K8s

4. CI 流水线

▶ 示例:完整 CI 工作流

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

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install UV
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run ruff lint
        run: uv run ruff check app/ tests/

      - name: Run ruff format check
        run: uv run ruff format --check app/ tests/

  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: pricetracker
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: pricetracker_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Install UV
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run pytest
        env:
          DATABASE_URL: postgresql+asyncpg://pricetracker:test_password@localhost:5432/pricetracker_test
          REDIS_URL: redis://localhost:6379/0
          SECRET_KEY: test-secret-key-for-ci
        run: uv run pytest tests/ -v --cov=app --cov-report=xml

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

  security:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Run safety check
        run: |
          pip install safety
          safety check --json || true

      - name: Run bandit
        run: |
          pip install bandit
          bandit -r app/ -f json || true

  build:
    runs-on: ubuntu-latest
    needs: [test, security]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: docker/Dockerfile
          push: false
          tags: pricetracker:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

输出:

ext CI/CD pipeline loaded Pipeline status: passed Tests: 12 passed, 0 failed


5. CD 流水线

▶ 示例:生产部署工作流

YAML
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: docker/Dockerfile
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.ref_name }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/pricetracker
            docker compose pull
            docker compose up -d --remove-orphans
            docker compose exec api alembic upgrade head
            echo "Deployed version ${{ github.ref_name }}"

输出:

TEXT 📖 仅展示
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

(2) CI/CD Pipeline 完整流程

100%
flowchart LR
    Push[Bob pushes code] --> Lint[Lint: ruff]
    Lint --> Test[Test: pytest]
    Lint --> Sec[Security: safety + bandit]
    Test --> Build[Docker Build]
    Sec --> Build
    Build -->|On tag| Push[Push to GHCR]
    Push --> Deploy[Deploy to Production]
    Deploy --> Health[Health Check]
    Health --> Done[✓ Live]

▶ 示例:分支保护与环境保护规则

YAML
# .github/workflows/branch-protection.yml
name: Branch Protection Check

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Verify PR target is main
        run: |
          if [ "${{ github.base_ref }}" != "main" ]; then
            echo "PR must target main branch"
            exit 1
          fi

      - name: Check environment approval
        if: github.event.pull_request.merged == true
        uses: octokit/request-action@v2
        with:
          route: POST /repos/{owner}/{repo}/deployments
          environment: staging
          required_reviewers: 1

输出:

TEXT 📖 仅展示
CI/CD pipeline loaded
Pipeline status: passed
Tests: 12 passed, 0 failed

❓ 常见问题

Q GitHub Actions 免费额度够用吗?
A 公开仓库无限制。私有仓库每月 2000 分钟免费,PriceTracker 的 CI 约 5 分钟/次,每天 10 次约 1500 分钟,够用。
Q 如何处理 CI 中的数据库迁移?
A CI 测试用 Base.metadata.create_all() 直接建表(不用 Alembic)。CD 部署脚本中运行 alembic upgrade head
Q Secrets 如何安全存储?
A GitHub 项目的 Settings → Secrets and variables → Actions。SERVER_SSH_KEYDB_PASSWORD 等存在这里,YAML 中用 ${{ secrets.XXX }} 引用。
Q 如何只对 main 分支部署?
Aon: push: tags: ['v*'] 只在打 tag 时触发部署。开发分支只跑测试不部署。
Q Docker 缓存怎么利用?
Acache-from: type=gha 利用 GitHub Actions 缓存。首次构建 5 分钟,后续改动只重建变化的层,约 1-2 分钟。
Q 部署失败怎么回滚?
A docker compose down + docker compose pull <previous-tag> + docker compose up -d。保留前一个版本的镜像标签便于回滚。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建 .github/workflows/ci.yml,在 push 和 PR 时自动运行 ruff checkpytest,验证 GitHub Actions 页面看到绿色通过。提示:on: push: branches: [main]
  2. 进阶题(难度⭐⭐):添加 PostgreSQL 和 Redis 服务容器到测试 Job,配置环境变量让 pytest 连接测试数据库,添加 Docker 构建步骤验证镜像可以成功构建。提示:services: + options: --health-cmd
  3. 挑战题(难度⭐⭐⭐):完整 CI/CD——CI 流水线含 lint + test + security + build,CD 流水线在 v* tag 时推送镜像到 GHCR 并 SSH 部署到服务器,配置 GitHub Secrets 存储密钥。提示:docker/login-action + appleboy/ssh-action + ${{ secrets.XXX }}

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏