> ## Documentation Index
> Fetch the complete documentation index at: https://docs.akria.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Day 19 | 生产部署与配置

> 告别裸奔：环境变量管理、Gunicorn 进程模型与 Docker 容器化最佳实践

<Info>
  **今日目标**：在本地跑通代码只是第一步，能在生产环境稳定运行才是真本事。今天我们要学习 **12-Factor App** 开发原则，学会用 `.env` 管理敏感配置，并打出你的第一个 Docker 镜像。今天不只是写代码，而是要彻底理解 **"为什么不能硬编码配置"**、**"Gunicorn 和 Uvicorn 的关系"** 以及 **"如何设计生产级的部署方案"**。
</Info>

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="book" size={26} color="#059669" /> 学习内容 (30 mins)</span>

在开始写代码前，先搞懂这些核心概念，否则后面的代码你会看得云里雾里。

<AccordionGroup>
  <Accordion title="12-Factor App 原则 (15 mins)" icon="check">
    **什么是 12-Factor App？**

    12-Factor App 是一套构建现代 Web 应用的最佳实践，由 Heroku 提出。它强调应用的可移植性、可扩展性和可维护性。

    **核心原则**：

    * **配置外置**：配置信息（如数据库密码）应该存储在环境变量中，而不是代码里
    * **无状态**：应用应该是无状态的，任何状态都应该存储在数据库或缓存中
    * **日志即流**：日志应该作为事件流输出，而不是写入文件

    **为什么配置不能写死在代码里？**

    * **安全性**：代码可能被提交到 Git，敏感信息会泄露
    * **灵活性**：不同环境（开发、测试、生产）需要不同配置
    * **可维护性**：修改配置不需要改代码，只需改环境变量

    **环境变量的优势**：

    * **安全性**：不会出现在代码仓库中
    * **灵活性**：不同环境可以有不同的值
    * **标准化**：所有配置都通过环境变量管理，统一规范
  </Accordion>

  <Accordion title="Uvicorn vs Gunicorn (15 mins)" icon="server">
    **Uvicorn 是什么？**

    Uvicorn 是一个高性能的 ASGI 服务器，专注于处理 HTTP 请求。

    * **优点**：轻量、快速、支持异步
    * **缺点**：单进程，如果进程崩溃，服务就停了
    * **适用场景**：开发环境、小规模应用

    **Gunicorn 是什么？**

    Gunicorn 是一个进程管理器，负责管理多个 Worker 进程。

    * **优点**：多进程、自动重启、负载均衡
    * **缺点**：需要配合 Worker（如 Uvicorn）使用
    * **适用场景**：生产环境

    **为什么生产环境要用 Gunicorn + Uvicorn？**

    * **高可用性**：一个进程崩溃，其他进程继续工作
    * **性能提升**：多个进程可以并行处理请求
    * **自动重启**：进程异常退出时自动重启
    * **负载均衡**：自动分配请求到不同的 Worker

    **类比**：

    * **Uvicorn**：单个服务员（Worker）
    * **Gunicorn**：餐厅经理（Manager），管理多个服务员

    **部署架构**：

    ```
    客户端请求
        ↓
    Gunicorn (Manager)
        ↓
    Uvicorn Workers (多个 Worker 进程)
        ↓
    FastAPI 应用
    ```
  </Accordion>
</AccordionGroup>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="code" size={26} color="#059669" /> 代码任务 (90 mins)</span>

