部署
将 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;
}
}
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 指标收集