10.6. 部署与运维

在前面的章节中,我们完成了离线流程、在线流程和前端应用的开发。各个模块在本地开发环境中可以正常运行,但如何将它们组合成一个完整的系统,并且能够在其他机器上快速部署?本节将介绍使用 Docker Compose 进行容器化部署的方法。

10.6.1. 为什么使用 Docker Compose

本项目依赖多个服务:PostgreSQL 存储业务数据,Redis 缓存用户特征,Elasticsearch 提供搜索功能。加上后端 API 和前端应用,一共有五个服务需要启动和协调。模型文件通过共享文件目录在离线和在线服务之间传递。

如果手动部署这些服务,需要在每台机器上分别安装 PostgreSQL、Redis、Elasticsearch 等软件,配置网络连接,处理版本兼容问题。这个过程繁琐且容易出错,不同机器上的环境差异也会导致各种问题。

Docker Compose 提供了一种声明式的方式来定义和运行多容器应用。通过一个 YAML 配置文件描述所有服务及其依赖关系,然后使用一条命令即可启动整个系统。容器化部署的优势包括:

  1. 环境一致性:每个服务运行在独立的容器中,容器镜像包含了服务所需的全部依赖。无论在开发机、测试机还是生产服务器上,运行环境都是一致的。

  2. 快速启动:只需执行 docker compose up 命令,所有服务按照依赖顺序自动启动。不需要手动安装和配置各个组件。

  3. 隔离与安全:各服务在独立的容器中运行,相互隔离。即使某个服务出现问题,也不会影响其他服务。

  4. 易于扩展:需要增加新服务时,只需在配置文件中添加相应的定义,不需要修改现有服务的配置。

10.6.2. Docker Compose 配置详解

本项目的 docker-compose.yaml 文件定义了六个服务。我们逐一介绍每个服务的配置。

10.6.2.1. 数据库服务(PostgreSQL)

PostgreSQL 是系统的主数据库,存储用户信息、电影信息和评分记录。

services:
  postgres:
    image: postgres:15-alpine
    container_name: funrec-postgres
    environment:
      POSTGRES_USER: funrec
      POSTGRES_PASSWORD: funrec123
      POSTGRES_DB: funrec_db
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - funrec-network

配置说明:

  • image: postgres:15-alpine:使用 PostgreSQL 15 的 Alpine 版本镜像。Alpine 是一个轻量级 Linux 发行版,镜像体积较小。

  • environment:通过环境变量设置数据库的用户名、密码和数据库名。容器首次启动时会自动创建这些配置。

  • ports:将容器内的 5432 端口映射到宿主机的 5432 端口,使得本地开发工具可以直接连接数据库。

  • volumes:使用命名卷 postgres_data 持久化数据。容器删除后,数据仍然保留在卷中。

  • networks:将服务加入 funrec-network 网络,使其能够与其他服务通信。

10.6.2.2. 缓存服务(Redis)

Redis 用于缓存用户画像和行为序列,支持在线推理时的快速特征读取。

redis:
    image: redis:7-alpine
    container_name: funrec-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - funrec-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

healthcheck 定义了健康检查机制。Docker 会定期执行 redis-cli ping 命令,如果连续 5 次超时(每次 3 秒)则认为服务不健康。依赖 Redis 的服务可以等待 Redis 健康后再启动。

10.6.2.3. 搜索服务(Elasticsearch)

Elasticsearch 提供电影的全文搜索功能,支持标题、类型、演员的模糊匹配。

elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.2.0
    container_name: funrec-elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - funrec-network
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5

配置说明:

  • discovery.type=single-node:单节点模式,适合开发和小规模部署。生产环境通常需要多节点集群。

  • xpack.security.enabled=false:禁用安全功能,简化开发环境配置。生产环境应该启用并配置认证。

  • ES_JAVA_OPTS:限制 JVM 堆内存为 512MB。Elasticsearch 默认会使用较多内存,在开发机上需要限制。

10.6.2.4. 后端服务

后端服务是 FastAPI 应用,提供推荐 API、用户认证、电影搜索等功能。

backend:
    build:
      context: ./backend
      dockerfile: dockerfile
    container_name: funrec-backend
    ports:
      - "8000:8000"
    env_file:
      - .env
    environment:
      - DATABASE_URL=postgresql://funrec:funrec123@postgres:5432/funrec_db
      - REDIS_URL=redis://redis:6379/0
      - ELASTICSEARCH_URL=http://elasticsearch:9200
      - MODEL_DEPLOY_DIR=/app/tmp/web_project/deployed_models
      - DATA_DIR=/data
    volumes:
      - ./backend:/app
      - ../tmp:/app/tmp
      - ${FUNREC_RAW_DATA_PATH}:/data
    depends_on:
      - postgres
      - elasticsearch
      - redis
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    networks:
      - funrec-network