<Steps>
  <Step title="环境准备">
    确保虚拟环境已激活，并安装必要的包：

    ```bash theme={null}
    # 确保虚拟环境已激活
    source .venv/bin/activate

    # 安装 pydantic-settings（用于读取环境变量）
    pip install pydantic-settings

    # 安装 python-dotenv（用于读取 .env 文件）
    pip install python-dotenv

    # 生产环境还需要 Gunicorn
    pip install gunicorn
    ```
  </Step>

  <Step title="任务 A：优雅的配置管理">
    使用 `pydantic-settings` 管理配置。

    **任务分解**：

    1. 创建 `.env` 文件存储配置
    2. 创建 `config.py` 读取配置
    3. 在应用中使用配置

    <CodeGroup>
      ```text .env theme={null}
      # Day 19 - 环境变量配置
      # 注意：这个文件应该添加到 .gitignore，不要提交到 Git！

      # 应用配置
      APP_NAME=Production Ops API
      DEBUG=False

      # 数据库配置
      # 真实的生产库地址（示例）
      DATABASE_URL=mysql+aiomysql://root:root@localhost/infra_db

      # 服务器配置
      HOST=0.0.0.0
      PORT=8000
      ```

      ```python config.py theme={null}
      #!/usr/bin/env python3
      """
      Day 19 - 配置管理
      使用 pydantic-settings 读取环境变量
      """

      from pydantic_settings import BaseSettings
      from typing import Optional

      class Settings(BaseSettings):
          """
          应用配置类
          自动从环境变量或 .env 文件读取配置
          """
          # 如果环境变量里没有 APP_NAME，就用默认值 "Ops API"
          app_name: str = "Ops API"
          
          # Pydantic 会自动把字符串 "False" 转成布尔值 False
          debug: bool = False
          
          # 这是必填项！如果环境变量里没找着，启动时直接报错
          # 防止带着空配置裸奔
          database_url: str
          
          # 服务器配置（可选，有默认值）
          host: str = "0.0.0.0"
          port: int = 8000
          
          class Config:
              # 告诉它优先读这个文件，找不到文件就读系统变量
              env_file = ".env"
              # 自动将字段名转换为大写（APP_NAME -> app_name）
              case_sensitive = False

      # 全局单例
      # 整个应用只需要一个配置实例
      settings = Settings()

      # 使用示例：
      # from config import settings
      # print(settings.database_url)
      ```
    </CodeGroup>

    **代码解释**：

    * **`BaseSettings`**：Pydantic 提供的配置基类
    * **`env_file = ".env"`**：优先从 `.env` 文件读取
    * **`case_sensitive = False`**：不区分大小写（`APP_NAME` 和 `app_name` 都可以）
    * **默认值**：如果环境变量不存在，使用默认值
    * **必填字段**：没有默认值的字段，如果环境变量不存在会报错

    **验证步骤**：

    1. 创建 `.env` 文件
    2. 创建 `config.py` 文件
    3. 测试读取配置：
       ```python theme={null}
       from config import settings
       print(settings.app_name)
       print(settings.database_url)
       ```

    **重要提示**：

    <Warning>
      **`.env` 文件必须添加到 `.gitignore`！**

      创建或编辑 `.gitignore` 文件：

      ```bash theme={null}
      echo ".env" >> .gitignore
      ```

      原因：`.env` 文件包含敏感信息（如数据库密码），不应该提交到 Git。
    </Warning>
  </Step>

  <Step title="任务 B：编写 Dockerfile">
    创建 Dockerfile，将应用容器化。

    <CodeGroup>
      ```dockerfile Dockerfile theme={null}
      # Day 19 - FastAPI 应用 Dockerfile
      # 生产级 Docker 镜像构建配置

      # ========== 1. 选择基础镜像 ==========
      # python:3.10-slim: Python 3.10 的精简版本
      # slim 版本去掉了 gcc 等编译工具，体积小（约 150MB）
      # 如果应用需要编译 C 扩展，使用 python:3.10
      FROM python:3.10-slim

      # ========== 2. 设置工作目录 ==========
      # 容器内的工作目录，后续命令都在这个目录执行
      WORKDIR /app

      # ========== 3. 设置环境变量 ==========
      # 防止 Python 生成 .pyc 文件
      ENV PYTHONDONTWRITEBYTECODE=1
      # 防止 Python 缓冲输出（日志实时显示）
      ENV PYTHONUNBUFFERED=1

      # ========== 4. 先复制依赖清单（利用 Docker 缓存层）==========
      # 如果 requirements.txt 没变，这一行和下一行 RUN 只会执行一次
      # 这样可以加快构建速度
      COPY requirements.txt .

      # ========== 5. 安装依赖 ==========
      # --no-cache-dir: 不缓存安装包，进一步减小体积
      # --upgrade: 升级 pip 到最新版本
      RUN pip install --no-cache-dir --upgrade pip && \
          pip install --no-cache-dir -r requirements.txt

      # ========== 6. 复制剩下的所有源码 ==========
      # 注意：先复制 requirements.txt，再复制源码
      # 这样如果源码改了但依赖没变，Docker 可以复用之前的缓存层
      COPY . .

      # ========== 7. 暴露端口 ==========
      # 仅作为文档说明，实际端口映射在 docker run 时指定
      EXPOSE 8000

      # ========== 8. 启动命令（经理带工人模式）==========
      # Gunicorn 作为进程管理器，管理多个 Uvicorn Worker
      # -w 4: 启动 4 个 Worker 进程（通常设置为 CPU 核心数 x 2）
      # -k uvicorn.workers.UvicornWorker: 指定 Worker 类型为 Uvicorn
      # -b 0.0.0.0:8000: 绑定 IP 和端口（0.0.0.0 表示监听所有网络接口）
      # --timeout 120: 请求超时时间（秒）
      # --access-logfile -: 访问日志输出到标准输出
      # --error-logfile -: 错误日志输出到标准错误
      CMD ["gunicorn", "19_main:app", \
           "-w", "4", \
           "-k", "uvicorn.workers.UvicornWorker", \
           "-b", "0.0.0.0:8000", \
           "--timeout", "120", \
           "--access-logfile", "-", \
           "--error-logfile", "-"]
      ```

      ```text requirements.txt theme={null}
      # Day 19 - 生产环境依赖
      fastapi==0.104.1
      uvicorn[standard]==0.24.0
      gunicorn==21.2.0
      pydantic-settings==2.1.0
      python-dotenv==1.0.0
      sqlalchemy==2.0.23
      aiomysql==0.2.0
      ```

      ```python 19_main.py theme={null}
      #!/usr/bin/env python3
      """
      Day 19 - 生产级 FastAPI 应用
      演示如何使用环境变量配置和 Gunicorn 部署
      """

      from fastapi import FastAPI
      from config import settings

      # 使用配置初始化应用
      app = FastAPI(
          title=settings.app_name,
          description="生产级 FastAPI 应用示例",
          version="1.0.0",
          debug=settings.debug
      )

      @app.get("/")
      def read_root():
          """
          根路由：返回应用信息
          """
          return {
              "app_name": settings.app_name,
              "status": "running",
              "debug": settings.debug
          }

      @app.get("/health")
      def health_check():
          """
          健康检查接口
          用于 Kubernetes/Docker 的健康检查
          """
          return {"status": "healthy"}
      ```
    </CodeGroup>

    **Dockerfile 解释**：

    * **多阶段构建**：先复制依赖，再复制源码，利用缓存加速构建
    * **slim 镜像**：使用精简版 Python 镜像，减小体积
    * **环境变量**：设置 Python 相关环境变量
    * **Gunicorn**：使用 Gunicorn 管理多个 Uvicorn Worker

    **验证步骤**：

    1. 创建 `requirements.txt` 文件
    2. 创建 `Dockerfile`
    3. 创建 `.dockerignore` 文件（排除不需要的文件）：
       ```text theme={null}
       .git
       .venv
       __pycache__
       *.pyc
       .env
       ```
    4. 构建镜像：
       ```bash theme={null}
       docker build -t my-fastapi-app .
       ```
    5. 运行容器：
       ```bash theme={null}
       docker run -d -p 8000:8000 \
         --name api-prod \
         --env-file .env \
         my-fastapi-app
       ```
    6. 检查日志：
       ```bash theme={null}
       docker logs -f api-prod
       ```
       * 应该看到：`[INFO] Booting worker with pid: xxx`

    **常见错误**：

    <Warning>
      * ❌ `ModuleNotFoundError` - requirements.txt 中缺少依赖，检查依赖列表
      * ❌ `Can't connect to MySQL server` - 数据库配置错误，检查 DATABASE\_URL
      * ❌ `Address already in use` - 端口被占用，检查端口映射
      * ❌ `docker: command not found` - Docker 未安装，需要先安装 Docker
    </Warning>
  </Step>

  <Step title="任务 C：Docker Compose 编排">
    使用 Docker Compose 一键启动应用和数据库。

    <CodeGroup>
      ```yaml docker-compose.yaml theme={null}
      # Day 19 - Docker Compose 配置
      # 一键启动 API 和数据库服务

      version: '3.8'

      services:
        # ========== 数据库服务 ==========
        db:
          image: mysql:8.0
          container_name: mysql-api
          environment:
            MYSQL_ROOT_PASSWORD: root
            MYSQL_DATABASE: infra_db
          ports:
            - "3306:3306"
          volumes:
            # 数据持久化（可选）
            - mysql_data:/var/lib/mysql
          healthcheck:
            # 健康检查：确保数据库完全启动
            test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
            interval: 10s
            timeout: 5s
            retries: 5

        # ========== API 服务 ==========
        api:
          build: .
          container_name: fastapi-app
          command: uvicorn 19_main:app --host 0.0.0.0 --port 8000
          ports:
            - "8000:8000"
          environment:
            # 使用服务名作为主机名（Docker Compose 自动 DNS 解析）
            DATABASE_URL: mysql+aiomysql://root:root@db/infra_db
            APP_NAME: Production Ops API
            DEBUG: False
          depends_on:
            db:
              condition: service_healthy  # 等待数据库健康检查通过
          restart: unless-stopped  # 自动重启（除非手动停止）

      # ========== 数据卷 ==========
      volumes:
        mysql_data:
          # 数据持久化，即使容器删除数据也不会丢失
      ```

      ```bash 启动服务 theme={null}
      # 构建并启动所有服务
      docker-compose up --build

      # 后台运行
      docker-compose up -d --build

      # 查看日志
      docker-compose logs -f api

      # 停止服务
      docker-compose down

      # 停止并删除数据卷
      docker-compose down -v
      ```
    </CodeGroup>

    **Docker Compose 解释**：

    * **services**：定义多个服务（db、api）
    * **depends\_on**：定义服务依赖关系
    * **healthcheck**：健康检查，确保服务完全启动
    * **volumes**：数据持久化

    **验证步骤**：

    1. 创建 `docker-compose.yaml` 文件
    2. 启动服务：`docker-compose up --build`
    3. 等待服务启动完成
    4. 访问 `http://localhost:8000/health`，应该返回 `{"status": "healthy"}`
  </Step>
