FastAPI 系列 ·(十二):生产部署——Docker + 配置管理(系列完结)

FastAPI 系列 · 第 12 篇:生产部署------Docker + 配置管理(系列完结)

🎯 适合人群 :已完成 shop-api 功能开发,希望将 FastAPI 应用部署到生产环境的工程师

⏱️ 阅读时间 :约 35 分钟

💬 一句话定位 :从 pydantic-settings 多环境配置管理到多阶段 Dockerfile 构建,从 docker-compose 全栈编排到 Nginx 反向代理,系列最终篇完整还原一个 FastAPI 应用的生产部署全流程。


一、配置管理:pydantic-settings

Spring Boot 用 application.yml + @ConfigurationProperties 管理配置,FastAPI 对应方案是 pydantic-settings + .env 文件。

bash 复制代码
pip install pydantic-settings python-dotenv

1.1 BaseSettings 配置类

python 复制代码
# app/config.py
from functools import lru_cache
from typing import Literal
from pydantic import field_validator, AnyHttpUrl
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    """
    全局配置类。
    对标 Spring Boot @ConfigurationProperties + application.yml。

    优先级(高到低):
    1. 环境变量
    2. .env 文件
    3. 字段默认值
    """

    model_config = SettingsConfigDict(
        env_file=".env",               # 默认读取 .env 文件
        env_file_encoding="utf-8",
        case_sensitive=False,          # 环境变量大小写不敏感
        extra="ignore",                # 忽略 .env 中多余的键
    )

    # ---- 应用基础 ----
    APP_NAME: str = "shop-api"
    APP_VERSION: str = "1.0.0"
    DEBUG: bool = False
    ENVIRONMENT: Literal["development", "test", "production"] = "development"

    # ---- 安全 ----
    SECRET_KEY: str                    # 无默认值,必须由环境变量提供
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    REFRESH_TOKEN_EXPIRE_DAYS: int = 7
    ALLOWED_ORIGINS: list[str] = ["http://localhost:3000"]

    # ---- 数据库 ----
    DATABASE_URL: str                  # 必填
    DB_POOL_SIZE: int = 10
    DB_MAX_OVERFLOW: int = 20
    DB_ECHO: bool = False              # 生产环境务必关闭 SQL 日志

    # ---- Redis ----
    REDIS_URL: str = "redis://localhost:6379/0"
    REDIS_MAX_CONNECTIONS: int = 20

    # ---- Celery ----
    CELERY_BROKER_URL: str = "redis://localhost:6379/1"
    CELERY_RESULT_BACKEND: str = "redis://localhost:6379/2"

    # ---- ClickHouse ----
    CLICKHOUSE_HOST: str = "localhost"
    CLICKHOUSE_PORT: int = 9000
    CLICKHOUSE_DATABASE: str = "shop_analytics"
    CLICKHOUSE_USER: str = "default"
    CLICKHOUSE_PASSWORD: str = ""

    # ---- 邮件 ----
    SMTP_HOST: str = "smtp.example.com"
    SMTP_PORT: int = 465
    SMTP_USER: str = ""
    SMTP_PASSWORD: str = ""

    @field_validator("SECRET_KEY")
    @classmethod
    def validate_secret_key(cls, v: str) -> str:
        if len(v) < 32:
            raise ValueError("SECRET_KEY 长度至少 32 位,生产环境请使用随机生成值")
        return v

    @field_validator("ALLOWED_ORIGINS", mode="before")
    @classmethod
    def parse_origins(cls, v):
        """支持逗号分隔的字符串:ALLOWED_ORIGINS=http://a.com,http://b.com"""
        if isinstance(v, str):
            return [origin.strip() for origin in v.split(",")]
        return v

    @property
    def is_production(self) -> bool:
        return self.ENVIRONMENT == "production"

    @property
    def is_development(self) -> bool:
        return self.ENVIRONMENT == "development"


@lru_cache
def get_settings() -> Settings:
    """
    单例模式获取配置,使用 lru_cache 缓存(对标 Spring 单例 Bean)。
    测试时可用 get_settings.cache_clear() 清除缓存以注入测试配置。
    """
    return Settings()


