Files
sanguo_vnpy_v2/docs/deployment/nas-deploy-plan.md
T
claude_dev c210197778
CI/CD / test (push) Successful in 8s
CI/CD / nas-deploy (push) Failing after 34s
CI/CD / nas-verify (push) Has been skipped
fix(entrypoint): +x 执行位 + SKIP_REDIS_CHECK 默认1(治 run535 NAS crash loop)
run535 部署后容器 crash loop(exit126),两个独立根因:
1. Permission denied: entrypoint.sh git mv 后 644 无执行位,rsync 推 NAS 也是 644,
   bind mount 覆盖镜像层 chmod +x 无效,tini exec 失败。→ entrypoint.sh git 100755。
2. Redis 连接超时:新版 entrypoint 检查 SKIP_REDIS_CHECK!=1 才查 Redis(旧版 if false
   硬跳过),NAS 无 Redis 卡检查 crash。→ 默认 SKIP_REDIS_CHECK=1(复现旧版跳过)。

附:文档(nas-deploy-plan 方案①已实施 + ops/README 根治标记)。
2026-08-01 21:43:29 +08:00

152 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sanguo VeighNa — NAS Docker 迭代部署方案
> 维护人:诸葛亮 · 最后更新:2026-07-04
> 基于实机查证(Synology NAS `cfeasynas` 216+II
> 首次安装见 [`synology-nas.md`](./synology-nas.md),本文只讲**日常迭代与运维**。
> **2026-07-07 Phase 3b 更新**:容器 uvicorn 目标从 `sanguo_web.api:app`(旧实盘交易 API)切到 `sanguo_api.main:create_app --factory`(研究/回测 API + Vue SPA),单 workerorchestrator 任务状态在内存)。Vue 前端构建产物 `frontend/dist/` 由 FastAPI StaticFiles 挂在 `/`。公网 `vnpy.mysanguo.top` 现为**研究控制台**(登录 admin/admin,默认密码部署后改)。旧实盘交易路由(trading/gateway/market)本期下线,D 期接国金 QMT 时合并回来。端口 8000 / frpc / socat / Caddy 全程未动。
---
## 一、核心思路:应用层与镜像层分离
代码不打进镜像运行时,而是 bind-mount 挂进容器。**绝大多数迭代不需要重新 build 镜像。**
| 层次 | 内容 | 变更频率 | 变更方式 |
|------|------|----------|----------|
| **镜像层** `sanguo_vnpy_v2:latest` (6.9GB) | Python 3.10 + pip 依赖 + TA-Lib + entrypoint.sh | 低 | `docker build` |
| **应用层** bind-mount → `/app` | 项目源码(sanguo_web、vnpy_v4.4.0…) | 高 | rsync + `docker restart` |
| **数据层** `/app/data` | SQLite、日志(持久化) | — | 不要覆盖 |
> 容器启动时 `/app` 被宿主机目录整体覆盖,镜像内 `COPY` 的代码运行时不生效。**改代码 = 改宿主机目录 + 重启容器。**
---
## 二、当前部署实况(已查证)
| 项目 | 值 |
|------|-----|
| NAS | `cfeasynas` 192.168.2.154 |
| SSH | `admin@192.168.2.154` |
| Docker 路径 | `/var/packages/Docker/target/usr/bin/docker`**不在 PATH** |
| 权限 | admin 在 `docker` 组,**无需 sudo** |
| 容器/镜像 | `sanguo_vnpy_v2` / `sanguo_vnpy_v2:latest` |
| 端口 | `8000→8000``8080→8080` |
| 代码挂载 | `/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2``/app` |
| 启动 | `/app/entrypoint.sh``python /app/run_web.py`run_web.py 跑 `uvicorn sanguo_api.main:create_app --factory`,单 workerPhase 3b 起) |
| 重启策略 | `unless-stopped` |
| 启动方式 | `docker run`(非 compose |
---
## 三、常规迭代(只改代码)— 90% 场景
```bash
# 在 Mac Mini 执行(ssh sanguo-nas 已 key 免密,见 ~/.ssh/config;不用 sshpass
DOCKER="/var/packages/Docker/target/usr/bin/docker"
SRC=~/.openclaw/sanguo_projects/sanguo_vnpy_v2/
DEST=sanguo-nas:/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/
# 1) 同步代码(已排除数据/缓存/venv/entrypoint/配置——配置含部署态密码,不覆盖)
# ⚠️ 每个 pattern 必须单独一个 --exclude=PATTERN(见下方警告框)
rsync -avz --delete \
--exclude='.git' --exclude='__pycache__' --exclude='*.pyc' --exclude='.pytest_cache' \
--exclude='data/' --exclude='data_cache/' --exclude='logs/' --exclude='temp/' \
--exclude='*.log' --exclude='.DS_Store' \
--exclude='.venv/' --exclude='venv/' --exclude='venv311/' \
--exclude='node_modules/' --exclude='config/' \
--exclude='entrypoint.sh' \
-e ssh \
"$SRC" "$DEST"
```
> ⚠️ **rsync --exclude 写法警告(2026-07-14 dry-run 实测固化)**
>
> 旧写法 `--exclude='.git' '__pycache__' '*.pyc' ...`(一个 `--exclude` 后紧挨多个 bare pattern**实测排除失效**——rsync 把 bare pattern 当**源路径**处理(报 `lstat: No such file or directory`),`--delete` 因此会误删 NAS 上:
> - `data_cache/` 全量 staging parquet**万级文件,dev/NAS 差约 1 万个**
> - `/app/entrypoint.sh`(容器 Entrypoint,删除后 `docker restart` 启动失败)
> - 以及 `venv/` 等 dev-only 目录被误推上去
>
> **必须**用每 pattern 单独 `--exclude=PATTERN` 写法(如上命令)。每次改排除列表后建议先 `rsync -avzn ...`dry-run)确认 `deleting` 列表无意外项再实跑。
```bash
# 2) 重启容器
ssh sanguo-nas "$DOCKER restart sanguo_vnpy_v2"
# 3) 看日志 + 冒烟
sleep 8
ssh sanguo-nas "$DOCKER logs --tail 20 sanguo_vnpy_v2"
curl -s http://192.168.2.154:8000/api/v1/auth/login -X POST \
-H 'Content-Type: application/json' -d '{"username":"admin","password":"<部署态密码>"}'
```
| 验证项 | 期望 |
|--------|------|
| `ssh sanguo-nas "$DOCKER ps"` | `Up` |
| `POST /api/v1/auth/login` | 返回 `{"token":...}` |
### 附:`entrypoint.sh` 根因说明(2026-07-14 dry-run + docker inspect 实证)
`docker inspect sanguo_vnpy_v2` 显示容器 `Entrypoint=[/app/entrypoint.sh]``Cmd=[]`
即容器启动**必须**找到 `/app/entrypoint.sh`。但 dev 仓库根目录已无此文件(已移到
`docker/entrypoint.sh`)。若 rsync 带 `--delete` 且不排除,会删掉 NAS 上的
`/app/entrypoint.sh` → 下次 `docker restart` 直接启动失败(entrypoint not found)。
- **短期保护(已落实)**:上方步骤 1 命令加了 `--exclude='entrypoint.sh'`NAS 现有文件
不会被删,容器可继续启动。代价:dev 侧对 entrypoint 的改动不会自动同步(需手动处理)。
- **根治方案(✅ 2026-08-01 已实施方案①)**dev 根目录恢复 `entrypoint.sh``git mv``docker/entrypoint.sh`,单一权威),promote 全量 rsync 自动推送(`RSYNC_EXCLUDE` 不排除 `entrypoint.sh`),NAS 宿主孤儿遗留被覆盖、可复现。Dockerfile `COPY entrypoint.sh /app/` + `ENV PYTHONPATH=/app:/app/vnpy_v4.4.0` 同步固化。方案②(镜像层提供)弃用——bind-mount 必然覆盖 /app,镜像层 entrypoint 无法生效,只能靠宿主(promote 推送的)那份。
---
## 四、依赖变更(改 requirements-docker.txt)— 偶尔
```bash
# 1) 先同步代码(含新 requirements)到 NAS,同第三节步骤 1
# 2) 在 NAS 重新 build
ssh sanguo-nas \
"cd /volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2 && \
/var/packages/Docker/target/usr/bin/docker build -f docker/Dockerfile -t sanguo_vnpy_v2:latest ."
# 3) 用相同参数重启容器
ssh sanguo-nas << 'EOF'
D=/var/packages/Docker/target/usr/bin/docker
$D stop sanguo_vnpy_v2 && $D rm sanguo_vnpy_v2
$D run -d --name sanguo_vnpy_v2 --restart unless-stopped \
-p 8000:8000 -p 8080:8080 \
-v /volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2:/app \
sanguo_vnpy_v2:latest
EOF
```
> ⚠️ NAS CPU 弱,build 编 TA-Lib + 大包约 3060 分钟。
---
## 五、外网链路(部署时不要动)
```
外网 https://vnpy.mysanguo.top
→ VPS Caddy :18000 → frps → Mac Mini frpc :8001
→ socat → 192.168.2.154:8000 → NAS 容器
```
**部署应用只动 NAS 容器,绝不碰 frpc / socat / Caddy。** 详见 `~/.openclaw/workspace/docs/vps-deployment-guide.md`
---
## 六、已知问题
| 问题 | 影响 | 处理 |
|------|------|------|
| `threads can only be started once` | VeighNa 引擎在 2 workers 下重复初始化 | Web/登录/查询正常;真实交易需改 1 worker |
| `/health` 返回 `degraded` | 引擎未初始化的降级 | 非致命 |
| Docker 不在 PATH | — | 一律用全路径 |
| admin 密码硬编码 | 安全 | 后续改 SSH key |
---
## 七、回滚
代码层(镜像未动):`git checkout <旧commit>` 后重跑第三节步骤 1-2。
镜像层:`docker images sanguo_vnpy_v2` 找旧 tag,重 `docker run`