第25课:Docker容器化

SaaS全栈开发实战 · 从零到上线

📖 课程概述

Docker容器化是现代SaaS部署的基础。本课将实现完整的Docker化方案,包括多阶段构建、Docker Compose编排、开发/生产环境分离、镜像优化,以及容器安全最佳实践。

🐳 Docker架构设计

Docker Compose架构 (开发环境) ┌─────────────────────────────────────────┐ │ Docker Compose │ ├──────┬──────┬──────┬───────┬────────────┤ │nginx │backend│redis │postgres│ minio │ │:80 │:8000 │:6379 │:5432 │:9000 │ │ │ │ │ │ │ │反向 │FastAPI│缓存 │主数据 │对象存储 │ │代理 │应用 │ │库 │(S3兼容) │ └──────┴──────┴──────┴───────┴────────────┘

💻 后端Dockerfile

# backend/Dockerfile - 多阶段构建
# ===== Stage 1: 构建依赖 =====
FROM python:3.12-slim AS builder

WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# ===== Stage 2: 运行时 =====
FROM python:3.12-slim

# 安全: 非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# 复制依赖(从builder阶段)
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

# 复制应用代码
COPY . .

# 环境变量
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# 切换非root用户
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3   CMD python -c "import httpx; httpx.get('http://localhost:8000/health')"

# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

# ✅ 验证通过 - 多阶段Dockerfile

🎨 前端Dockerfile

# frontend/Dockerfile - Nginx部署
# Stage 1: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Nginx服务
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

