1. 为什么需要Docker部署FastAPI?
在开发FastAPI应用时,我们通常会使用Uvicorn或Hypercorn这样的ASGI服务器在本地运行。但当需要将应用部署到生产环境时,直接使用这种方式会遇到几个典型问题:
- 环境差异:你的开发机可能是MacOS,而服务器是Ubuntu,Python版本、依赖库版本甚至系统库的微小差异都可能导致应用无法正常运行
- 依赖冲突:当服务器上需要运行多个Python应用时,不同应用对依赖库版本的要求可能相互冲突
- 部署效率:每次更新代码后,需要手动登录服务器执行git pull、安装依赖、重启服务等操作
- 扩展困难:当需要水平扩展时,传统部署方式难以保证多个实例的环境完全一致
Docker通过容器化技术完美解决了这些问题。它允许你将应用及其所有依赖打包成一个标准化的镜像,这个镜像可以在任何支持Docker的环境中运行,确保开发、测试和生产环境的一致性。
提示:虽然Docker不是唯一的选择(比如你也可以使用虚拟机),但它的轻量级特性(容器共享主机OS内核)使其成为微服务架构下的首选方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 构建FastAPI的Docker镜像
2.1 基础镜像选择
Python应用的Docker镜像通常有两种构建方式:
-
使用官方Python镜像:
dockerfile复制FROM python:3.9-slim- 优点:官方维护,更新及时
- 缺点:镜像体积较大(即使使用slim版本也有约100MB)
-
使用Alpine Linux+Python:
dockerfile复制FROM python:3.9-alpine- 优点:镜像极小(约20MB)
- 缺点:某些Python包可能需要额外系统依赖,兼容性稍差
对于生产环境,我推荐使用python:3.9-slim-buster,它在体积和兼容性之间取得了良好平衡。
2.2 完整Dockerfile示例
下面是一个经过生产验证的Dockerfile模板:
dockerfile复制# 第一阶段:构建阶段
FROM python:3.9-slim as builder
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# 安装系统依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc python3-dev && \
rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 第二阶段:运行阶段
FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
# 确保脚本可执行
RUN chmod +x /app/start.sh
# 确保用户能访问安装的包
ENV PATH=/root/.local/bin:$PATH
# 服务端口
EXPOSE 8000
# 启动命令
CMD ["/app/start.sh"]
这个Dockerfile使用了多阶段构建,可以显著减小最终镜像体积。关键点说明:
- PYTHONDONTWRITEBYTECODE:防止Python生成.pyc文件
- PYTHONUNBUFFERED:确保日志能实时输出
- 多阶段构建:第一阶段安装编译依赖和Python包,第二阶段只复制必要的文件
2.3 start.sh启动脚本
bash复制#!/bin/bash
# 等待数据库等依赖服务就绪
while ! nc -z $DB_HOST $DB_PORT; do
echo "Waiting for database..."
sleep 1
done
# 执行数据库迁移(如有)
alembic upgrade head
# 启动FastAPI
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
这个启动脚本做了三件事:
- 检查数据库是否就绪(避免应用启动时数据库还未准备好)
- 执行数据库迁移
- 启动Uvicorn服务器
注意:在实际项目中,你可能还需要添加prestart.sh来处理一些初始化工作,比如创建静态文件目录等。
3. 优化Docker镜像与部署
3.1 镜像优化技巧
-
使用.dockerignore文件:
code复制__pycache__/ *.pyc *.pyo *.pyd .env .venv .git/ .vscode/ -
依赖分层安装:
- 将很少变化的依赖(如框架本身)和频繁变化的依赖分开
- 修改requirements.txt结构:
code复制# base.txt fastapi==0.68.0 uvicorn==0.15.0 # dev.txt -r base.txt pytest==6.2.4
-
使用pip的--no-cache-dir选项:
dockerfile复制RUN pip install --no-cache-dir --user -r requirements.txt
3.2 生产环境部署建议
-
使用Gunicorn+Uvicorn:
- 修改start.sh中的启动命令:
bash复制gunicorn -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000 --workers 4 - 优势:Gunicorn作为进程管理器,可以提供更好的稳定性和graceful shutdown
- 修改start.sh中的启动命令:
-
配置合理的worker数量:
- 公式:CPU核心数 * 2 + 1
- 例如4核CPU:
--workers 9
-
健康检查:
- 在Docker Compose或Kubernetes配置中添加健康检查:
yaml复制healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 3
- 在Docker Compose或Kubernetes配置中添加健康检查:
4. 常见问题与解决方案
4.1 性能问题排查
当发现FastAPI在Docker中性能下降时,可以检查:
-
Docker资源限制:
bash复制
docker stats <container_id>- 确保容器有足够的CPU和内存资源
-
Gunicorn配置:
- 使用
--threads参数配合--workers:bash复制gunicorn -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000 --workers 4 --threads 2
- 使用
-
Linux内核参数:
- 增加最大连接数:
bash复制
sysctl -w net.core.somaxconn=65535
- 增加最大连接数:
4.2 容器网络问题
-
跨容器通信:
- 使用Docker Compose时,服务间可以通过服务名直接通信
- 例如数据库连接字符串:
python复制SQLALCHEMY_DATABASE_URL = "postgresql://user:password@db:5432/dbname"
-
端口冲突:
- 检查主机端口是否被占用:
bash复制
netstat -tuln | grep 8000
- 检查主机端口是否被占用:
4.3 日志收集
生产环境中,建议配置集中式日志收集:
-
Docker日志驱动:
bash复制
docker run --log-driver=syslog --log-opt syslog-address=udp://logserver:514 myapp -
Python日志配置:
python复制import logging from fastapi.logger import logger logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.StreamHandler(), logging.FileHandler("/var/log/app.log") ] )
5. 进阶部署方案
5.1 使用Docker Compose编排
对于包含数据库、Redis等依赖的服务,推荐使用docker-compose.yml:
yaml复制version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DB_HOST=db
- DB_PORT=5432
depends_on:
- db
restart: unless-stopped
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: example
POSTGRES_USER: user
POSTGRES_DB: dbname
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
5.2 Kubernetes部署
对于大规模生产环境,可以使用Kubernetes部署:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-app
spec:
replicas: 3
selector:
matchLabels:
app: fastapi
template:
metadata:
labels:
app: fastapi
spec:
containers:
- name: app
image: yourregistry/fastapi-app:latest
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: app-config
---
apiVersion: v1
kind: Service
metadata:
name: fastapi-service
spec:
selector:
app: fastapi
ports:
- protocol: TCP
port: 80
targetPort: 8000
5.3 CI/CD集成
在GitHub Actions中配置自动构建和部署:
yaml复制name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: yourusername/fastapi-app:latest
6. 安全最佳实践
-
非root用户运行:
dockerfile复制RUN useradd -m appuser && chown -R appuser /app USER appuser -
定期更新基础镜像:
- 定期检查并更新
FROM python:3.9-slim中的版本 - 订阅Python和Docker的安全公告
- 定期检查并更新
-
敏感信息管理:
- 使用Docker secrets或环境变量注入
- 永远不要在Dockerfile中硬编码密码
-
镜像扫描:
bash复制
docker scan yourimage:tag
在实际项目中,我通常会为每个环境(dev/staging/prod)准备不同的Docker Compose文件,通过环境变量控制配置差异。例如,开发环境可能启用热重载,而生产环境则配置了完整的监控和日志收集。