</Steps>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="lightbulb" size={26} color="#059669" /> 拓展任务 (30 mins)</span>

<CardGroup cols={2}>
  <Card title="挑战 1：多环境配置" icon="code">
    **任务**：创建不同环境的配置文件（`.env.dev`、`.env.prod`），并在 Docker Compose 中使用。

    **提示**：

    ```yaml theme={null}
    env_file:
      - .env.prod
    ```
  </Card>

  <Card title="挑战 2：健康检查" icon="check">
    **任务**：在 Dockerfile 中添加健康检查指令。

    **提示**：

    ```dockerfile theme={null}
    HEALTHCHECK --interval=30s --timeout=3s \
      CMD curl -f http://localhost:8000/health || exit 1
    ```
  </Card>
</CardGroup>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="file" size={26} color="#059669" /> 今日产出物</span>

* `.env` - 环境变量配置文件（不提交到 Git）
* `config.py` - 配置管理模块
* `Dockerfile` - Docker 镜像构建文件
* `docker-compose.yaml` - Docker Compose 编排文件
* `requirements.txt` - 生产环境依赖列表

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="github" size={26} color="#059669" /> 参考代码</span>

<CardGroup cols={2}>
  <Card title="查看参考代码" icon="code" href="https://github.com/akriamail/insightful-ops/tree/main/docs/mintlify/scripts/training/30daystudy/week-3/day-19">
    在 GitHub 查看完整的示例代码

    <br />

    <small>包含配置管理和 Docker 部署文件</small>
  </Card>

  <Card title="在线运行" icon="play" href="https://replit.com">
    使用在线编辑器测试代码

    <br />

    <small>无需本地环境配置</small>
  </Card>