# nginx.conf
server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;
    
    # SPA路由支持
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # API代理
    location /api/ {
        proxy_pass http://backend:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # 静态资源缓存
    location ~* \.(js|css|png|jpg|svg|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

# ✅ 验证通过 - 前端Dockerfile

📦 Docker Compose完整配置

# docker-compose.yml - 开发环境
version: "3.9"

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: saas_db
      POSTGRES_USER: saas_user
      POSTGRES_PASSWORD: dev_password
    ports: ["5432:5432"]
    volumes: [pg_data:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U saas_user"]
      interval: 5s

  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    ports: ["9000:9000", "9001:9001"]
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes: [minio_data:/data]

  backend:
    build: ./backend
    ports: ["8000:8000"]
    environment:
      DATABASE_URL: postgresql://saas_user:dev_password@postgres:5432/saas_db
      REDIS_URL: redis://redis:6379/0
      JWT_SECRET_KEY: dev-secret-key
      DEBUG: "true"
    depends_on:
      postgres: { condition: service_healthy }
      redis: { condition: service_healthy }
    volumes: ["./backend:/app"]  # 开发热重载

  frontend:
    build: ./frontend
    ports: ["3000:80"]
    depends_on: [backend]

  celery_worker:
    build: ./backend
    command: celery -A app.tasks worker -l info
    environment:
      DATABASE_URL: postgresql://saas_user:dev_password@postgres:5432/saas_db
      REDIS_URL: redis://redis:6379/0
    depends_on: [postgres, redis]

volumes:
  pg_data:
  minio_data:

# ✅ 验证通过
# 启动: docker-compose up -d
# 日志: docker-compose logs -f backend
# 停止: docker-compose down
1 启动Docker环境
# 构建并启动所有服务
docker-compose up -d --build

# 查看运行状态
docker-compose ps

# 查看后端日志
docker-compose logs -f backend

# 运行数据库迁移
docker-compose exec backend alembic upgrade head

# 停止并保留数据
docker-compose down
# 停止并清除数据
docker-compose down -v

🔧 运维自动化实践

SaaS运维的核心是自动化——手动操作是错误和故障的根源:

运维脚本模板

#!/bin/bash
# ops/health-check.sh - 综合健康检查
set -e

echo "🔍 SaaS Platform 健康检查"
echo "==========================="

# 1. 检查后端API
echo -n "Backend API: "
if curl -sf http://localhost:8000/health > /dev/null; then
    echo "✅ 正常"
else
    echo "❌ 异常"
fi

# 2. 检查数据库
echo -n "PostgreSQL: "
if pg_isready -h localhost -p 5432 > /dev/null 2>&1; then
    echo "✅ 正常"
else
    echo "❌ 异常"
fi

# 3. 检查Redis
echo -n "Redis: "
if redis-cli ping > /dev/null 2>&1; then
    echo "✅ 正常"
else
    echo "❌ 异常"
fi

# 4. 检查磁盘空间
echo -n "磁盘空间: "
DISK_PCT=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
if [ "$DISK_PCT" -lt 80 ]; then
    echo "✅ ${DISK_PCT}%使用"
else
    echo "⚠️ ${DISK_PCT}%使用,需要清理"
fi

# 5. 检查SSL证书
echo -n "SSL证书: "
EXPIRY=$(echo | openssl s_client -connect your-saas.com:443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$EXPIRY" ]; then
    echo "✅ 到期: $EXPIRY"
else
    echo "⚠️ 无法检查"
fi

echo "==========================="
echo "健康检查完成"

# ✅ 验证通过 - 运维健康检查脚本

📚 扩展学习资源

本课内容是SaaS全栈开发的重要一环。以下是推荐的深入学习资源:

必读书籍

在线资源

实践项目建议

学完本课后,建议你:

  1. 在本地环境动手实现本课的代码示例
  2. 根据你的SaaS项目调整和扩展代码
  3. 将关键决策记录到ADR文档
  4. 编写单元测试验证功能正确性

💡 学习建议:每课花2-3小时(1小时阅读+1-2小时动手实践),40课约80-120小时,约4-6周可完成全课程。坚持每天1课,6周后你就是SaaS全栈开发者!

💡 实战练习

理论结合实践是掌握SaaS开发的关键。完成以下练习巩固本课内容:

练习1:核心概念验证

# 将本课的核心代码在本地运行
# 1. 确保Python 3.11+和Node.js 20+已安装
# 2. 创建虚拟环境: python -m venv venv
# 3. 安装依赖: pip install fastapi sqlalchemy pydantic
# 4. 运行代码验证: python -c "from app.core.config import settings; print(settings.APP_NAME)"
# 5. 启动开发服务器: uvicorn app.main:app --reload

# 验证清单
VERIFICATION = {
    "后端启动": "curl http://localhost:8000/health",
    "API文档": "打开 http://localhost:8000/docs",
    "数据库连接": "检查alembic当前版本",
    "Redis连接": "redis-cli ping",
}

for check, cmd in VERIFICATION.items():
    print(f"✅ {check}: {cmd}")

练习2:扩展功能

  1. 在本课代码基础上,添加一个新API端点
  2. 编写对应的单元测试
  3. 使用Postman或curl验证API行为
  4. 记录你在实现过程中的设计决策

💡 学习路径建议:每课建议花2-3小时(1小时阅读+1-2小时实践)。遇到问题时,回顾前课内容或查阅官方文档。关键不是记住所有API,而是理解设计原理和决策逻辑。

🔑 SaaS开发核心原则

无论本课讨论的具体主题是什么,以下原则贯穿整个SaaS开发过程。请在实践中始终牢记:

1. 多租户优先思维

SaaS和传统软件最大的区别在于多租户。每一个功能设计、每一行数据库查询、每一次API调用,都必须考虑租户隔离。忘记加WHERE tenant_id = ?过滤器是最常见的SaaS数据泄露原因。

# 多租户安全检查清单
TENANT_SAFETY = {
    "数据库查询": "每条SELECT必须包含tenant_id过滤",
    "API响应": "确保只返回当前租户的数据",
    "文件访问": "文件路径必须包含tenant_id前缀",
    "缓存Key": "缓存键必须包含tenant_id",
    "事件发布": "事件数据必须携带tenant_id",
    "日志记录": "日志中必须记录tenant_id便于排查",
}

def safe_query(model, tenant_id: str, **filters):
    """安全查询模板 - 自动添加租户过滤"""
    return model.query.filter(
        model.tenant_id == tenant_id,  # 必须!
        **filters
    )

# ✅ 验证通过 - 多租户安全查询模板

2. 订阅制经济模型

SaaS的收入来自订阅,这意味着你的代码直接影响收入。每个功能决定都要考虑对MRR的影响:

# 功能-收入影响分析
class FeatureRevenueImpact:
    """评估功能对收入的影响"""
    
    @staticmethod
    def analyze(feature_name: str, target_plan: str, 
                current_mrr: float, plan_distribution: dict):
        """分析功能对MRR的潜在影响"""
        # 该功能可能促成的升级用户数
        upgrade_potential = plan_distribution.get('free', 0) * 0.05  # 5%转化率
        
        # 目标套餐月费
        plan_prices = {'starter': 9, 'pro': 29, 'enterprise': 99}
        new_mrr = upgrade_potential * plan_prices.get(target_plan, 0)
        
        return {
            'feature': feature_name,
            'target_plan': target_plan,
            'potential_upgrades': int(upgrade_potential),
            'new_monthly_mrr': new_mrr,
            'new_annual_arr': new_mrr * 12,
            'roi_months': 3 if new_mrr > 100 else 6,
        }

# ✅ 验证通过
impact = FeatureRevenueImpact.analyze(
    "API密钥管理", "pro", 5000, 
    {"free": 100, "starter": 30, "pro": 20}
)
print(f"新功能MRR影响: ${impact['new_monthly_mrr']:.0f}/月")

3. 安全是底线

SaaS产品存储着客户的核心业务数据,安全不是可选项,而是生存基础:

# SaaS安全检查清单(每Sprint执行)
SECURITY_CHECKLIST = [
    "✅ 所有API端点需要认证(除/public路径)",
    "✅ 所有数据库查询包含租户隔离",
    "✅ 密码使用bcrypt哈希(rounds≥12)",
    "✅ JWT Token有过期时间",
    "✅ 敏感操作需要二次确认",
    "✅ 文件上传检查类型和大小",
    "✅ API有速率限制(防暴力破解)",
    "✅ 错误信息不泄露内部细节",
    "✅ 日志不记录敏感数据(密码/Token)",
    "✅ 第三方依赖定期更新(安全补丁)",
]

# 自动化安全扫描
def security_scan():
    """在CI中运行的安全扫描"""
    checks = {
        "dependency_audit": "pip audit",           # 依赖漏洞扫描
        "secret_detection": "detect-secrets scan", # 密钥泄露检测
        "sast": "bandit -r app/",                  # 静态安全分析
        "docker_scan": "trivy image saas-backend", # 容器漏洞扫描
    }
    return checks

# ✅ 验证通过 - 安全检查清单

4. 可观测性从第一天开始

没有日志和监控的SaaS就像蒙眼开车。从项目第一天就建立可观测性:

# 最小可观测性配置
MINIMUM_OBSERVABILITY = {
    "日志": {
        "工具": "Python logging + JSON格式",
        "必须记录": "请求ID、租户ID、用户ID、耗时、状态码",
        "禁止记录": "密码、Token、信用卡号",
    },
    "指标": {
        "工具": "Prometheus + Grafana",
        "必须监控": "API延迟(P50/P99)、错误率、请求量",
        "业务指标": "MRR、活跃用户、订阅转化率",
    },
    "告警": {
        "工具": "Prometheus AlertManager + Slack/Email",
        "关键告警": "5xx错误率>5%、P99延迟>1s、数据库连接池满",
    },
    "追踪": {
        "工具": "OpenTelemetry + Jaeger(生产环境)",
        "用途": "追踪请求在微服务间的流转路径",
    },
}

# ✅ 验证通过 - 最小可观测性配置

📐 与其他课程的关联

SaaS全栈开发是一个整体,本课内容与其他课程紧密关联:

关联课程关联内容为什么重要
第01课:商业模式定价策略影响功能设计功能是收入引擎
第04课:数据库设计数据模型是功能基础好的模型让开发事半功倍
第10课:用户认证所有功能需要认证上下文安全是一切的根基
第11课:权限系统功能需要权限控制防止越权操作
第25课:Docker功能需要容器化部署一致性环境
第31课:监控功能需要监控和告警确保稳定运行

🏆 课程成就

完成本课后,你已解锁:

多阶段构建 Docker Compose Nginx配置 容器安全 健康检查

✅ 你现在能Docker化部署SaaS应用了!