FastAPI: 项目部署与上线 — 从开发到生产
最后更新:2026-08-26
上线就像火箭发射——前 24 课是设计和制造,这一课是倒计时和发射。发射前有一长串检查清单,每项打勾才能点火。没有检查清单的发射,叫赌博。
1. 你将学到
- 生产环境 Checklist:安全、性能、监控三维度检查
- 灰度发布策略:蓝绿部署 / 金丝雀发布的实现方案
- 数据库迁移安全:零停机迁移的 Alembic 最佳实践
- 日志聚合:结构化日志 + ELK / Loki 集成
- Alice 场景终章:PriceTracker 正式上线
2. Alice 的真实故事
(1) 痛点:上线事故频发
PriceTracker 前两次上线都出了事故:第一次忘记配 HTTPS,第二次数据库迁移锁表导致服务中断 10 分钟,第三次日志没聚合,出问题后 Charlie 花 2 小时在 5 台服务器上翻日志。Alice 需要一份系统化的上线流程,确保每次上线安全可控。
(2) 上线 Checklist 的解法
上线不是"代码推上去就完事",而是一系列检查清单:安全加固、性能验证、迁移策略、灰度发布、监控确认——每一步打勾后才进入下一步。
(3) 收益
第三次上线 Checklist 全部打勾,HTTPS 配好、迁移零停机、灰度发布先切 10% 流量验证、日志自动聚合到 Loki——上线全程 0 事故,Charlie 的监控大屏全程绿灯。
3. 生产环境 Checklist
(1) 上线流程
flowchart TD
A[Code Freeze] --> B[Security Audit]
B --> C[Load Test]
C --> D[Staging Deploy]
D --> E[Smoke Test]
E --> F[Canary Release 10%]
F --> G{Metrics OK?}
G -->|Yes| H[Full Rollout 100%]
G -->|No| I[Rollback]
I --> J[Investigate]
J --> A
H --> K[Monitor 1h]
K --> L[✓ Live]
(2) 三维度 Checklist
| 维度 | 检查项 | 状态 |
|---|---|---|
| 安全 | HTTPS 证书配置 | ☐ |
| CORS 仅允许生产域名 | ☐ | |
| Rate Limiting 启用 | ☐ | |
| JWT SECRET_KEY 已更换 | ☐ | |
| 无硬编码密钥 | ☐ | |
| 安全扫描通过 | ☐ | |
| 性能 | Uvicorn Workers ≥ 4 | ☐ |
| DB 连接池大小合理 | ☐ | |
| Redis 缓存启用 | ☐ | |
| GZip 压缩启用 | ☐ | |
| 负载测试通过(目标 QPS) | ☐ | |
| 监控 | /health 端点正常 | ☐ |
| Prometheus 采集指标 | ☐ | |
| Grafana Dashboard 就绪 | ☐ | |
| 告警规则配置 | ☐ | |
| 日志聚合运行 | ☐ |
4. 灰度发布策略
(1) 蓝绿部署
flowchart LR
LB[Load Balancer] -->|100% traffic| Blue[Blue v1]
subgraph Deploy
Green[Green v2]
end
LB -.->|Switch| Green
style Blue fill:#4caf50
style Green fill:#2196f3
| 步骤 | 操作 | 回滚 |
|---|---|---|
| 1 | 部署 Green(v2)环境 | - |
| 2 | 在 Green 运行冒烟测试 | - |
| 3 | LB 切换到 Green | 切回 Blue |
| 4 | 监控 Green 30 分钟 | 切回 Blue |
| 5 | 确认稳定,下线 Blue | - |
▶ 示例:Nginx 蓝绿配置
NGINX
# nginx/conf.d/pricetracker.conf
upstream pricetracker_blue {
server api-blue:8000;
}
upstream pricetracker_green {
server api-green:8000;
}
# Currently serving Blue
server {
listen 80;
server_name api.pricetracker.example.com;
location / {
proxy_pass http://pricetracker_blue;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Switch to Green by changing proxy_pass target
# Then: docker compose -f docker-compose.green.yml up -d
输出:
TEXT
📖 仅展示
// 执行成功
(2) 金丝雀发布
| 策略 | 流量分配 | 观察时间 | 回滚 |
|---|---|---|---|
| 10% | 1 v2 + 9 v1 | 15 min | 切回 v1 |
| 30% | 3 v2 + 7 v1 | 15 min | 切回 v1 |
| 50% | 5 v2 + 5 v1 | 15 min | 切回 v1 |
| 100% | 全部 v2 | 30 min | 回滚镜像版本 |
5. 零停机数据库迁移
(1) 安全迁移原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 只加不删 | 先加新列,不删旧列 | ALTER TABLE ADD COLUMN new_col |
| 双写兼容 | 新旧代码都能工作 | 新代码写两列,旧代码只读旧列 |
| 延迟清理 | 确认稳定后再删旧列 | 部署 v2 后 7 天删旧列 |
| 小步迁移 | 每次只改一个东西 | 不在一个迁移中又加列又改类型 |
▶ 示例:安全添加列迁移
PYTHON
# alembic/versions/xxx_add_wholesale_price.py
"""Add wholesale_price column to products
Safe migration: ADD COLUMN is non-blocking in PostgreSQL
"""
def upgrade():
op.add_column(
"products",
sa.Column("wholesale_price", sa.Float(), nullable=True),
)
# Set default for existing rows (separate statement for performance)
op.execute("UPDATE products SET wholesale_price = base_price * 0.6 WHERE wholesale_price IS NULL")
# Make non-nullable in next migration after code deployed
def downgrade():
op.drop_column("products", "wholesale_price")
输出:
TEXT
📖 仅展示
# 函数定义成功
(2) 危险迁移 vs 安全迁移
| 迁移类型 | 安全性 | 锁表风险 | 建议 |
|---|---|---|---|
| ADD COLUMN | 安全 | 无 | 可在线执行 |
| CREATE INDEX | 较安全 | 可能 | 用 CREATE INDEX CONCURRENTLY |
| DROP COLUMN | 危险 | 有 | 先确保代码不再使用 |
| ALTER COLUMN TYPE | 危险 | 高 | 分步迁移(加新列→迁移数据→删旧列) |
| RENAME COLUMN | 危险 | 中 | 双写兼容后再改名 |
6. 日志聚合
(1) 结构化日志
▶ 示例:结构化日志配置
PYTHON
# app/core/logging.py
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno,
}
# Add request context if available
if hasattr(record, "request_id"):
log_entry["request_id"] = record.request_id
if hasattr(record, "user_id"):
log_entry["user_id"] = record.user_id
return json.dumps(log_entry)
# Configure logging
logger = logging.getLogger("pricetracker")
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
输出:
TEXT
📖 仅展示
# 函数定义成功
(2) 日志聚合方案对比
| 方案 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| ELK(Elasticsearch+Logstash+Kibana) | 功能全、搜索强 | 资源消耗大 | 大型项目 |
| Loki+Grafana+Promtail | 轻量、与 Grafana 一体 | 搜索能力弱 | 中小项目 |
| CloudWatch Logs | 零运维 | AWS 锁定 | AWS 部署 |
▶ 示例:Docker Compose 添加 Loki
YAML
# Add to docker-compose.yml
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
volumes:
- loki_data:/loki
promtail:
image: grafana/promtail:latest
volumes:
- /var/log:/var/log:ro
- ./docker/promtail.yml:/etc/promtail/config.yml
depends_on:
- loki
输出:
TEXT
📖 仅展示
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
7. PriceTracker 正式上线
(1) 项目完成里程碑
gantt
title PriceTracker Development Timeline
dateFormat YYYY-MM-DD
section Phase 1
FastAPI Intro :p1a, 2026-01-01, 1d
Installation & UV :p1b, after p1a, 1d
Path & Query Params :p1c, after p1b, 1d
Request Body & Pydantic :p1d, after p1c, 1d
Response Models :p1e, after p1d, 1d
Phase 1 Capstone :p1f, after p1e, 2d
section Phase 2
Middleware :p2a, after p1f, 1d
Dependency Injection :p2b, after p2a, 1d
Database SQLAlchemy :p2c, after p2b, 2d
CRUD Operations :p2d, after p2c, 1d
Authentication JWT :p2e, after p2d, 2d
Phase 2 Capstone :p2f, after p2e, 2d
section Phase 3
WebSocket :p3a, after p2f, 1d
Celery :p3b, after p3a, 2d
File Upload :p3c, after p3b, 1d
Testing :p3d, after p3c, 1d
Caching :p3e, after p3d, 1d
OpenAPI :p3f, after p3e, 1d
section Phase 4
Docker :p4a, after p3f, 1d
Performance :p4b, after p4a, 1d
Monitoring :p4c, after p4b, 1d
CI/CD :p4d, after p4c, 1d
section Phase 5
Project Design :p5a, after p4d, 1d
Project Development :p5b, after p5a, 2d
Project Deployment :p5c, after p5b, 1d
(2) 上线验证清单
| 验证项 | 验证方式 | 通过标准 |
|---|---|---|
| API 可用性 | curl /health |
{"status": "healthy"} |
| 前端对接 | Bob 前端调 API | 全部端点正常 |
| 认证 | JWT 登录+受保护端点 | 登录成功,未认证返回 401 |
| 权限 | Free/Pro/Enterprise 限流 | 按计划正确限制 |
| WebSocket | 连接+价格推送 | 实时收到变动 |
| 缓存 | 热门商品缓存命中率 | > 80% |
| 监控 | Grafana Dashboard | 指标正常显示 |
| 告警 | 模拟 P99 飙升 | 告警触发通知 |
| 日志 | 查询 Loki | 结构化日志可搜索 |
| CI/CD | Tag 推送触发部署 | 自动部署成功 |
❓ 常见问题
Q 蓝绿部署需要双倍服务器吗?
A 是的,切换期间需要两套环境。K8s 环境可以用滚动更新替代,不需要双倍资源。
Q 金丝雀发布如何分配流量?
A Nginx 用
weight 参数(server v2:8000 weight=1; server v1:8000 weight=9;),K8s 用 canary Deployment 调整副本数。Q 数据库迁移锁表怎么办?
A 用
CREATE INDEX CONCURRENTLY(不锁表建索引),避免 DDL 在高峰期执行。大表迁移分批进行。Q 结构化日志和普通日志有什么区别?
A 结构化日志是 JSON 格式,每条日志有固定字段(timestamp, level, message, request_id),机器可解析可搜索。普通日志是人读的文本,机器难以解析。
Q 上线后第一小时应该看什么?
A 看 Grafana Dashboard 的三个核心指标:QPS(是否正常)、P99 延迟(是否飙升)、错误率(是否 > 1%)。同时看日志聚合是否有异常 ERROR。
Q 回滚需要多长时间?
A Docker 回滚约 30 秒(切回旧版本镜像+重启)。数据库回滚取决于迁移复杂度,简单列删除秒级,复杂迁移可能需要数据修复。
📖 小节
- 上线 Checklist 三维度:安全(HTTPS/CORS/RateLimit)、性能(Workers/连接池/缓存)、监控(指标/告警/日志)
- 蓝绿部署:两套环境切换,回滚秒级;金丝雀发布:逐步放量,10%→30%→50%→100%
- 零停机迁移原则:只加不删、双写兼容、延迟清理、小步迁移
- 结构化 JSON 日志 + Loki/Grafana 聚合,按 request_id 追踪请求
- PriceTracker 正式上线:Bob 前端对接成功,Charlie 监控绿灯,百万级价格数据稳定流转
📝 作业
- 基础题(难度⭐):创建 PriceTracker 上线 Checklist(安全/性能/监控三维度各 5 项),逐项验证你的部署环境。提示:参考本文 Checklist 表格
- 进阶题(难度⭐⭐):实现结构化 JSON 日志(含 timestamp/level/message/request_id),添加日志中间件为每个请求注入 request_id,配置 Docker Compose + Loki 聚合日志。提示:JSONFormatter +
logging.getLogger("pricetracker") - 挑战题(难度⭐⭐⭐):完整上线流程——编写安全迁移脚本(只加不删原则)、配置 Nginx 蓝绿部署、配置 Grafana 告警规则(P99>500ms + 错误率>5%)、执行灰度发布验证(先 10% 流量到 v2 再全量)。提示:
CREATE INDEX CONCURRENTLY+ Nginx upstream 切换
---| 返回目录