.. _sec_projects_deployment: 部署与运维 ========== 在前面的章节中,我们完成了离线流程、在线流程和前端应用的开发。各个模块在本地开发环境中可以正常运行,但如何将它们组合成一个完整的系统,并且能够在其他机器上快速部署?本节将介绍使用 Docker Compose 进行容器化部署的方法。 为什么使用 Docker Compose ------------------------- 本项目依赖多个服务:PostgreSQL 存储业务数据,Redis 缓存用户特征,Elasticsearch 提供搜索功能。加上后端 API 和前端应用,一共有五个服务需要启动和协调。模型文件通过共享文件目录在离线和在线服务之间传递。 如果手动部署这些服务,需要在每台机器上分别安装 PostgreSQL、Redis、Elasticsearch 等软件,配置网络连接,处理版本兼容问题。这个过程繁琐且容易出错,不同机器上的环境差异也会导致各种问题。 Docker Compose 提供了一种声明式的方式来定义和运行多容器应用。通过一个 YAML 配置文件描述所有服务及其依赖关系,然后使用一条命令即可启动整个系统。容器化部署的优势包括: 1. **环境一致性**\ :每个服务运行在独立的容器中,容器镜像包含了服务所需的全部依赖。无论在开发机、测试机还是生产服务器上,运行环境都是一致的。 2. **快速启动**\ :只需执行 ``docker compose up`` 命令,所有服务按照依赖顺序自动启动。不需要手动安装和配置各个组件。 3. **隔离与安全**\ :各服务在独立的容器中运行,相互隔离。即使某个服务出现问题,也不会影响其他服务。 4. **易于扩展**\ :需要增加新服务时,只需在配置文件中添加相应的定义,不需要修改现有服务的配置。 Docker Compose 配置详解 ----------------------- 本项目的 ``docker-compose.yaml`` 文件定义了六个服务。我们逐一介绍每个服务的配置。 数据库服务(PostgreSQL) ~~~~~~~~~~~~~~~~~~~~~~~~ PostgreSQL 是系统的主数据库,存储用户信息、电影信息和评分记录。 .. raw:: latex \diilbookstyleinputcell .. code:: yaml 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`` 网络,使其能够与其他服务通信。 缓存服务(Redis) ~~~~~~~~~~~~~~~~~ Redis 用于缓存用户画像和行为序列,支持在线推理时的快速特征读取。 .. raw:: latex \diilbookstyleinputcell .. code:: yaml 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 健康后再启动。 搜索服务(Elasticsearch) ~~~~~~~~~~~~~~~~~~~~~~~~~ Elasticsearch 提供电影的全文搜索功能,支持标题、类型、演员的模糊匹配。 .. raw:: latex \diilbookstyleinputcell .. code:: yaml 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 默认会使用较多内存,在开发机上需要限制。 后端服务 ~~~~~~~~ 后端服务是 FastAPI 应用,提供推荐 API、用户认证、电影搜索等功能。 .. raw:: latex \diilbookstyleinputcell .. code:: yaml 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 定义了镜像的构建过程: .. raw:: latex \diilbookstyleinputcell .. code:: 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 采用分层构建:先安装依赖,再复制代码。这样修改代码时不需要重新安装依赖,加快构建速度。 前端服务 ~~~~~~~~ 前端服务使用多阶段构建,先用 Node.js 构建静态文件,再用 Nginx 提供服务。 .. raw:: latex \diilbookstyleinputcell .. code:: yaml frontend: build: context: ./frontend dockerfile: dockerfile container_name: funrec-frontend ports: - "3000:80" depends_on: - backend networks: - funrec-network 前端的 Dockerfile 实现了多阶段构建: .. raw:: latex \diilbookstyleinputcell .. code:: 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 和开发依赖,镜像体积更小。 网络与数据 ~~~~~~~~~~ 配置文件的末尾定义了共享的网络和数据卷: .. raw:: latex \diilbookstyleinputcell .. code:: yaml volumes: postgres_data: redis_data: elasticsearch_data: networks: funrec-network: driver: bridge 所有服务加入同一个桥接网络 ``funrec-network``\ ,可以通过服务名相互访问。命名卷用于持久化各服务的数据,即使容器删除,数据也不会丢失。 环境准备与启动流程 ------------------ 前置条件 ~~~~~~~~ 在开始部署之前,需要确保系统已安装以下工具: - **Docker**\ :容器运行时。可以从 `Docker 官网 `__ 下载安装。 - **Docker Compose**\ :多容器编排工具。Docker Desktop 已经内置了 Compose,Linux 用户可能需要单独安装。 - **uv**\ :Python 包管理器,用于运行离线流程。可以通过 ``pip install uv`` 安装。 验证安装: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker --version docker compose version uv --version 数据准备 ~~~~~~~~ 本项目使用预处理后的 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 └── ... 环境变量配置 ~~~~~~~~~~~~ 项目根目录下有一个 ``.env.example`` 文件,包含了所需的环境变量模板。复制这个文件并配置: .. raw:: latex \diilbookstyleinputcell .. code:: bash cd web_project cp .env.example .env 编辑 ``.env`` 文件,设置数据路径: .. raw:: latex \diilbookstyleinputcell .. code:: bash # 原始数据目录(上一步解压的位置) FUNREC_RAW_DATA_PATH=/Users/yourname/data/funrec-movielens-1m # 处理后数据的缓存目录(可自由设置) FUNREC_PROCESSED_DATA_PATH=/Users/yourname/data/funrec-processed ``FUNREC_PROCESSED_DATA_PATH`` 用于存储特征工程和模型训练的中间产物,需要设置为一个可写的目录。 启动基础设施 ~~~~~~~~~~~~ 环境准备完成后,启动所有服务: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker compose up --build ``--build`` 参数会重新构建后端和前端的镜像。首次运行时需要下载各服务的基础镜像,可能需要几分钟时间。 启动成功后,终端会显示各服务的日志输出。可以按 ``Ctrl+C`` 停止所有服务,或者使用 ``-d`` 参数让服务在后台运行: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker compose up -d --build 后台运行时,可以使用以下命令查看日志: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker compose logs -f # 查看所有服务的日志 docker compose logs -f backend # 只看后端日志 运行离线流程 ~~~~~~~~~~~~ 服务启动后,需要运行离线流程来训练模型和初始化数据。 首先进入后端目录并安装依赖: .. raw:: latex \diilbookstyleinputcell .. code:: bash cd backend uv sync 然后运行离线流程: .. raw:: latex \diilbookstyleinputcell .. code:: bash make run-offline-pipeline 这个命令会执行以下步骤: 1. 特征工程:处理原始数据,生成训练样本 2. 模型训练:训练 YoutubeDNN 召回模型和 DeepFM 排序模型 3. 特征上线:将用户画像和行为序列写入 Redis 4. 模型部署:将模型文件部署到共享目录 整个过程大约需要 10-20 分钟,取决于机器性能。 加载数据到数据库 ~~~~~~~~~~~~~~~~ 离线流程完成后,将业务数据加载到 PostgreSQL: .. raw:: latex \diilbookstyleinputcell .. code:: bash make ingest-data-to-database 这个命令会: 1. 创建数据库表结构 2. 导入用户、电影、评分数据 3. 创建测试用户(\ ``test@funrec.com`` / ``test123456``\ ) 索引电影到搜索引擎 ~~~~~~~~~~~~~~~~~~ 将电影数据索引到 Elasticsearch,支持搜索功能: .. raw:: latex \diilbookstyleinputcell .. code:: bash make index-movies-to-elasticsearch 索引完成后,电影的标题、类型、演员等字段就可以被搜索了。 访问应用 ~~~~~~~~ 所有步骤完成后,可以通过以下地址访问应用: ============= ========================== ============ 服务 地址 说明 ============= ========================== ============ 前端应用 http://localhost:3000 用户界面 后端 API http://localhost:8000 API 服务 API 文档 http://localhost:8000/docs Swagger 文档 Elasticsearch http://localhost:9200 搜索服务 ============= ========================== ============ 使用测试账号登录前端应用: - Email: ``test@funrec.com`` - Password: ``test123456`` 登录后可以看到个性化推荐、搜索电影、查看电影详情、提交评分等功能。 服务健康检查与调试 ------------------ 部署过程中可能会遇到各种问题。本节介绍如何检查各服务的运行状态,以及常见问题的排查方法。 检查服务状态 ~~~~~~~~~~~~ 使用 ``docker compose ps`` 查看所有容器的状态: .. raw:: latex \diilbookstyleinputcell .. code:: bash 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``\ ,说明启动失败,需要查看日志排查原因。 验证各服务 ~~~~~~~~~~ **后端 API** .. raw:: latex \diilbookstyleinputcell .. code:: bash curl http://localhost:8000/health 正常响应: .. raw:: latex \diilbookstyleinputcell .. code:: json {"status": "healthy"} **PostgreSQL** .. raw:: latex \diilbookstyleinputcell .. code:: bash docker exec -it funrec-postgres pg_isready -U funrec 正常输出: :: /var/run/postgresql:5432 - accepting connections **Redis** .. raw:: latex \diilbookstyleinputcell .. code:: bash docker exec -it funrec-redis redis-cli ping 正常输出: :: PONG **Elasticsearch** .. raw:: latex \diilbookstyleinputcell .. code:: bash curl http://localhost:9200 正常响应会返回 Elasticsearch 的版本信息。 查看 Redis 中的数据 ~~~~~~~~~~~~~~~~~~~ 离线流程完成后,可以验证用户特征是否已写入 Redis: .. raw:: latex \diilbookstyleinputcell .. code:: bash # 查看某用户的偏好类型 docker exec -it funrec-redis redis-cli hget user:6041:profile frequent_genres # 查看某用户的历史行为长度 docker exec -it funrec-redis redis-cli llen user:6041:history 常见问题排查 ~~~~~~~~~~~~ **问题:容器启动失败** 查看失败容器的日志: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker compose logs backend 常见原因: 1. 环境变量未配置:检查 ``.env`` 文件是否存在且路径正确 2. 端口被占用:检查 5432、6379、8000、9200 等端口是否被其他程序占用 3. 依赖服务未就绪:后端启动时 PostgreSQL 可能还在初始化,重试几次通常能解决 **问题:数据库连接失败** 检查 PostgreSQL 容器是否正常运行: .. raw:: latex \diilbookstyleinputcell .. code:: bash docker compose logs postgres 如果看到 “database system is ready to accept connections”,说明数据库已就绪。 **问题:模型加载失败** 检查共享目录中是否有模型文件: .. raw:: latex \diilbookstyleinputcell .. code:: bash ls -la ${FUNREC_PROCESSED_DATA_PATH}/web_project/deployed_models/ 如果没有模型文件,需要重新运行离线流程: .. raw:: latex \diilbookstyleinputcell .. code:: bash make run-offline-pipeline **问题:搜索无结果** 检查 Elasticsearch 索引是否创建: .. raw:: latex \diilbookstyleinputcell .. code:: bash curl http://localhost:9200/_cat/indices 如果没有 ``movies`` 索引,需要重新执行索引命令: .. raw:: latex \diilbookstyleinputcell .. code:: bash make index-movies-to-elasticsearch