10.6. 部署与运维¶
在前面的章节中,我们完成了离线流程、在线流程和前端应用的开发。各个模块在本地开发环境中可以正常运行,但如何将它们组合成一个完整的系统,并且能够在其他机器上快速部署?本节将介绍使用 Docker Compose 进行容器化部署的方法。
10.6.1. 为什么使用 Docker Compose¶
本项目依赖多个服务:PostgreSQL 存储业务数据,Redis 缓存用户特征,Elasticsearch 提供搜索功能。加上后端 API 和前端应用,一共有五个服务需要启动和协调。模型文件通过共享文件目录在离线和在线服务之间传递。
如果手动部署这些服务,需要在每台机器上分别安装 PostgreSQL、Redis、Elasticsearch 等软件,配置网络连接,处理版本兼容问题。这个过程繁琐且容易出错,不同机器上的环境差异也会导致各种问题。
Docker Compose 提供了一种声明式的方式来定义和运行多容器应用。通过一个 YAML 配置文件描述所有服务及其依赖关系,然后使用一条命令即可启动整个系统。容器化部署的优势包括:
环境一致性:每个服务运行在独立的容器中,容器镜像包含了服务所需的全部依赖。无论在开发机、测试机还是生产服务器上,运行环境都是一致的。
快速启动:只需执行
docker compose up命令,所有服务按照依赖顺序自动启动。不需要手动安装和配置各个组件。隔离与安全:各服务在独立的容器中运行,相互隔离。即使某个服务出现问题,也不会影响其他服务。
易于扩展:需要增加新服务时,只需在配置文件中添加相应的定义,不需要修改现有服务的配置。
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 的地址使用服务名(如postgres、redis)而不是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 数据集。数据包含用户信息、电影信息(含海报)和评分记录。
下载数据文件
funrec-movielens-1m.zip并解压到本地目录记录数据的绝对路径,例如
/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
这个命令会执行以下步骤:
特征工程:处理原始数据,生成训练样本
模型训练:训练 YoutubeDNN 召回模型和 DeepFM 排序模型
特征上线:将用户画像和行为序列写入 Redis
模型部署:将模型文件部署到共享目录
整个过程大约需要 10-20 分钟,取决于机器性能。
10.6.3.6. 加载数据到数据库¶
离线流程完成后,将业务数据加载到 PostgreSQL:
make ingest-data-to-database
这个命令会:
创建数据库表结构
导入用户、电影、评分数据
创建测试用户(
test@funrec.com/test123456)
10.6.3.7. 索引电影到搜索引擎¶
将电影数据索引到 Elasticsearch,支持搜索功能:
make index-movies-to-elasticsearch
索引完成后,电影的标题、类型、演员等字段就可以被搜索了。
10.6.3.8. 访问应用¶
所有步骤完成后,可以通过以下地址访问应用:
服务 |
地址 |
说明 |
|---|---|---|
前端应用 |
用户界面 |
|
后端 API |
API 服务 |
|
API 文档 |
Swagger 文档 |
|
Elasticsearch |
搜索服务 |
使用测试账号登录前端应用:
Email:
test@funrec.comPassword:
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。如果某个服务显示 Exited 或
Restarting,说明启动失败,需要查看日志排查原因。
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
常见原因:
环境变量未配置:检查
.env文件是否存在且路径正确端口被占用:检查 5432、6379、8000、9200 等端口是否被其他程序占用
依赖服务未就绪:后端启动时 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