54f9ab4c4f
vps-production-runbook(VPS运维一站式,已验证状态+拓扑+发布流水线+人类闸口+排障); env-version-matrix(三机Python/deps矩阵+Lock建议); scripts/ops(NAS bridge探针-容器无curl-+VPS→NAS备份脚本); nas-deploy-plan§3(修rsync --exclude语法,原会删万级staging parquet+破坏entrypoint启动).
159 lines
7.4 KiB
Markdown
159 lines
7.4 KiB
Markdown
# 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),单 worker(orchestrator 任务状态在内存)。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`,单 worker,Phase 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 的改动不会自动同步(需手动处理)。
|
||
- **根治方案(待用户确认采用哪种)**:
|
||
|
||
| 方案 | 做法 | 适用 |
|
||
|------|------|------|
|
||
| ① 纳入 git 根目录版本化 | dev 根目录恢复 `entrypoint.sh`(与 `docker/entrypoint.sh` 统一为同一份),去掉 `--exclude='entrypoint.sh'`,rsync 正常同步 | entrypoint 需随代码迭代频繁改 |
|
||
| ② 由镜像层提供 | 确认 `entrypoint.sh` 由 Dockerfile `COPY` 进镜像;则 bind-mount 不应覆盖它(调整挂载/文件位置) | entrypoint 极少改、希望与代码解耦 |
|
||
|
||
> ⚠️ 两种方案互斥。当前默认走"短期保护",**待用户确认**后再切到 ① 或 ②。
|
||
|
||
---
|
||
|
||
## 四、依赖变更(改 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 + 大包约 30–60 分钟。
|
||
|
||
---
|
||
|
||
## 五、外网链路(部署时不要动)
|
||
|
||
```
|
||
外网 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`。
|