配置说明:

  • build:指定构建上下文和 Dockerfile 路径。Docker Compose 会根据 Dockerfile 构建镜像。

  • env_file:从 .env 文件读取环境变量。

  • environment:覆盖容器内的环境变量。注意数据库和 Redis 的地址使用服务名(如 postgresredis)而不是 localhost,因为容器之间通过服务名进行 DNS 解析。

  • volumes:挂载本地目录到容器。./backend:/app 将代码目录挂载进去,配合 --reload 参数实现代码热更新。${FUNREC_RAW_DATA_PATH} 挂载数据目录。

  • depends_on:声明依赖关系。Docker Compose 会先启动 PostgreSQL、Redis 等服务,再启动后端。

后端的 Dockerfile 定义了镜像的构建过程:

FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    postgresql-client \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 安装 uv 包管理器
RUN pip install uv

# 复制依赖文件并安装
COPY pyproject.toml ./
RUN uv pip install --system -e .

# 复制应用代码
COPY . .

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Dockerfile 采用分层构建:先安装依赖,再复制代码。这样修改代码时不需要重新安装依赖,加快构建速度。

10.6.2.5. 前端服务

前端服务使用多阶段构建,先用 Node.js 构建静态文件,再用 Nginx 提供服务。

frontend:
    build:
      context: ./frontend
      dockerfile: dockerfile
    container_name: funrec-frontend
    ports:
      - "3000:80"
    depends_on:
      - backend
    networks:
      - funrec-network

前端的 Dockerfile 实现了多阶段构建:

# 构建阶段
FROM node:22-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

多阶段构建的优势是最终镜像只包含构建产物和 Nginx,不包含 Node.js 和开发依赖,镜像体积更小。

10.6.2.6. 网络与数据

配置文件的末尾定义了共享的网络和数据卷:

volumes:
  postgres_data:
  redis_data:
  elasticsearch_data:

networks:
  funrec-network:
    driver: bridge

所有服务加入同一个桥接网络 funrec-network,可以通过服务名相互访问。命名卷用于持久化各服务的数据,即使容器删除,数据也不会丢失。

10.6.3. 环境准备与启动流程

10.6.3.1. 前置条件

在开始部署之前,需要确保系统已安装以下工具:

  • Docker:容器运行时。可以从 Docker 官网 下载安装。

  • Docker Compose:多容器编排工具。Docker Desktop 已经内置了 Compose,Linux 用户可能需要单独安装。

  • uv:Python 包管理器,用于运行离线流程。可以通过 pip install uv 安装。

验证安装:

docker --version
docker compose version
uv --version

10.6.3.2. 数据准备

本项目使用预处理后的 MovieLens-1M 数据集。数据包含用户信息、电影信息(含海报)和评分记录。

  1. 下载数据文件 funrec-movielens-1m.zip 并解压到本地目录

  2. 记录数据的绝对路径,例如 /Users/yourname/data/funrec-movielens-1m

数据目录结构如下:

funrec-movielens-1m/
├── movies.pkl          # 电影信息
├── ratings.pkl         # 评分记录
├── users.pkl           # 用户信息
└── image/              # 电影海报
    ├── 1.jpg
    ├── 2.jpg
    └── ...

10.6.3.3. 环境变量配置

项目根目录下有一个 .env.example 文件,包含了所需的环境变量模板。复制这个文件并配置:

cd web_project
cp .env.example .env

编辑 .env 文件,设置数据路径:

# 原始数据目录(上一步解压的位置)
FUNREC_RAW_DATA_PATH=/Users/yourname/data/funrec-movielens-1m

# 处理后数据的缓存目录(可自由设置)
FUNREC_PROCESSED_DATA_PATH=/Users/yourname/data/funrec-processed

FUNREC_PROCESSED_DATA_PATH 用于存储特征工程和模型训练的中间产物,需要设置为一个可写的目录。

10.6.3.4. 启动基础设施

环境准备完成后,启动所有服务:

docker compose up --build

--build 参数会重新构建后端和前端的镜像。首次运行时需要下载各服务的基础镜像,可能需要几分钟时间。

启动成功后,终端会显示各服务的日志输出。可以按 Ctrl+C 停止所有服务,或者使用 -d 参数让服务在后台运行:

docker compose up -d --build

后台运行时,可以使用以下命令查看日志:

docker compose logs -f           # 查看所有服务的日志
docker compose logs -f backend   # 只看后端日志

10.6.3.5. 运行离线流程

服务启动后,需要运行离线流程来训练模型和初始化数据。

首先进入后端目录并安装依赖:

cd backend
uv sync

然后运行离线流程:

make run-offline-pipeline

这个命令会执行以下步骤:

  1. 特征工程:处理原始数据,生成训练样本

  2. 模型训练:训练 YoutubeDNN 召回模型和 DeepFM 排序模型

  3. 特征上线:将用户画像和行为序列写入 Redis

  4. 模型部署:将模型文件部署到共享目录

整个过程大约需要 10-20 分钟,取决于机器性能。

10.6.3.6. 加载数据到数据库

离线流程完成后,将业务数据加载到 PostgreSQL:

make ingest-data-to-database

这个命令会:

  1. 创建数据库表结构

  2. 导入用户、电影、评分数据

  3. 创建测试用户(test@funrec.com / test123456

10.6.3.7. 索引电影到搜索引擎

将电影数据索引到 Elasticsearch,支持搜索功能:

make index-movies-to-elasticsearch

索引完成后,电影的标题、类型、演员等字段就可以被搜索了。

10.6.3.8. 访问应用

所有步骤完成后,可以通过以下地址访问应用:

服务

地址

说明

前端应用

http://localhost:3000

用户界面

后端 API

http://localhost:8000

API 服务

API 文档

http://localhost:8000/docs

Swagger 文档

Elasticsearch

http://localhost:9200

搜索服务

使用测试账号登录前端应用:

  • Email: test@funrec.com

  • Password: test123456

登录后可以看到个性化推荐、搜索电影、查看电影详情、提交评分等功能。

10.6.4. 服务健康检查与调试

部署过程中可能会遇到各种问题。本节介绍如何检查各服务的运行状态,以及常见问题的排查方法。

10.6.4.1. 检查服务状态

使用 docker compose ps 查看所有容器的状态:

docker compose ps

输出示例:

NAME                  STATUS          PORTS
funrec-backend        Up 2 minutes    0.0.0.0:8000->8000/tcp
funrec-elasticsearch  Up 2 minutes    0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp
funrec-frontend       Up 2 minutes    0.0.0.0:3000->80/tcp
funrec-postgres       Up 2 minutes    0.0.0.0:5432->5432/tcp
funrec-redis          Up 2 minutes    0.0.0.0:6379->6379/tcp

所有服务的状态应该显示为 Up。如果某个服务显示 ExitedRestarting,说明启动失败,需要查看日志排查原因。

10.6.4.2. 验证各服务

后端 API

curl http://localhost:8000/health

正常响应:

{"status": "healthy"}

PostgreSQL

docker exec -it funrec-postgres pg_isready -U funrec

正常输出:

/var/run/postgresql:5432 - accepting connections

Redis

docker exec -it funrec-redis redis-cli ping

正常输出:

PONG

Elasticsearch

curl http://localhost:9200

正常响应会返回 Elasticsearch 的版本信息。

10.6.4.3. 查看 Redis 中的数据

离线流程完成后,可以验证用户特征是否已写入 Redis:

# 查看某用户的偏好类型
docker exec -it funrec-redis redis-cli hget user:6041:profile frequent_genres

# 查看某用户的历史行为长度
docker exec -it funrec-redis redis-cli llen user:6041:history

10.6.4.4. 常见问题排查

问题:容器启动失败

查看失败容器的日志:

docker compose logs backend

常见原因:

  1. 环境变量未配置:检查 .env 文件是否存在且路径正确

  2. 端口被占用:检查 5432、6379、8000、9200 等端口是否被其他程序占用

  3. 依赖服务未就绪:后端启动时 PostgreSQL 可能还在初始化,重试几次通常能解决

问题:数据库连接失败

检查 PostgreSQL 容器是否正常运行:

docker compose logs postgres

如果看到 “database system is ready to accept connections”,说明数据库已就绪。

问题:模型加载失败

检查共享目录中是否有模型文件:

ls -la ${FUNREC_PROCESSED_DATA_PATH}/web_project/deployed_models/

如果没有模型文件,需要重新运行离线流程:

make run-offline-pipeline

问题:搜索无结果

检查 Elasticsearch 索引是否创建:

curl http://localhost:9200/_cat/indices

如果没有 movies 索引,需要重新执行索引命令:

make index-movies-to-elasticsearch