settings = get_settings()

1.2 三套环境配置文件

bash 复制代码
# .env.development(本地开发)
APP_NAME=shop-api
ENVIRONMENT=development
DEBUG=true
SECRET_KEY=dev-only-secret-key-32-chars-minimum!!
DATABASE_URL=mysql+aiomysql://root:password@localhost:3306/shop_dev
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/1
CLICKHOUSE_HOST=localhost
DB_ECHO=true
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
bash 复制代码
# .env.test(测试环境 / CI)
ENVIRONMENT=test
DEBUG=false
SECRET_KEY=test-secret-key-for-ci-pipeline-only!!
DATABASE_URL=mysql+aiomysql://root:testpass@localhost:3306/shop_test
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/1
CLICKHOUSE_HOST=localhost
DB_ECHO=false
bash 复制代码
# .env.production(生产环境,此文件不进代码库!)
ENVIRONMENT=production
DEBUG=false
SECRET_KEY=<openssl rand -hex 32 生成的随机值>
DATABASE_URL=mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/2
CLICKHOUSE_HOST=clickhouse
CLICKHOUSE_PASSWORD=${CH_PASSWORD}
DB_POOL_SIZE=20
DB_MAX_OVERFLOW=40
ALLOWED_ORIGINS=https://shop.example.com

⚠️ 重要.env.production 和任何包含真实密码的文件绝对不能提交到代码库 。将它们加入 .gitignore。生产密钥通过 CI/CD 系统(GitHub Secrets、Vault、K8s Secret)注入。


二、多阶段 Dockerfile

Spring Boot 打包成一个 Fat JAR,FastAPI 的对应方案是多阶段 Docker 构建:第一阶段安装依赖,第二阶段只复制必要文件,显著减小镜像体积。

dockerfile 复制代码
# Dockerfile
# -------------------------------------------------------
# Stage 1: Builder --- 安装依赖(镜像会很大,但不会进入最终镜像)
# -------------------------------------------------------
FROM python:3.11-slim AS builder

WORKDIR /build

