📌 项目基本盘
- 部署环境:阿里云 2核(vCPU) 2 GiB 内存服务器(Linux / Debian 环境)
- 全栈架构:7 个核心容器服务(
ui、api、celery、redis、db(PostgreSQL)、weaviate、nginx) - 基础环境:Python 3.13-slim-bookworm + Docker Compose (BuildKit)
🛠️ 核心血泪踩坑点与终极解决方案
- 国际网络连接超时 (i/o timeout)
-
现象:刚启动时显示“只拉起了两个服务”,整个 Docker 构建在拉取
python:3.13-slim-bookworm时因网络连接超时强行崩溃中断。 -
根因:国内访问 Docker Hub 官方镜像仓库受限,导致依赖它的后端镜像无法继续构建,后续的 5 个服务根本没有机会排队执行。
-
解法:
- 巧妙在
docker-compose.yaml中将第三方镜像(如redis、nginx)替换为国内 Daocloud 等加速源。 - 修改
../api/Dockerfile内部的第一行,将基础镜像改为FROM docker.m.daocloud.io/library/python:3.13。
- 巧妙在
- 禁用了 BuildKit 导致语法不支持
- 现象:构建报错提示
--mount=type=cache语法不被支持。 - 根因:在旧版 Docker 引擎命令中误加了
DOCKER_BUILDKIT=0。而较新的Dockerfile内部为了加速pip install普遍使用了缓存挂载语法,必须依赖新一代构建引擎 BuildKit。 - 解法:修改命令开头,强行开启引擎:
DOCKER_BUILDKIT=1 docker compose up -d --build。
- 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。 -
解法:
- 顺藤摸瓜,通过
docker rmi强行粉碎残留的损坏镜像。 - 手动在
requirements.txt底部追加pyjwt和langchain-ollama,且去掉死锁的具体版本号,交由pip在构建时自动进行最优版本树计算(最终匹配出protobuf==5.29.3和langchain-core==1.2.27的完美兼容组合)。
- 顺藤摸瓜,通过
- 跨平台依赖引发 Linux 编译死锁
- 现象:
pip install阶段服务卡死,甚至导致云服务器假死不得不重启。 - 根因:依赖清单中包含了
pywin32==308这种专属 Windows 系统的底层包,引入 Linux 容器内导致编译链发生死锁。 - 解法:对
requirements.txt进行 Linux 生产环境专项瘦身,彻底剔除pywin32相关模块。
- 隐藏的旧表结构与 init.sql 带来的死循环
-
现象:服务稳定 Up 后依旧 502,日志无限报错
column "datasets" of relation "app_config" does not exist。即便执行了docker compose down -v也清除不掉。 -
根因:
- 本地数据库挂载使用了宿主机相对路径
./volumes/db/data,Docker 安全机制导致down -v无法对其进行物理抹除。 - 挂载的
./postgres/init.sql内部缺少项目急需的CREATE EXTENSION IF NOT EXISTS "uuid-ossp";底层 UUID 扩展包。 - 项目随着版本迭代,迁移脚本(Migration)中写了试图去执行
DROP COLUMN datasets的历史指令。由于新用户从零建表时默认就没这一列,导致严谨的 PostgreSQL 直接报错闪退。
- 本地数据库挂载使用了宿主机相对路径
-
解法:
- 彻底
docker compose down释放文件锁,用sudo rm -rf ./volumes/db/data/*物理清空本地脏数据。 - 深入项目迁移源码目录
api/app/internal/migration/versions/,精准找到对应的.py脚本,将batch_op.drop_column('datasets')这一行代码注释掉,跳过这段历史冲突逻辑。
- 彻底
- 入口执行脚本中的变量名严重错位
- 现象:数据库升级成功走完,但紧接着抛出
AppImportError: Failed to find attribute 'app' in 'app.http.app'导致无限 15 秒闪退重启。 - 根因:项目真正的源码里将 Flask 实例命名为了
flask_app,将 Celery 实例命名为了celery_app。而官方自带的启动脚本entrypoint.sh里面却想当然地在用旧的名字app和celery去调起服务,导致启动路径断裂。 - 解法:
打开api/docker/entrypoint.sh,将其中的 Flask 迁移命令对齐为app.http.app:flask_app,将生产环境 Gunicorn 启动命令对齐为app.http.app:flask_app,将 Celery 异步任务命令对齐为app.http.app:celery_app。
- 2核2G 低配服务器的硬件极限榨干 (OOM / CPU 80%+)
-
现象:变量对齐后服务终于全部成功开机,但 30 秒后服务器瞬间被卡死,CPU 飙升至 80% 以上,网页因底层 I/O 堵塞完全刷不出来,Celery 日志频繁出现
Killed。 -
根因:Celery 默认开启了 4 个多核并发 Worker 进程(
-c 4),配合向量数据库 Weaviate,在 2G 内存的超低配环境中疯狂抢占硬件资源,直接触发了 Linux 内核的 OOM(内存溢出)保护机制,导致容器被连环物理抹杀。 -
解法(2G内存极限生存配置) :
- 修改
entrypoint.sh配合docker-compose.yaml环境变量,强行将 Celery 并发数锁死为1(CELERY_WORKER_AMOUNT: "1")。 - 在
docker-compose.yaml中为llmops-weaviate注入LIMIT_RESOURCES: "true"和GOMAXPROCS: "1",死锁其只能使用 1 个 CPU 核心。 - 在
docker-compose.yaml的deploy.resources.limits层级为 API、Celery、Weaviate 施加硬性物理紧箍咒,各自最高限制 500M-600M 内存。既给了 Python 加载基础深度学习库的保底空间,又刚好卡满 2G 总上限,完美达成资源平衡。
- 修改
🏁 最终战果(鏖战一整天..)
- CPU 使用率:从暴涨假死的 80%+ 成功稳定回落至健康的 30% 左右。
- 服务状态:7 个重量级服务全部呈现
Up(健康运行) ,无一闪退。 - 用户体验:Nginx 反向代理完美打通内部 5001 端口,前端 502 Bad Gateway 彻底消灭,页面丝滑展开!LLMOps 项目 Docker 全栈部署避坑与通关实录