3.4 Docker 终极指南:一键部署你的多角色智能体系统

1 阅读1分钟

导语:我们已经成功地在本地开发并运行了“旅小智”这个由前端、后端和 AI 核心组成的全栈应用。但是,我们的“征途”还未结束。如何将这个由多个服务组成的复杂系统,方便、可靠地部署到任何地方?如何让一个新同事仅用一个命令就将整个应用跑起来?答案,就在于 Docker Compose。在本章中,我们将学习如何为“旅小智”的每个部分(FastAPI 后端、Streamlit 前端)分别编写 Dockerfile,然后使用 Docker Compose 这根“魔法棒”,将它们编排成一个有机的、一键启动的整体。这将是你从部署单个容器到部署完整微服务应用的决定性一步。

目录

  1. 从单个容器到多容器应用:为什么需要 Docker Compose?
    • 回顾:docker run 的局限性
    • Docker Compose 的角色:多容器应用的“总指挥”
    • docker-compose.yml:定义你的应用“天团”
  2. 第一步:为每个服务创建独立的 Dockerfile
    • app/Dockerfile:后端 FastAPI 服务的容器化清单
    • ui/Dockerfile:前端 Streamlit 服务的容器化清单
    • 保持 Dockerfile 的简洁与专注
  3. 第二步:编写 docker-compose.yml
    • services: 定义应用的组成部分(backend, frontend
    • build: 指定每个服务对应的 Dockerfile 路径
    • ports: 将容器端口映射到宿主机
    • volumes: 实现代码热重载与数据持久化
    • environment / env_file: 管理敏感信息和环境变量
    • depends_on: 定义服务间的启动依赖关系
    • networks: 创建一个共享网络,让容器间通过“服务名”通信
  4. 第三步:网络与通信的关键
    • Compose 网络:backend 服务如何被 frontend 服务找到?
    • 修改 Streamlit 代码:将 API 地址从 localhost 改为后端的服务名(http://backend:8000
  5. 第四步:一键启动与管理
    • docker-compose up --build: 构建并启动你的全栈 AI 应用
    • docker-compose down: 停止并移除所有相关容器和网络
    • 查看日志:docker-compose logs -f <service_name>
  6. 实战演练:部署“旅小智”
    • 将所有文件放在正确的位置
    • 运行 docker-compose up,见证奇迹
    • 在浏览器中访问 Streamlit UI,并验证其与后端容器的通信
  7. 总结:迈向专业的微服务部署

1. 从单个容器到多容器应用:为什么需要 Docker Compose?

2.6 节中,我们学会了用 docker builddocker run 来部署单个服务。对于 DeepResearch 项目,这已经足够了。但“旅小智”是一个多服务应用,它至少包含两个需要同时运行的进程:

  1. FastAPI 后端服务。
  2. Streamlit 前端服务。

我们可以手动管理它们:

  • 打开终端 A,运行 docker run ... 启动后端容器。
  • 打开终端 B,运行 docker run ... 启动前端容器。
  • 手动创建一个 Docker 网络,让它们可以互相通信。
  • ...

这个过程非常繁琐、易错,并且难以自动化。

Docker Compose 的角色:多容器应用的“总指挥”

Docker Compose 是 Docker 官方提供的、用于定义和运行多容器 Docker 应用的工具。它允许你使用一个 YAML 文件(docker-compose.yml)来配置应用的所有服务,然后用一个简单的命令,就可以根据你的配置创建和启动所有服务。

它就像一个“总指挥”,你把所有“兵种”(服务)的配置都写在一张“作战计划”(docker-compose.yml)上,然后一声令下(docker-compose up),所有部队都能协同作战。

2. 第一步:为每个服务创建独立的 Dockerfile

我们的项目结构已经为这一步做好了准备。我们需要在 app/ui/ 目录下分别创建 Dockerfile。

app/Dockerfile

这个文件与我们在 2.6 节中为 FastAPI 服务编写的 Dockerfile 基本相同。

# trip-genius/app/Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY ./app /app

# 注意:这里我们假设 requirements.txt 在 app 目录下
RUN pip install --no-cache-dir -r requirements.txt

# 端口声明
EXPOSE 8000

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

ui/Dockerfile

前端服务的 Dockerfile也非常类似,只是启动命令和暴露的端口不同。

# trip-genius/ui/Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY ./ui /app

# 注意:这里我们假设 requirements.txt 在 ui 目录下
RUN pip install --no-cache-dir -r requirements.txt

# Streamlit 默认端口是 8501
EXPOSE 8501

# 启动命令
CMD ["streamlit", "run", "chat_app.py", "--server.port", "8501", "--server.address", "0.0.0.0"]

3. 第二步:编写 docker-compose.yml

这是本章的核心。在项目的根目录trip-genius/)下,创建 docker-compose.yml 文件。

# trip-genius/docker-compose.yml

version: '3.8' # 指定 compose 文件版本

services:
  # --- 后端服务定义 ---
  backend:
    build:
      context: . # Dockerfile 的上下文路径
      dockerfile: app/Dockerfile # 指定 Dockerfile 的位置
    ports:
      - "8000:8000" # 将宿主机的 8000 端口映射到容器的 8000 端口
    env_file:
      - .env # 从 .env 文件加载环境变量
    volumes:
      - ./app:/app # 将本地 app 目录挂载到容器的 /app 目录,实现代码热重载
      - ./agents:/app/agents # 同样挂载 agents 目录
      - sqlite_data:/app/data # 将 sqlite 数据库文件持久化到名为 sqlite_data 的卷中
    networks:
      - trip_genius_net # 将服务连接到我们自定义的网络

  # --- 前端服务定义 ---
  frontend:
    build:
      context: .
      dockerfile: ui/Dockerfile
    ports:
      - "8501:8501" # 映射 Streamlit 的端口
    volumes:
      - ./ui:/app # 实现前端代码的热重载
    depends_on:
      - backend # 确保后端服务启动后,再启动前端
    networks:
      - trip_genius_net

# --- 网络定义 ---
networks:
  trip_genius_net:
    driver: bridge # 使用默认的桥接网络驱动

# --- 卷定义 ---
volumes:
  sqlite_data: # 定义一个命名的卷,用于持久化数据库

docker-compose.yml 指令解读

  • services: 定义了组成我们应用的两个服务:backendfrontend
  • build.context: 设置为 . (根目录),这样 Dockerfile 在构建时就可以访问到项目的所有文件(比如从 app/ 目录 COPY ./agents)。
  • build.dockerfile: 精确指定了每个服务使用的 Dockerfile。
  • ports: 和 docker run -p 一样,负责端口映射,让我们可以从宿主机访问容器内的服务。
  • env_file: 从根目录下的 .env 文件加载环境变量,并注入到 backend 容器中。
  • volumes: 这是开发时的一大利器。
    • ./app:/app 将我们本地的 app 目录“覆盖”到容器的 /app 目录。这意味着当我们在本地修改了 main.py,容器内的文件会实时同步uvicorn 的热重载会立即生效,我们无需重新构建镜像就能看到改动。
    • sqlite_data:/app/data 创建一个命名的卷(named volume)。Docker 会管理这个卷,确保即使容器被删除,卷中的数据(我们的数据库文件)也能保留下来,实现了数据持久化。
  • depends_on: 定义了服务间的依赖关系。这里表示 frontend 服务会等待 backend 服务启动完成后再启动,避免了前端启动时后端还未就绪的错误。
  • networks: 这是实现容器间通信的关键。我们创建了一个名为 trip_genius_net 的自定义网络。所有加入这个网络的服务,都可以通过它们的服务名backend, frontend)作为主机名(hostname)来互相访问。