# 安装编译依赖(aiomysql、cryptography 等需要编译的包)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    libssl-dev \
    libffi-dev \
    && rm -rf /var/lib/apt/lists/*

# 先复制依赖文件,利用 Docker layer 缓存
# 只有 requirements.txt 变化时才重新安装
COPY requirements.txt .

# 安装到 /install 目录,与系统 Python 隔离
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt


# -------------------------------------------------------
# Stage 2: Runtime --- 最终镜像,只包含运行时必要内容
# -------------------------------------------------------
FROM python:3.11-slim AS runtime

WORKDIR /app

# 安全:创建非 root 用户运行应用(对标 Spring Boot 不以 root 运行)
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 从 builder 阶段复制已安装的依赖
COPY --from=builder /install /usr/local

# 复制应用代码
COPY app/ ./app/
COPY alembic/ ./alembic/
COPY alembic.ini .

# 设置 Python 环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONPATH=/app

# 切换到非 root 用户
USER appuser

# 健康检查(定期调用 /health 接口)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"

EXPOSE 8000

# 使用 Gunicorn + UvicornWorker 运行(生产推荐)
CMD ["gunicorn", "app.main:app", \
     "--workers", "4", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000", \
     "--access-logfile", "-", \
     "--error-logfile", "-", \
     "--log-level", "info", \
     "--proxy-headers", \
     "--forwarded-allow-ips", "*"]

2.1 镜像体积对比

方式 镜像大小
单阶段构建(含编译工具) ~850MB
多阶段构建(runtime only) ~280MB
.dockerignore 优化 ~260MB
text 复制代码
# .dockerignore
.git
.gitignore
.env*
__pycache__
*.pyc
*.pyo
.pytest_cache
tests/
docs/
*.md
.venv
htmlcov/

三、Gunicorn + UvicornWorker

FastAPI 基于 ASGI,不能直接用 Gunicorn 的默认 WSGI Worker,需要 UvicornWorker。

bash 复制代码
pip install gunicorn uvicorn[standard]

3.1 Worker 数量计算

bash 复制代码
# 经典公式:2 * CPU 核数 + 1
# 4 核服务器推荐 9 个 worker
# 注意:worker 过多反而会因内存竞争降低性能

# 生产启动命令
gunicorn app.main:app \
    --workers $((2 * $(nproc) + 1)) \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \               # 请求超时(秒)
    --keepalive 5 \               # Keep-Alive 连接时长
    --max-requests 1000 \         # 每个 worker 处理 N 个请求后自动重启(防内存泄漏)
    --max-requests-jitter 100 \   # 随机抖动,避免所有 worker 同时重启
    --access-logfile - \          # 输出到 stdout
    --error-logfile - \
    --log-level info \
    --proxy-headers               # 信任 Nginx 的 X-Forwarded-For

💡 提示 :容器环境推荐单进程运行(--workers 1),通过横向扩容增加实例数,而非单容器多进程。这样更符合 12-Factor App 原则,也方便 Kubernetes 管理。


四、docker-compose.yml 全栈编排

yaml 复制代码
# docker-compose.yml
version: "3.9"

services:
  # -------------------------------------------------------
  # FastAPI 应用(对标 Spring Boot 打包后的 JAR 部署)
  # -------------------------------------------------------
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    image: shop-api:latest
    container_name: shop-api-app
    restart: unless-stopped
    env_file:
      - .env.production
    environment:
      DATABASE_URL: mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
    ports:
      - "8000:8000"
    depends_on:
      mysql:
        condition: service_healthy   # 等待 MySQL 健康检查通过再启动
      redis:
        condition: service_healthy
    networks:
      - backend
    volumes:
      - app-logs:/app/logs
    healthcheck:
      test: ["CMD", "python", "-c",
             "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # -------------------------------------------------------
  # MySQL 8.0
  # -------------------------------------------------------
  mysql:
    image: mysql:8.0
    container_name: shop-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: shop_prod
      MYSQL_USER: shop_user
      MYSQL_PASSWORD: ${DB_PASSWORD}
      TZ: Asia/Shanghai             # 时区(避免坑 1:容器内时区问题)
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./scripts/mysql-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - backend
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root",
             "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # -------------------------------------------------------
  # Redis 7
  # -------------------------------------------------------
  redis:
    image: redis:7-alpine
    container_name: shop-redis
    restart: unless-stopped
    command: >
      redis-server
      --requirepass ${REDIS_PASSWORD}
      --maxmemory 512mb
      --maxmemory-policy allkeys-lru
      --save 900 1
      --save 300 10
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # -------------------------------------------------------
  # Celery Worker(对标 Spring @Async 的独立工作进程)
  # -------------------------------------------------------
  celery-worker:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    image: shop-api:latest
    container_name: shop-celery-worker
    restart: unless-stopped
    command: >
      celery -A app.celery_app worker
      --loglevel=info
      --concurrency=4
      --queues=default,high_priority,email
      --hostname=worker@%h
    env_file:
      - .env.production
    environment:
      DATABASE_URL: mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - backend
    volumes:
      - app-logs:/app/logs

  # -------------------------------------------------------
  # Celery Beat(定时任务调度器,只能运行一个实例!)
  # -------------------------------------------------------
  celery-beat:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    image: shop-api:latest
    container_name: shop-celery-beat
    restart: unless-stopped
    command: >
      celery -A app.celery_app beat
      --loglevel=info
      --scheduler django_celery_beat.schedulers:DatabaseScheduler
    env_file:
      - .env.production
    depends_on:
      - redis
      - celery-worker
    networks:
      - backend

  # -------------------------------------------------------
  # Nginx 反向代理(对标 Spring Cloud Gateway / Nginx 前置)
  # -------------------------------------------------------
  nginx:
    image: nginx:1.25-alpine
    container_name: shop-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs:ro        # SSL 证书
      - nginx-logs:/var/log/nginx
    depends_on:
      app:
        condition: service_healthy
    networks:
      - backend
      - frontend

networks:
  backend:
    driver: bridge
  frontend:
    driver: bridge

volumes:
  mysql-data:
  redis-data:
  app-logs:
  nginx-logs:

五、Nginx 配置

nginx 复制代码
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日志格式(含请求耗时,方便性能分析)
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    client_max_body_size 20m;

    # Gzip 压缩(API 响应通常是 JSON,压缩效果显著)
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
}
nginx 复制代码
# nginx/conf.d/shop-api.conf

upstream shop_api {
    server app:8000;          # 对应 docker-compose 中的 app 服务
    keepalive 32;             # 复用连接,减少握手开销
}

# HTTP → HTTPS 跳转
server {
    listen 80;
    server_name shop.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 主服务
server {
    listen 443 ssl http2;
    server_name shop.example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;

    # 安全 Header
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    # API 代理
    location /api/ {
        proxy_pass http://shop_api;
        proxy_http_version 1.1;

        # 传递真实客户端信息(FastAPI 通过 --proxy-headers 读取)
        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;

        # WebSocket 支持(如有 WS 接口)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 超时配置
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 缓冲配置
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }

    # FastAPI Swagger UI(仅开发/测试环境暴露,生产可关闭)
    location /docs {
        proxy_pass http://shop_api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /openapi.json {
        proxy_pass http://shop_api;
    }

    # 健康检查端点(不记录访问日志,避免日志污染)
    location /health {
        proxy_pass http://shop_api;
        access_log off;
    }
}

六、健康检查端点

python 复制代码
# app/routers/health.py
import asyncio
from fastapi import APIRouter, status
from sqlalchemy import text
from app.database import AsyncSessionLocal
from app.redis.client import get_redis
from app.config import settings

router = APIRouter(tags=["infrastructure"])


@router.get(
    "/health",
    status_code=status.HTTP_200_OK,
    summary="服务健康检查",
)
async def health_check():
    """
    检查应用自身、数据库、Redis 的连通性。
    Docker HEALTHCHECK + Nginx upstream health check 都调用此接口。
    对标 Spring Boot Actuator /health 端点。
    """
    checks = {}
    overall_status = "ok"

    # 检查 MySQL
    try:
        async with AsyncSessionLocal() as session:
            await session.execute(text("SELECT 1"))
        checks["mysql"] = "ok"
    except Exception as e:
        checks["mysql"] = f"error: {str(e)[:100]}"
        overall_status = "degraded"

    # 检查 Redis
    try:
        redis = await get_redis()
        await redis.ping()
        checks["redis"] = "ok"
    except Exception as e:
        checks["redis"] = f"error: {str(e)[:100]}"
        overall_status = "degraded"

    response_body = {
        "status": overall_status,
        "version": settings.APP_VERSION,
        "environment": settings.ENVIRONMENT,
        "checks": checks,
    }

    # 如果核心依赖不可用,返回 503(触发 Docker 重启策略)
    if overall_status != "ok":
        from fastapi import HTTPException
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=response_body,
        )

    return response_body

七、数据库迁移(容器启动时自动执行)

python 复制代码
# scripts/start.sh(容器启动脚本,替换 CMD 中的直接调用)
#!/bin/bash
set -e

echo "==> 等待数据库就绪..."
python -c "
import asyncio
import sys
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
from app.config import settings

async def wait_for_db():
    engine = create_async_engine(settings.DATABASE_URL)
    for i in range(30):
        try:
            async with engine.connect() as conn:
                await conn.execute(text('SELECT 1'))
            print('数据库连接成功')
            await engine.dispose()
            return
        except Exception as e:
            print(f'等待数据库... ({i+1}/30): {e}')
            await asyncio.sleep(2)
    print('数据库连接超时,退出')
    sys.exit(1)

asyncio.run(wait_for_db())
"

echo "==> 执行数据库迁移..."
alembic upgrade head

echo "==> 启动应用..."
exec gunicorn app.main:app \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --access-logfile - \
    --error-logfile - \
    --log-level info \
    --proxy-headers \
    --forwarded-allow-ips "*"
dockerfile 复制代码
# Dockerfile 末尾修改 CMD
COPY scripts/start.sh /app/start.sh
RUN chmod +x /app/start.sh
CMD ["/app/start.sh"]

八、常见坑与最佳实践

坑 1:容器内时区错误

yaml 复制代码
# ❌ 不设置时区,容器默认 UTC,日志时间和业务时间对不上
services:
  app:
    image: shop-api

# ✅ 统一设置时区(所有服务都要设置)
services:
  app:
    image: shop-api
    environment:
      TZ: Asia/Shanghai
  mysql:
    environment:
      TZ: Asia/Shanghai
  redis:
    environment:
      TZ: Asia/Shanghai
dockerfile 复制代码
# ✅ 也可以在 Dockerfile 中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

坑 2:SECRET_KEY 泄漏

python 复制代码
# ❌ 硬编码 SECRET_KEY,一旦代码库泄漏,所有 Token 作废
SECRET_KEY = "mysecretkey"

# ❌ 使用默认值,忘记在生产环境覆盖
SECRET_KEY: str = "change-me-in-production"

# ✅ 无默认值,强制外部注入
SECRET_KEY: str  # 无默认值,必须由环境变量提供

# ✅ 生产环境用强随机值
# openssl rand -hex 32
# 输出:a3f8c2d1e4b5a6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1

坑 3:--reload 进入生产

bash 复制代码
# ❌ 开发时习惯加 --reload,生产环境误用
uvicorn app.main:app --reload --host 0.0.0.0

# --reload 的危害:
# 1. 监听文件系统变化,CPU 和内存消耗高
# 2. 代码文件被修改时自动重启,生产环境不稳定
# 3. 不支持多 worker

# ✅ 生产环境用 Gunicorn 管理 worker
gunicorn app.main:app --worker-class uvicorn.workers.UvicornWorker --workers 4

坑 4:数据库未就绪导致应用启动失败

yaml 复制代码
# ❌ 只用 depends_on 服务名,不等健康检查
services:
  app:
    depends_on:
      - mysql   # 只等 MySQL 容器启动,不等 MySQL 进程就绪

# ✅ 使用 condition: service_healthy,等待健康检查通过
services:
  app:
    depends_on:
      mysql:
        condition: service_healthy
  mysql:
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5

坑 5:镜像中包含 .env 文件

dockerfile 复制代码
# ❌ 不写 .dockerignore,.env 文件被打包进镜像
COPY . .   # 把整个目录复制,包含 .env!

# ✅ 在 .dockerignore 中排除所有 .env 文件
# .dockerignore
.env
.env.*
!.env.example    # 只保留示例文件

# ✅ 运行时通过 env_file 或环境变量注入,不打包进镜像

九、部署操作手册

bash 复制代码
# 一键启动全栈(首次部署)
cp .env.production.example .env.production  # 填写真实密钥
docker compose up -d --build

# 查看服务状态
docker compose ps
docker compose logs -f app        # 实时查看应用日志

# 执行数据库迁移(升级时)
docker compose exec app alembic upgrade head

# 滚动更新应用(不停服)
docker compose build app
docker compose up -d --no-deps app  # 只重启 app 服务

# 扩容 Celery Worker
docker compose up -d --scale celery-worker=3

# 停止并清理(保留数据卷)
docker compose down

# 停止并清理(包含数据卷,谨慎!)
docker compose down -v

十、全系列回顾

经过 12 篇的系统学习,shop-api 从零搭建到生产部署,覆盖了 FastAPI 完整技术栈。

篇次 主题 核心技术 Spring Boot 对应
01 初体验 FastAPI + Uvicorn + Pydantic Spring Boot + Tomcat + Bean Validation
02 路由与请求模型 APIRouter + Pydantic v2 @Controller + @RequestBody
03 依赖注入 Depends + yield @Autowired + @Transactional
04 数据库集成 SQLAlchemy 2.0 async + Alembic Spring Data JPA + Flyway
05 认证与鉴权 JWT + OAuth2 + python-jose Spring Security + JWT
06 异步编程 asyncio + httpx + asyncio.gather Spring WebFlux + Reactor
07 Redis 缓存 redis.asyncio + Cache Aside Spring Data Redis + @Cacheable
08 后台任务 BackgroundTasks + Celery + Beat @Async + Spring Batch
09 中间件与错误处理 BaseHTTPMiddleware + structlog + slowapi Filter + @ControllerAdvice
10 测试 pytest + AsyncClient + dependency_overrides JUnit5 + MockMvc + @MockBean
11 ClickHouse 集成 asynch + MergeTree + 物化视图 Spring JDBC + OLAP 数据库
12 生产部署 pydantic-settings + Dockerfile + docker-compose Spring Profiles + Docker

贯穿项目 shop-api 最终目录结构

复制代码
shop-api/
├── app/
│   ├── main.py                  # FastAPI 应用入口,lifespan 配置
│   ├── config.py                # pydantic-settings 配置管理
│   ├── database.py              # SQLAlchemy 异步引擎
│   ├── models/                  # ORM 模型(User, Product, Order)
│   ├── schemas/                 # Pydantic 请求/响应模型
│   ├── routers/                 # 路由层(APIRouter)
│   │   ├── auth.py
│   │   ├── products.py
│   │   ├── orders.py
│   │   ├── analytics.py         # ClickHouse 统计 API
│   │   └── health.py
│   ├── services/                # 业务逻辑层
│   ├── repositories/            # 数据访问层
│   ├── auth/                    # JWT + OAuth2
│   │   ├── security.py
│   │   └── dependencies.py
│   ├── redis/                   # Redis 连接池 + 缓存工具
│   ├── clickhouse/              # ClickHouse 连接 + 查询
│   ├── tasks/                   # Celery 任务定义
│   └── celery_app.py            # Celery 应用配置
├── alembic/                     # 数据库迁移
├── tests/                       # 测试套件
│   ├── conftest.py
│   ├── unit/
│   └── integration/
├── nginx/                       # Nginx 配置
├── scripts/                     # 启动脚本
├── Dockerfile
├── docker-compose.yml
├── .env.development
├── .env.test
├── .env.production.example      # 模板,真实值不提交
├── requirements.txt
└── pyproject.toml

总结

知识点 FastAPI 方案 Spring Boot 对应
配置管理 pydantic-settings + .env @ConfigurationProperties + application.yml
多环境配置 .env.development / .env.test / .env.production spring.profiles.active
容器化 多阶段 Dockerfile Jib / Spring Boot Maven Plugin
多服务编排 docker-compose Docker Compose / Kubernetes
反向代理 Nginx Spring Cloud Gateway / Nginx
进程管理 Gunicorn + UvicornWorker Spring Boot Embedded Tomcat
健康检查 /health + Docker HEALTHCHECK Spring Actuator /health
密钥管理 环境变量 + .gitignore Spring Cloud Vault / Kubernetes Secret

🎯 金句:代码写得再好,部署做不好,也只是本地的艺术品。Docker + 配置管理,是代码从开发机走向生产的最后一公里。


参考资料


系列完结

🎉 FastAPI 系列到此全部完结!

12 篇文章,带你从"Spring Boot 工程师的第一次 FastAPI 体验"一路走到"生产环境 Docker 部署",完整覆盖了:

路由 → 依赖注入 → 数据库 → 认证 → 异步 → 缓存 → 任务队列 → 中间件 → 测试 → OLAP → 部署

贯穿项目 shop-api 从骨架到上线,Spring Boot 到 FastAPI 的心智模型转换已在代码注释和对比表格中悄悄完成。

如果这个系列对你有帮助,欢迎在评论区留下你的实践心得。下个系列见!

相关推荐
蠢货爱好者4 小时前
Docker基础操作
运维·docker·容器
山人在山上4 小时前
docker zlmediakit 部署
docker·zlmediakit
hopsky5 小时前
phoenix docker 启动
运维·docker·容器
L_cl6 小时前
大模型应用开发 9.FastAPI ① 请求与响应
python·fastapi
2601_948810606 小时前
k8s-EFK
云原生·容器·kubernetes
Nontee8 小时前
Docker基础
docker·容器·eureka
烟雨江南aabb9 小时前
Docker第一弹 Docker是什么?
运维·docker·容器
ai产品老杨9 小时前
解耦异构算力与多协议接入:基于 Docker 与 GB28181 的企业级 AI 视频管理平台架构演进与源码交付实践
人工智能·docker·音视频
2301_803538959 小时前
Pod启动失败?K8s中Pod创建常见问题与排查指南
docker·容器·kubernetes