Chapter 81
10.6. 部署与运维
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.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。如果某个服务显示 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
正常响应会返回 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
Table Of Contents
-
10.6. 部署与运维 10.6.1. 为什么使用 Docker Compose
-
10.6.2. Docker Compose 配置详解 10.6.2.1. 数据库服务(PostgreSQL)
-
10.6.2.2. 缓存服务(Redis)
-
10.6.2.3. 搜索服务(Elasticsearch)
-
10.6.2.4. 后端服务
-
10.6.2.5. 前端服务
-
10.6.2.6. 网络与数据
-
10.6.3. 环境准备与启动流程 10.6.3.1. 前置条件
-
10.6.3.2. 数据准备
-
10.6.3.3. 环境变量配置
-
10.6.3.4. 启动基础设施
-
10.6.3.5. 运行离线流程
-
10.6.3.6. 加载数据到数据库
-
10.6.3.7. 索引电影到搜索引擎
-
10.6.3.8. 访问应用
-
10.6.4. 服务健康检查与调试 10.6.4.1. 检查服务状态
-
10.6.4.2. 验证各服务
-
10.6.4.3. 查看 Redis 中的数据
-
10.6.4.4. 常见问题排查
codePrevious 10.5. 前端与交互 Next 10.7. 本章总结
