部署
将 FastAPI 应用部署到生产环境需要考虑性能、安全、可靠性等多个方面。本章介绍常见的部署方式和最佳实践。
运行方式
Uvicorn
Uvicorn 是一个 ASGI 服务器,是运行 FastAPI 应用的标准选择:
# 开发模式(自动重载)
uvicorn main:app --reload
# 生产模式
uvicorn main:app --host 0.0.0.0 --port 8000
# 多 worker(利用多核 CPU)
uvicorn main:app --workers 4
# 完整配置
uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--loop uvloop \
--http httptools \
--log-config log_config.json
FastAPI CLI
FastAPI 提供了便捷的命令行工具:
# 开发模式
fastapi dev main.py
# 生产模式
fastapi run main.py --workers 4
Gunicorn + Uvicorn
生产环境推荐使用 Gunicorn 作为进程管理器,Uvicorn 作为工作进程:
# 安装
pip install gunicorn uvicorn[standard]
# 运行
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \
--keep-alive 5
Gunicorn 配置文件 gunicorn.conf.py:
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1 # 推荐配置
worker_class = "uvicorn.workers.UvicornWorker"
keepalive = 120
timeout = 120
环境变量
使用 pydantic-settings
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "FastAPI Application"
debug: bool = False
database_url: str
secret_key: str
class Config:
env_file = ".env"
settings = Settings()
.env 文件
# .env
APP_NAME=My API
DEBUG=false
DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=your-secret-key-here
Docker 中使用环境变量
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 环境变量可以在运行时设置
ENV APP_NAME="Production API"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# 运行时传入环境变量
docker run -e DATABASE_URL=postgresql://... -e SECRET_KEY=... my-app
Docker 部署
单容器部署
# Dockerfile
FROM python:3.11-slim as builder
WORKDIR /app
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# 最终镜像
FROM python:3.11-slim
WORKDIR /app
# 复制依赖
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# 复制应用代码
COPY . .
# 非 root 用户
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
构建和运行:
# 构建镜像
docker build -t my-fastapi-app .
# 运行容器
docker run -p 8000:8000 my-fastapi-app
Docker Compose
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- SECRET_KEY=${SECRET_KEY}
depends_on:
- db
- redis
volumes:
- ./app:/app/app
db:
image: postgres:15
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- web
volumes:
postgres_data:
启动:
docker-compose up -d
Nginx 反向代理
基本配置
# /etc/nginx/conf.d/fastapi.conf
upstream fastapi {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://fastapi;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
代理转发头(Proxy Forwarded Headers)
当 FastAPI 应用位于代理(如 Nginx、Traefik)后面时,代理会添加一些特殊的请求头来传递原始请求信息:
- X-Forwarded-For:原始客户端 IP 地址
- X-Forwarded-Proto:原始协议(如
https) - X-Forwarded-Host:原始主机名(如
example.com)
这些信息对于生成正确的重定向 URL 和 OpenAPI 文档非常重要。
启用代理头信任
默认情况下,出于安全考虑,FastAPI 不会解析这些转发头。需要显式告诉 FastAPI 信任来自代理的头信息:
# 信任所有 IP 的转发头
fastapi run --forwarded-allow-ips="*"
# 或信任特定 IP
fastapi run --forwarded-allow-ips="10.0.0.1,10.0.0.2"
工作原理:
当启用 --forwarded-allow-ips 后,FastAPI 会解析代理添加的转发头,并使用这些信息生成正确的 URL。
例如,重定向时会从 http://localhost:8000/items/ 变为 https://example.com/items/。
使用 root_path 处理路径前缀
如果代理将应用部署在某个路径前缀下(如 /api/v1),需要配置 root_path:
场景说明
假设:
- 代理监听
https://example.com/api/v1/ - FastAPI 应用监听
http://127.0.0.1:8000/ - 应用的路由定义为
/items
实际访问路径应该是 https://example.com/api/v1/items,但 FastAPI 应用只知道 /items。
配置方式
方式一:命令行参数
fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
方式二:在代码中配置
from fastapi import FastAPI, Request
app = FastAPI(root_path="/api/v1")
@app.get("/items")
async def read_items(request: Request):
return {
"message": "Hello World",
"root_path": request.scope.get("root_path")
}
验证 root_path
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/app")
async def read_main(request: Request):
return {
"message": "Hello World",
"root_path": request.scope.get("root_path")
}
启动命令:
fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
访问 /app 返回:
{
"message": "Hello World",
"root_path": "/api/v1"
}
OpenAPI 文档自动适配
配置 root_path 后,OpenAPI 文档会自动包含正确的路径前缀:
{
"openapi": "3.1.0",
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
"/app": { ... }
}
}
这样,Swagger UI 就能正确地发送请求到 /api/v1/app 而不是 /app。
自定义服务器列表
可以为 OpenAPI 文档指定多个服务器地址:
from fastapi import FastAPI
app = FastAPI(
servers=[
{"url": "/api/v1", "description": "当前环境"},
{"url": "https://staging.example.com/api/v1", "description": "测试环境"},
{"url": "https://prod.example.com/api/v1", "description": "生产环境"},
],
root_path="/api/v1"
)
生成的 OpenAPI 文档会在服务器列表中首先包含 root_path 对应的服务器,然后是自定义的服务器。
如果不需要自动添加 root_path 服务器:
app = FastAPI(
servers=[...],
root_path="/api/v1",
root_path_in_servers=False # 禁用自动添加
)
完整的代理配置示例
Nginx 配置(带路径前缀)
server {
listen 80;
server_name example.com;
location /api/v1/ {
proxy_pass http://127.0.0.1:8000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
}
启动 FastAPI
fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
这样配置后:
- 用户访问
https://example.com/api/v1/items - Nginx 将请求转发到
http://127.0.0.1:8000/items - FastAPI 知道自己的
root_path是/api/v1 - OpenAPI 文档和重定向都能正常工作
HTTPS 配置
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://fastapi;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
使用 Let's Encrypt
# 安装 certbot
sudo apt install certbot python3-certbot-nginx
# 获取证书
sudo certbot --nginx -d example.com -d www.example.com
# 自动续期
sudo certbot renew --dry-run
Systemd 服务
创建系统服务管理 FastAPI 应用:
# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI Application
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/fastapi
Environment="PATH=/var/www/fastapi/venv/bin"
Environment="DATABASE_URL=postgresql://..."
Environment="SECRET_KEY=..."
ExecStart=/var/www/fastapi/venv/bin/gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 127.0.0.1:8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
管理服务:
# 重新加载配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start fastapi
# 开机自启
sudo systemctl enable fastapi
# 查看状态
sudo systemctl status fastapi
# 查看日志
sudo journalctl -u fastapi -f
性能优化
Worker 数量
推荐公式:workers = (2 * CPU核心数) + 1
# 查看核心数
nproc
# 设置 worker 数量
uvicorn main:app --workers 9 # 假设 4 核
使用 uvloop 和 httptools
# 安装
pip install uvicorn[standard]
# 或单独安装
pip install uvloop httptools
Uvicorn 会自动检测并使用这些优化库。
数据库连接池
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=10, # 连接池大小
max_overflow=20, # 最大溢出连接
pool_timeout=30, # 获取连接超时
pool_recycle=1800, # 连接回收时间
)
异步数据库
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
async_engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
pool_size=10,
max_overflow=20,
)
AsyncSessionLocal = sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False
)
缓存
使用 Redis 缓存:
from fastapi import FastAPI
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
from redis import asyncio as aioredis
app = FastAPI()
@app.on_event("startup")
async def startup():
redis = aioredis.from_url("redis://localhost")
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
@app.get("/items/")
@cache(expire=60) # 缓存 60 秒
async def get_items():
return expensive_db_query()
监控与日志
结构化日志
import logging
import json
from fastapi import FastAPI, Request
import time
app = FastAPI()
class JSONFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
}
return json.dumps(log_data)
# 配置日志
logger = logging.getLogger("uvicorn.access")
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.handlers = [handler]
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
logger.info(
f"{request.method} {request.url.path}",
extra={
"method": request.method,
"path": request.url.path,
"status_code": response.status_code,
"duration": time.time() - start_time
}
)
return response
健康检查
@app.get("/health")
async def health_check():
return {"status": "healthy"}
@app.get("/ready")
async def readiness_check(db: Session = Depends(get_session)):
# 检查数据库连接
try:
db.execute("SELECT 1")
return {"status": "ready"}
except Exception:
raise HTTPException(status_code=503, detail="Not ready")
Prometheus 指标
from prometheus_fastapi_instrumentator import Instrumentator
app = FastAPI()
Instrumentator().instrument(app).expose(app)
访问 /metrics 获取 Prometheus 格式的指标。
完整部署示例
项目结构
myapp/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── models.py
│ └── routers/
├── tests/
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── requirements.txt
├── gunicorn.conf.py
└── .env.example
requirements.txt
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
gunicorn>=21.0.0
sqlmodel>=0.0.14
asyncpg>=0.29.0
pydantic-settings>=2.0.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
python-multipart>=0.0.6
redis>=5.0.0
Dockerfile(生产)
# 构建阶段
FROM python:3.11-slim as builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
# 运行阶段
FROM python:3.11-slim
WORKDIR /app
# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖
COPY --from=builder /app/wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
# 创建非 root 用户
RUN useradd -m -r appuser && chown -R appuser /app
# 复制应用
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]
docker-compose.yml(生产)
version: '3.8'
services:
web:
build: .
restart: always
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- SECRET_KEY=${SECRET_KEY}
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: always
networks:
- backend
nginx:
image: nginx:alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- web
networks:
- backend
networks:
backend:
volumes:
postgres_data:
小结
本章我们学习了:
- 运行方式:Uvicorn、FastAPI CLI、Gunicorn
- 环境变量:使用 pydantic-settings 管理配置
- Docker 部署:Dockerfile 和 Docker Compose
- Nginx 反向代理:配置 HTTPS 和负载均衡
- Systemd 服务:将应用注册为系统服务
- 性能优化:Worker 数量、连接池、缓存
- 监控日志:结构化日志、健康检查、Prometheus
部署最佳实践:
- 使用环境变量管理配置
- 非 root 用户运行
- 启用 HTTPS
- 配置健康检查
- 收集日志和监控指标
- 使用缓存减轻数据库压力
练习
- 编写一个完整的 Dockerfile,使用多阶段构建
- 配置 Nginx 反向代理和 HTTPS
- 创建 Systemd 服务配置文件
- 添加 Prometheus 指标收集