</CardGroup>

***

## 实际应用场景

<CardGroup cols={2}>
  <Card title="生产部署在运维中的应用" icon="server">
    * **容器化部署**：使用 Docker 实现应用的可移植性
    * **环境隔离**：不同环境使用不同的配置文件
    * **高可用性**：使用 Gunicorn 多进程，提高稳定性
    * **自动化部署**：配合 CI/CD 实现自动化部署
    * **资源管理**：通过 Docker Compose 管理多个服务
  </Card>

  <Card title="配置管理最佳实践" icon="check">
    * **环境变量优先**：敏感信息存储在环境变量中
    * **配置文件分离**：不同环境使用不同的配置文件
    * **默认值设置**：为可选配置设置合理的默认值
    * **验证配置**：启动时验证必填配置是否存在
    * **文档完善**：在代码中注释配置的用途和默认值
  </Card>
</CardGroup>

<Tip>
  **与 Day 20 的关联**：今天学习的配置管理和 Docker 部署，明天会结合所有知识点，开发一个完整的生产级 API 项目。
</Tip>

***

<CardGroup cols={2}>
  <Card title="上一天: 数据库整合" href="/training/30daystudy/day-18">
    Day 18 | FastAPI 数据库整合
  </Card>

  <Card title="下一天: 阶段实战" href="/training/30daystudy/day-20">
    Day 20 | 第三阶段综合实战
  </Card>
</CardGroup>
