第 22 章 · 生产部署实战
本章目标:理解生产部署的核心概念模型,掌握多 worker 调优、Docker 镜像构建最佳实践与健康检查、优雅关闭。
22.1 部署的概念模型
部署 FastAPI(或任何 Web API)时,真正要关心的只有六件事:
- 安全(HTTPS)——由应用外部的 TLS 终止代理负责加密;
- 开机自启——服务器重启后应用自动拉起;
- 崩溃重启——进程挂掉后自动恢复;
- 副本数(replication)——跑多少个进程来利用多核;
- 内存——控制每个进程的用量;
- 启动前置步骤——迁移数据库等。
理解一个关键分层:ASGI 服务器(Uvicorn)运行你的应用,但 HTTPS 通常不归它管。TLS 终止代理(Traefik / Caddy / Nginx + Certbot / 云负载均衡)在外层解密流量后转发给 Uvicorn。而"保活与重启"由 Docker/Kubernetes/systemd/Supervisor 这类进程管理器负责。各司其职,不要让 Python 进程去干代理的活。
客户端 --HTTPS--> TLS 终止代理(443) --HTTP--> Uvicorn/FastAPI (8000, 内网)22.2 fastapi run 与多 worker 调优
开发用 fastapi dev;生产环境官方 CLI 提供对应的生产模式:
# 单进程
fastapi run main.py --port 80
# 多 worker:主进程 + 4 个工作子进程
fastapi run main.py --workers 4
# 等价的原生方式
uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
# 若部署在 Nginx/Traefik 等代理后面,需要信任代理头
fastapi run app/main.py --port 80 --proxy-headers--workers 4 会启动 1 个父进程(进程管理器)+ 4 个 worker 进程,各自独立处理请求,从而利用多个 CPU 核。
worker 数量调优经验:
- CPU 密集型为主 → 接近
CPU 核数; - I/O 密集型为主(典型 API 应用)→ 可以更高,如
(2 × CPU) + 1; - 上限受内存约束:每个 worker 是完整独立进程,内存占用线性叠加;
- 容器/Kubernetes 场景例外:通常每容器只跑 1 个 Uvicorn 进程,横向扩容交给副本数(pod 数),避免双重复制导致资源超卖。
不要用 fastapi dev 上生产
dev 模式带自动重载和调试功能,性能与安全性都不适合生产;生产一律 fastapi run 或裸 uvicorn。
22.3 Dockerfile 最佳实践
官方推荐的 Dockerfile 结构(每一行都有讲究):
FROM python:3.13
WORKDIR /code
# ① 先只拷贝依赖清单 —— 它很少变
COPY ./requirements.txt /code/requirements.txt
# ② 再装依赖 —— 依赖清单没变时直接命中 Docker 缓存,秒级完成
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# ③ 最后才拷贝业务代码 —— 变化最频繁,放最后保证缓存有效
COPY ./app /code/app
# ④ 必须用 exec 形式的 CMD(JSON 数组)
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]四个关键细节:
- 分层顺序即缓存策略:requirements.txt 几乎不变,放前面;
app/天天变,放后面。顺序错了每次改一行代码都要重装全部依赖。 --no-cache-dir是 pip 的选项,不把下载的包留在镜像里,减小体积。- CMD 必须用 exec 形式(
["fastapi", "run", ...]):shell 形式(CMD fastapi run ...)会让命令跑在 shell 子进程里,无法正确接收 SIGTERM 信号,优雅关闭失效。 - 配套的
.dockerignore避免把垃圾拷进镜像:
.git
__pycache__
*.pyc
.env # 永远不要打进镜像!
docs
tests
.venv构建与运行:
docker build -t myapp .
docker run -d --name mycontainer -p 80:80 myapp22.4 健康检查与优雅关闭
健康检查端点
负载均衡器/K8s 需要一个轻量端点探测进程是否存活:
from fastapi import FastAPI
app = FastAPI(lifespan=lifespan)
@app.get("/health", include_in_schema=False) # 不进公开文档
async def health():
return {"status": "ok"}注意存活探针应保持极轻——不要在里面做重量级数据库查询(探针失败会被重启,误判雪崩);深度检查拆成独立的 /ready 就绪探针:
from fastapi import Response, status
from fastapi.responses import JSONResponse
from sqlalchemy import text
@app.get("/ready", include_in_schema=False)
async def ready(response: Response):
"""就绪探针:检查真实依赖(数据库)是否可用。"""
try:
engine = app.state.engine
async with engine.connect() as conn:
await conn.execute(text("SELECT 1"))
return {"status": "ready"}
except Exception:
# 依赖不可用时不返回 200,让 K8s 摘除该实例的流量
return JSONResponse(status_code=503, content={"status": "not ready"})部署后还可以写一个冒烟脚本,在发布流水线里验证新版本真的健康:
# smoke_test.py —— 发布后自动运行,任一断言失败则回滚
import sys
import httpx
BASE = "https://api.example.com"
resp = httpx.get(f"{BASE}/health", timeout=5.0)
assert resp.status_code == 200, f"健康检查失败: {resp.status_code}"
resp = httpx.get(f"{BASE}/openapi.json", timeout=5.0)
assert resp.status_code == 200 and "/items/" in resp.text, "OpenAPI 文档异常"
print("✅ 部署冒烟测试通过")
sys.exit(0)程序化启动与优雅关闭
滚动发布时,旧进程要"处理完手头的请求再退出"。Uvicorn 收到 SIGTERM 后会停止接收新连接、等待存量请求完成再退出,等待上限可用参数控制:
uvicorn main:app --timeout-graceful-shutdown 20 # 最多宽限 20 秒这也是 CMD 必须写 exec 形式的原因:PID 1 直接是 uvicorn 进程才能收到信号;包在 shell 里信号就丢失了,只能被强杀,正在处理的请求全部中断。
除了命令行方式,也可以在 Python 里程序化启动生产服务器,方便嵌入自定义启动逻辑:
# serve.py —— python serve.py 等价于 uvicorn main:app --workers 4
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8080,
workers=4,
proxy_headers=True, # 信任 Nginx/Traefik 传来的 X-Forwarded-*
timeout_graceful_shutdown=20, # 优雅关闭宽限 20 秒
access_log=False, # 生产可关访问日志,交给代理记录
)uvicorn.run() 内部同样会安装 SIGTERM/SIGINT 处理器:收到信号后停止 accept 新连接 → 等待存量请求完成(不超过宽限期)→ 执行 lifespan 的 shutdown 段释放资源 → 退出。K8s 滚动更新正是依赖这条链路做到零请求损失的。
22.5 本章小结
- 部署六要素:HTTPS、自启、重启、副本、内存、前置步骤;HTTPS 由外层 TLS 终止代理负责,Python 不管加解密;
- 生产用
fastapi run(非 dev),多 worker 利用多核,数量按 CPU/IO 特征权衡,容器内通常单进程+多副本; - Dockerfile 黄金顺序:先 COPY requirements → 装依赖 → 再 COPY 代码;CMD 用 exec 形式保证信号可达;
.env与敏感信息绝不进镜像,用.dockerignore兜底;- 提供
/health存活探针;Uvicorn 默认响应 SIGTERM 优雅排空请求,--timeout-graceful-shutdown控制宽限时长。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. HTTPS 加密(TLS 终止)在生产架构中通常由谁负责?
2. 为什么 CMD 要用 exec 形式 CMD ["fastapi","run","main.py"]?
3. Dockerfile 中先 COPY requirements.txt 安装依赖、最后才 COPY 业务代码,目的是?
4. 在 Kubernetes 中部署 FastAPI,关于进程副本的推荐做法是?
🛠️ 动手实践
- 把你的 todos 应用写成完整 Docker 工程:
.dockerignore+ 分层 Dockerfile,构建后docker run验证/health可访问。 - 在 docker-compose.yml 中编排 FastAPI + Nginx:Nginx 监听 443(自签证书即可)做 TLS 终止并反代到应用容器。
- 写一个耗时 3 秒的慢接口,分别在 shell 形式与 exec 形式 CMD 下
docker stop容器,对比日志观察优雅关闭的差异。
恭喜完成 FastAPI 全部 22 章学习!建议回到课程导学回顾知识地图,并用动手实践项目巩固。