量化系统 Docker→裸跑迁移:一份踩坑笔记
本文最后更新于 2026年7月4日 凌晨
量化系统 Docker→裸跑迁移:一份踩坑笔记
背景:Docker 卸载后,谁还在偷偷调用它?
一套量化交易系统(AutoQuant),原本跑在 Docker 容器里。2026-06-30 做了一次基础设施迁移:从 Docker 搬到 systemd 裸跑服务,docker 命令已从系统中卸载。
迁移文档记录了核心服务已就绪,systemd 服务正常运行。一切看起来没问题。
直到周六凌晨 03:00,周期数据自动更新的 cron 任务触发——全盘失败。
根因:macro_cycle_workflow.py(宏观周期分析工作流主控脚本)仍在用 docker exec invest-workbench python3 ... 调用 11 个子脚本。Docker 没了,所有调用直接报错。
这是一个典型的”迁移残留”问题。本文记录了排查、修复和验证的完整过程。
根因分析:为什么迁移文档说”已改”,代码却还是旧的?
迁移文档 migration-post-fix-20260630.md 明确记录:workflow 已被子代理重写为裸跑模式。
但检查当前代码,docker_exec 调用赫然还在。
时间线还原:
- 06-30:Docker→systemd 迁移完成,workflow 被重写为裸跑模式 ✅
- 07-03:新功能”去美元化脚本”集成(commit 62a8687)
- 07-03 集成时发生 cherry-pick 冲突
- 冲突解决过程中,裸跑改动被意外丢失,代码回退到
docker_exec模式 ❌
教训:cherry-pick 冲突解决是高危操作。冲突时手动选择”保留哪边”很容易遗漏关键改动。这个案例中,迁移改动的 106 行代码在冲突解决时全部丢失了。
更隐蔽的是——验证环节没有发现。当时的验证只做了子脚本语法检查(python3 -c "import ..."),但子脚本本身是好的(它们用了自适应路径 os.path.expanduser("~/.autoquant/cycles")),只有 workflow 主控脚本的 docker_exec 调用才是坏的。
修复方案
核心改动集中在一个文件:macro_cycle_workflow.py。
改动 1:docker_exec → 本地 subprocess
1 | |
改动 2:容器路径 → 自适应路径
1 | |
改动 3:venv 自动定位
这是迁移中最关键的函数。子脚本依赖 akshare、pandas 等第三方库,必须用项目 venv 里的 Python,不能用系统裸 python3:
1 | |
改动 4:shell 包装脚本更新
1 | |
验证:11 脚本分步测试
迁移不能靠”语法检查通过”就算完。设计了三道验证关卡:
关卡 1:代码扫描(grep 验证残留)
1 | |
关卡 2:实际运行(11 脚本逐一执行)
1 | |
10/11 通过。唯一失败的 geopolitical_risk_tracker.py 是外部数据源(学术网站 Excel 文件下载)超时,非本次改动引入的 regression。
关卡 3:产出物验证
1 | |
看板数据完整:周期定位、共振等级、经济象限、配置建议、黑天鹅评分、反身性检测、美元信用、去美元化、再平衡建议——9 个维度全部有值。
C 阶段踩坑:OpenCode 写的 subprocess 用裸 python3
修复过程中用 AI 编程工具(OpenCode)辅助编码,它写的 run_script 函数直接用了裸 python3:
1 | |
结果 5 个依赖 akshare 的子脚本全部 ImportError: No module named 'akshare'——因为系统 python3 没装 akshare,它只在项目 venv 里。
修复:手写 _find_venv_python() 自动定位 venv。
教训:AI 编程工具不知道你的项目有独立 venv。给 AI 的任务文档必须显式写明 venv python 路径,否则它会默认用系统 python。
总结:Docker→裸跑迁移检查清单
1 | |
三个核心教训
cherry-pick 冲突解决是高危操作——冲突时一定要 diff 两边的完整差异,不能只看冲突标记区域。本例中 106 行迁移改动在冲突区域之外,被整个回退了。
语法检查 ≠ 功能验证——子脚本语法正确不代表 workflow 主控脚本的调用方式正确。必须实际运行端到端流程。
AI 辅助编码需手动审计环境依赖——AI 编程工具默认用系统环境,不知道项目有独立 venv。任务文档中必须显式声明 Python 路径。
迁移最难的部分不是”改代码”,而是”确认没有遗漏”。Docker 卸载三个月后还能发现残留的
docker_exec调用,说明验证的深度永远不够。grep 扫描 + 实际运行 + 产出物检查,三道关卡缺一不可。