4. 第三步:网络与通信的关键

Docker Compose 最神奇的地方之一就是它内置的 DNS 服务。对于 frontend 容器来说,主机名 backend 会被自动解析为 backend 容器的内部 IP 地址。

因此,我们必须修改前端代码,使其不再访问 localhost

修改 ui/chat_app.py

# ui/chat_app.py

# ...
import os

# --- 后端 API 地址 ---
# 不再硬编码,而是从环境变量读取,如果不存在则默认为本地开发地址
# 这使得我们的代码更具灵活性
BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8000")

# ...
# 在调用 requests 时,使用这个变量
# with requests.post(f"{BACKEND_URL}/invoke", ...)
# ...

现在,我们还需要在 docker-compose.yml 中为 frontend 服务设置这个环境变量。

修改 docker-compose.yml

# ...
services:
  backend:
    # ...
  frontend:
    # ...
    environment:
      - BACKEND_URL=http://backend:8000 # 关键!
    # ...

现在,当 frontend 容器启动时,它内部的 chat_app.py 代码会读取到 BACKEND_URL 这个环境变量,其值为 http://backend:8000。当它发起 requests 调用时,就会正确地访问到同一网络下的 backend 容器的 8000 端口。

5. 第四步:一键启动与管理

现在,所有的配置都已就绪。

启动应用

在项目根目录(trip-genius/)下,打开一个终端,运行:

docker-compose up --build
  • docker-compose up: 根据 docker-compose.yml 文件启动并运行所有服务。
  • --build: 在启动前,强制重新构建镜像。第一次运行时需要,后续如果 Dockerfile 没有改变则可以省略。
  • (可以加上 -d 在后台运行)

你会看到 Docker Compose 依次构建 backendfrontend 的镜像,然后按顺序启动容器。两个服务的日志会交错地显示在同一个终端中,非常便于观察整体情况。

停止应用

当你想关闭整个应用时,在同一个终端按下 Ctrl+C,或者如果是在后台运行,则执行:

docker-compose down

这个命令会优雅地停止并移除所有相关的容器和网络,让你的系统恢复干净。

查看特定服务的日志

如果日志太多太乱,你可以只看某个服务的日志:

docker-compose logs -f frontend

6. 实战演练:部署“旅小智”

  1. 确保你的项目文件结构与设计的一致。
  2. 确保 app/Dockerfile, ui/Dockerfile, docker-compose.yml 的内容正确无误。
  3. 在根目录创建 .env 文件并填入你的 API keys。
  4. 运行 docker-compose up --build
  5. 等待所有服务启动成功。
  6. 在浏览器中打开 http://localhost:8501
  7. 开始与“旅小智”对话,观察终端中 backendfrontend 服务的日志,验证它们之间的通信和 AI 逻辑是否正常工作。

7. 总结:迈向专业的微服务部署

通过本章,你已经掌握了使用 Docker Compose 部署一个完整、多服务 AI 应用的“屠龙之技”。

你不再将应用视为一个单一的、巨大的整体,而是学会了将其拆分为多个职责单一的微服务backend, frontend),并使用 Docker Compose 这个强大的工具来编排它们。

这不仅仅是一种部署技巧,更是一种现代化的软件架构思想。它让你的应用:

  • 更易于维护:你可以独立地更新或修改某个服务,而无需触动整个系统。
  • 更具扩展性:未来当后端压力过大时,你可以轻易地通过 docker-compose up --scale backend=3 来水平扩展后端服务的实例数量。
  • 更贴近生产:几乎所有的现代云原生应用都是以类似的方式进行组织和部署的。

你已经为将你的 AI 应用部署到真实的生产云环境(如 Kubernetes)打下了最坚实的基础。