AI-LLMOps 项目 Docker 全栈部署避坑与通关实录

0 阅读5分钟

📌 项目基本盘

  • 部署环境:阿里云 2核(vCPU) 2 GiB 内存服务器(Linux / Debian 环境)
  • 全栈架构:7 个核心容器服务(uiapiceleryredisdb(PostgreSQL)weaviatenginx
  • 基础环境:Python 3.13-slim-bookworm + Docker Compose (BuildKit)

🛠️ 核心血泪踩坑点与终极解决方案

  1. 国际网络连接超时 (i/o timeout)
  • 现象:刚启动时显示“只拉起了两个服务”,整个 Docker 构建在拉取 python:3.13-slim-bookworm 时因网络连接超时强行崩溃中断。

  • 根因:国内访问 Docker Hub 官方镜像仓库受限,导致依赖它的后端镜像无法继续构建,后续的 5 个服务根本没有机会排队执行。

  • 解法

    1. 巧妙在 docker-compose.yaml 中将第三方镜像(如 redisnginx)替换为国内 Daocloud 等加速源。
    2. 修改 ../api/Dockerfile 内部的第一行,将基础镜像改为 FROM docker.m.daocloud.io/library/python:3.13
  1. 禁用了 BuildKit 导致语法不支持
  • 现象:构建报错提示 --mount=type=cache 语法不被支持。
  • 根因:在旧版 Docker 引擎命令中误加了 DOCKER_BUILDKIT=0。而较新的 Dockerfile 内部为了加速 pip install 普遍使用了缓存挂载语法,必须依赖新一代构建引擎 BuildKit。
  • 解法:修改命令开头,强行开启引擎:DOCKER_BUILDKIT=1 docker compose up -d --build
  1. Python 3.13 底层依赖环境大换血 (ModuleNotFoundError)
  • 现象:网络和构建通关后,接口陷入 502 Bad Gateway,查看日志发现 ModuleNotFoundError: No module named 'jwt' 并且无限重启。

  • 根因:项目原配的 requirements.txt 中引入了 authlib==1.7.2。该库在老版本 Python 下能免安装调用 jwt,但在最新的 Python 3.13 生产环境镜像中发生底层错位。此外,代码中引入了 ChatOllama,但依赖清单中彻底漏掉了 langchain-ollama

  • 解法

    1. 顺藤摸瓜,通过 docker rmi 强行粉碎残留的损坏镜像。
    2. 手动在 requirements.txt 底部追加 pyjwtlangchain-ollama,且去掉死锁的具体版本号,交由 pip 在构建时自动进行最优版本树计算(最终匹配出 protobuf==5.29.3langchain-core==1.2.27 的完美兼容组合)。
  1. 跨平台依赖引发 Linux 编译死锁
  • 现象pip install 阶段服务卡死,甚至导致云服务器假死不得不重启。
  • 根因:依赖清单中包含了 pywin32==308 这种专属 Windows 系统的底层包,引入 Linux 容器内导致编译链发生死锁。
  • 解法:对 requirements.txt 进行 Linux 生产环境专项瘦身,彻底剔除 pywin32 相关模块。
  1. 隐藏的旧表结构与 init.sql 带来的死循环
  • 现象:服务稳定 Up 后依旧 502,日志无限报错 column "datasets" of relation "app_config" does not exist。即便执行了 docker compose down -v 也清除不掉。

  • 根因

    1. 本地数据库挂载使用了宿主机相对路径 ./volumes/db/data,Docker 安全机制导致 down -v 无法对其进行物理抹除。
    2. 挂载的 ./postgres/init.sql 内部缺少项目急需的 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 底层 UUID 扩展包。
    3. 项目随着版本迭代,迁移脚本(Migration)中写了试图去执行 DROP COLUMN datasets 的历史指令。由于新用户从零建表时默认就没这一列,导致严谨的 PostgreSQL 直接报错闪退。
  • 解法

    1. 彻底 docker compose down 释放文件锁,用 sudo rm -rf ./volumes/db/data/* 物理清空本地脏数据。
    2. 深入项目迁移源码目录 api/app/internal/migration/versions/,精准找到对应的 .py 脚本,将 batch_op.drop_column('datasets') 这一行代码注释掉,跳过这段历史冲突逻辑。
  1. 入口执行脚本中的变量名严重错位
  • 现象:数据库升级成功走完,但紧接着抛出 AppImportError: Failed to find attribute 'app' in 'app.http.app' 导致无限 15 秒闪退重启。
  • 根因:项目真正的源码里将 Flask 实例命名为了 flask_app,将 Celery 实例命名为了 celery_app。而官方自带的启动脚本 entrypoint.sh 里面却想当然地在用旧的名字 appcelery 去调起服务,导致启动路径断裂。
  • 解法
    打开 api/docker/entrypoint.sh,将其中的 Flask 迁移命令对齐为 app.http.app:flask_app,将生产环境 Gunicorn 启动命令对齐为 app.http.app:flask_app,将 Celery 异步任务命令对齐为 app.http.app:celery_app
  1. 2核2G 低配服务器的硬件极限榨干 (OOM / CPU 80%+)
  • 现象:变量对齐后服务终于全部成功开机,但 30 秒后服务器瞬间被卡死,CPU 飙升至 80% 以上,网页因底层 I/O 堵塞完全刷不出来,Celery 日志频繁出现 Killed

  • 根因:Celery 默认开启了 4 个多核并发 Worker 进程(-c 4),配合向量数据库 Weaviate,在 2G 内存的超低配环境中疯狂抢占硬件资源,直接触发了 Linux 内核的 OOM(内存溢出)保护机制,导致容器被连环物理抹杀。

  • 解法(2G内存极限生存配置)

    1. 修改 entrypoint.sh 配合 docker-compose.yaml 环境变量,强行将 Celery 并发数锁死为 1CELERY_WORKER_AMOUNT: "1")。
    2. docker-compose.yaml 中为 llmops-weaviate 注入 LIMIT_RESOURCES: "true"GOMAXPROCS: "1",死锁其只能使用 1 个 CPU 核心。
    3. docker-compose.yamldeploy.resources.limits 层级为 API、Celery、Weaviate 施加硬性物理紧箍咒,各自最高限制 500M-600M 内存。既给了 Python 加载基础深度学习库的保底空间,又刚好卡满 2G 总上限,完美达成资源平衡。

🏁 最终战果(鏖战一整天..)

  • CPU 使用率:从暴涨假死的 80%+ 成功稳定回落至健康的 30% 左右
  • 服务状态:7 个重量级服务全部呈现 Up(健康运行) ,无一闪退。
  • 用户体验:Nginx 反向代理完美打通内部 5001 端口,前端 502 Bad Gateway 彻底消灭,页面丝滑展开!LLMOps 项目 Docker 全栈部署避坑与通关实录