FastAPI: CI/CD — GitHub Actions 自动化交付流水线
最后更新:2026-08-26
CI/CD 就像汽车工厂的自动化流水线——零件(代码)进来,经过焊接(lint)、质检(test)、喷涂(安全扫描),合格的成品(Docker 镜像)自动送到 4S 店(生产环境)。
1. 你将学到
- GitHub Actions 基础:Workflow / Job / Step / Action 概念
- CI 流水线:lint → pytest → 安全扫描 → Docker 构建
- CD 流水线:镜像推送 → Docker Hub/GHCR → 服务器部署
- 环境管理:dev / staging / production 的分支策略
- Alice 场景:Bob 提交 PR → 自动测试 → Charlie merge → 自动部署
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) 核心概念
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) 分支与环境映射
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 完整流程
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_KEY、DB_PASSWORD 等存在这里,YAML 中用 ${{ secrets.XXX }} 引用。Q 如何只对 main 分支部署?
A 用
on: push: tags: ['v*'] 只在打 tag 时触发部署。开发分支只跑测试不部署。Q Docker 缓存怎么利用?
A 用
cache-from: type=gha 利用 GitHub Actions 缓存。首次构建 5 分钟,后续改动只重建变化的层,约 1-2 分钟。Q 部署失败怎么回滚?
A
docker compose down + docker compose pull <previous-tag> + docker compose up -d。保留前一个版本的镜像标签便于回滚。📖 小节
- GitHub Actions 由 Workflow → Job → Step → Action 四级结构组成
- CI 流水线:lint(ruff)→ test(pytest + PostgreSQL/Redis 服务容器)→ security → build
- CD 流水线:tag 触发 → 构建 Docker 镜像 → 推送 GHCR → SSH 部署到服务器
- 分支策略:feature PR 测试,develop 部署 Dev,main 部署 Staging,v* tag 部署 Production
- Docker 层缓存 + GitHub Actions 缓存加速构建,Secrets 安全管理密钥
📝 作业
- 基础题(难度⭐):创建
.github/workflows/ci.yml,在 push 和 PR 时自动运行ruff check和pytest,验证 GitHub Actions 页面看到绿色通过。提示:on: push: branches: [main] - 进阶题(难度⭐⭐):添加 PostgreSQL 和 Redis 服务容器到测试 Job,配置环境变量让 pytest 连接测试数据库,添加 Docker 构建步骤验证镜像可以成功构建。提示:
services:+options: --health-cmd - 挑战题(难度⭐⭐⭐):完整 CI/CD——CI 流水线含 lint + test + security + build,CD 流水线在 v* tag 时推送镜像到 GHCR 并 SSH 部署到服务器,配置 GitHub Secrets 存储密钥。提示:
docker/login-action+appleboy/ssh-action+${{ secrets.XXX }}
---|