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启动).
18 KiB
VPS 生产运维 Runbook
sanguo QMT bridge 生产环境运维手册。 VPS 已部署并在运行,本文档是"运维 + 已验证状态记录",不是从零搭建指南。
相关文档:
- bridge 接口契约:
sanguo_qmt_bridge/README.md- 首次部署参考(历史):
d-phase-windows-deploy.md/windows-bridge-setup.md- NAS 容器运维:
nas-deploy-plan.md安全约定:本文档进 git,不硬编码 BRIDGE_TOKEN。所有命令中
<BRIDGE_TOKEN>为占位符,真实值见 Claude memorywindows-vps-access.md或 VPS 系统环境变量BRIDGE_TOKEN。
1. 已验证生产状态(2026-07-14 巡检)
以下事实经实地验证,直接采纳。下次巡检时更新日期并重新核实。
| 项目 | 值 | 备注 |
|---|---|---|
| VPS | 49.232.102.198 |
腾讯云轻量,Win Server 2022,4C/16G/180G SSD |
| SSH | ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 |
Mac ~/.ssh/id_ed25519 免密 |
| 磁盘 | C: 24G 用 / 156G 空闲 | 充裕 |
| Python | C:\Python310\python.exe (3.10.11) |
xtquant import OK |
| miniQMT | C:\国金QMT交易端\ userdata userdata_mini |
模拟账户 66639661 已登录 |
| bridge 代码 | C:\sanguo_qmt_bridge\ |
FastAPI + uvicorn :8765 |
| bridge 启动脚本 | C:\run_bridge.ps1 |
设环境变量 + BRIDGE_SESSION_ID + uvicorn |
| Caddy | C:\caddy\ + C:\run_caddy.ps1 |
:443 → :8765 反代,Let's Encrypt 自动续期 |
| schtasks | sanguo-bridge + sanguo-caddy 均 Running |
SYSTEM 账户 / onstart 自启 |
进程状态
| 进程 | 映像名 | 角色 |
|---|---|---|
| XtMiniQmt | XtMiniQmt.exe |
miniQMT 客户端,已登录模拟账户 |
| python | python.exe |
uvicorn bridge :8765 |
| caddy | caddy.exe |
HTTPS 反代 :443 → :8765 |
bridge 端点验证(VPS 本地 127.0.0.1:8765)
| 端点 | 响应 |
|---|---|
GET /health |
{"status":"ok","miniqmt_connected":true} |
GET /account |
{"ok":true,"cash":9997075.51,"frozen":9001769.0,"market_value":2901.0,"total":9999978.51} |
GET /positions |
{"ok":true,"positions":[...sh600000(200股), sz000001(100股)...]} |
三条访问路径
| # | 路径 | 地址 | 场景 |
|---|---|---|---|
| ① | VPS 本地 | http://127.0.0.1:8765 |
运维 RDP/SSH 内验证 |
| ② | 公网 HTTPS | https://bridge.mysanguo.top |
外部客户端(Mac 浏览器等) |
| ③ | NAS 容器经 Mac 隧道 | http://192.168.2.101:8765 |
NAS sanguo 容器调 bridge |
2. 拓扑图
┌───────────────────────────────────────────────────┐
│ Windows VPS · 49.232.102.198 │
│ Win Server 2022 · 4C/16G/180G SSD │
│ │
│ ┌───────────┐ xtquant ┌──────────────┐ │
│ │ miniQMT │◄──────────│ bridge │ │
│ │ 66639661 │ │ :8765 │ │
│ └───────────┘ └──────┬───────┘ │
│ │ 反代 │
│ ┌──────▼───────┐ │
│ │ Caddy │ │
│ │ :443 │ │
│ │ Let's Encr. │ │
│ └──────┬───────┘ │
└──────────────────────────────────┼────────────────┘
│
┌────────────────────────┼───────────────────┐
│ │ │
① VPS 本地 ② 公网 HTTPS ③ NAS→Mac→VPS
http://127.0.0.1:8765 https://bridge. http://192.168.2.101
(运维 RDP/SSH) mysanguo.top :8765
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ VPS 终端 │ │ 外部客户端 │ │ Mac Mini │
└──────────┘ │(Mac 浏览器等) │ │ 192.168.2.101│
└──────────────┘ │ 开发 + 隧道 │
│ │
│ ssh -N -L │
│ 0.0.0.0:8765:│──SSH:22──► VPS
│ 127.0.0.1:8765│
│ │
│ caffeinate │
│ -i -s 防睡眠 │
└──────┬───────┘
│
┌──────▼───────┐
│ NAS 容器 │
│ 192.168.2.154│
│ sanguo_vnpy │
│ _v2 (测试+备) │
└──────────────┘
⚠ 华为光猫拦截 NAS→VPS 的 80/443 → NAS 不能走路径②
→ NAS 容器走路径③:HTTP 到 Mac :8765 → Mac SSH 隧道 → VPS :8765
→ Mac 需常驻 SSH tunnel + caffeinate -i -s 防睡眠
三机分工
| 机器 | IP | 角色 | 能做什么 | 不能做什么 |
|---|---|---|---|---|
| VPS | 49.232.102.198 | 生产 | miniQMT + bridge + Caddy 全链路 | 无开发环境 |
| Mac Mini | 192.168.2.101 | 开发 + 隧道中继 | 写代码、git、scp 部署、SSH 隧道 | 跑不了 xtquant(Windows only) |
| NAS | 192.168.2.154 | 测试 + 备份 | Docker 容器跑集成测试、回测、每日备份 | 无法直连 VPS 80/443(光猫拦截) |
3. 日常运维操作
以下命令均从 Mac Mini 执行,通过 SSH 远程操控 VPS。
3.1 健康巡检(一条命令验三端点)
快速健康(无需 token):
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"curl -s http://127.0.0.1:8765/health"
期望:{"status":"ok","miniqmt_connected":true}
完整三端点验证(含 account + positions,需 token):
复杂引号场景用 stdin 喂 PowerShell(见 §8 Windows 坑):
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
Write-Host "=== health ==="
curl.exe -s http://127.0.0.1:8765/health
Write-Host "`n=== account ==="
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/account
Write-Host "`n=== positions ==="
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/positions
PS1
检查 schtasks 状态 + 进程 + 磁盘:
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
Write-Host "=== schtasks ==="
schtasks /query /tn "sanguo-bridge" /fo list | Select-String "Status"
schtasks /query /tn "sanguo-caddy" /fo list | Select-String "Status"
Write-Host "=== processes ==="
Get-Process python,caddy,XtMiniQmt -ErrorAction SilentlyContinue | Format-Table Name,Id,CPU -Auto
Write-Host "=== disk ==="
Get-PSDrive C | Format-Table Used,Free -Auto
PS1
3.2 bridge 重启(必须换 BRIDGE_SESSION_ID)
关键:xtquant 的
connect()复用旧 session 会返回 -1。每次重启 bridge 前必须换BRIDGE_SESSION_ID,否则 bridge 起来但 miniQMT 连不上。
步骤:
- 改 session_id — RDP 或 SSH 编辑
C:\run_bridge.ps1,把$env:BRIDGE_SESSION_ID改为新值(如日期递增20260714→20260715或加后缀20260714b):
# SSH 在线编辑(PowerShell 替换)
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
$path = "C:\run_bridge.ps1"
$content = Get-Content $path -Raw
# 把旧 session_id 替换为今天的(按实际值调整正则)
$newId = Get-Date -Format "yyyyMMddHHmm"
$content = $content -replace 'BRIDGE_SESSION_ID\s*=\s*"\d+"', "BRIDGE_SESSION_ID = `"$newId`""
Set-Content $path -Value $content -Encoding UTF8
Write-Host "session_id updated to $newId"
# 确认
Select-String -Path $path -Pattern "BRIDGE_SESSION_ID"
PS1
- 重启 schtask:
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"schtasks /end /tn \"sanguo-bridge\" & schtasks /run /tn \"sanguo-bridge\""
- 等 5 秒后验证健康(同 §3.1 快速健康命令)。
3.3 Caddy 重启
Caddy 无 session 状态问题,直接重启:
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"schtasks /end /tn \"sanguo-caddy\" & schtasks /run /tn \"sanguo-caddy\""
验证公网:
curl -s https://bridge.mysanguo.top/health
3.4 miniQMT 崩溃恢复(human-gated)
miniQMT 是券商客户端 GUI 程序,崩溃后需要人工 RDP 登录。无法自动化。
症状:/health 返回 {"miniqmt_connected": false},或 bridge 日志 xtquant connect 报错。
恢复步骤(人工操作):
- RDP 登录 VPS(远程桌面
49.232.102.198) - 手动启动 miniQMT 客户端 → 登录模拟账户
66639661 - 确认客户端进入极简模式/独立交易界面
- 换 BRIDGE_SESSION_ID 后重启 bridge(同 §3.2)
- 验证
/health→miniqmt_connected: true
4. dev → test → prod 发布流水线
Mac Mini (开发) NAS (测试) VPS (生产)
───────────── ────────── ──────────
写代码 git pull scp bridge 代码
sanguo_qmt_bridge/ → 容器跑集成测试 → schtasks 重启
本地 unit test 验证通过 → 健康巡检
│ │ │
▼ ▼ ▼
git push ──────────► gitea ──────► NAS pull ────► VPS deploy
(git.mysanguo.top) (Docker 容器) (scp + restart)
│
每日 VPS 状态
备份 → NAS
| 阶段 | 机器 | 动作 | 验证 |
|---|---|---|---|
| dev | Mac Mini | 改 sanguo_qmt_bridge/ 代码 |
本地 unit test(pytest tests/) |
| push | Mac Mini | git push origin master |
gitea 仓库更新 |
| test | NAS | 容器 git pull + 跑集成测试 |
bridge_client 测试通过、数据管道 OK |
| prod | VPS | scp 变更文件 + 重启 bridge | /health + /account + /positions 三通 |
| backup | NAS | 每日 VPS 状态备份到 NAS | bridge 代码 + config 快照 |
注意:NAS 测试只能验 bridge_client 端(发 HTTP 请求到 bridge),无法验 xtquant/miniQMT 端(Windows only)。bridge 服务端 + xtquant + miniQMT 的端到端验证只能在 VPS 上做。
5. 代码部署到 VPS(Mac → VPS scp + 重启)
5.1 scp 变更文件
bridge 代码在 Mac 的 sanguo_qmt_bridge/ 目录,改完后 scp 到 VPS:
# 只传变更的 .py 文件(快速迭代)
scp -i ~/.ssh/id_ed25519 \
sanguo_qmt_bridge/bridge.py \
sanguo_qmt_bridge/xt_gateway.py \
sanguo_qmt_bridge/auth.py \
Administrator@49.232.102.198:C:/sanguo_qmt_bridge/
# 或整目录同步(含 requirements.txt 等)
scp -i ~/.ssh/id_ed25519 sanguo_qmt_bridge/*.py \
Administrator@49.232.102.198:C:/sanguo_qmt_bridge/
Windows scp 路径用正斜杠:
C:/sanguo_qmt_bridge/(不是反斜杠)。
5.2 换 session_id + 重启
# 1. 换 BRIDGE_SESSION_ID(同 §3.2 步骤 1)
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
$path = "C:\run_bridge.ps1"
$content = Get-Content $path -Raw
$newId = Get-Date -Format "yyyyMMddHHmm"
$content = $content -replace 'BRIDGE_SESSION_ID\s*=\s*"\d+"', "BRIDGE_SESSION_ID = `"$newId`""
Set-Content $path -Value $content -Encoding UTF8
Write-Host "session_id → $newId"
PS1
# 2. 重启 schtask
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"schtasks /end /tn \"sanguo-bridge\" & schtasks /run /tn \"sanguo-bridge\""
# 3. 等 5 秒,验证
sleep 5
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"curl -s http://127.0.0.1:8765/health"
5.3 如改了 requirements.txt(依赖变更)
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"cd C:\sanguo_qmt_bridge && C:\Python310\python.exe -m pip install -r requirements.txt"
# 然后同 5.2 重启
6. 人类闸口清单
以下操作无法自动化,必须人工执行。自动化脚本碰到这些步骤应明确报"需人工干预"。
| 操作 | 为什么只能人做 | 频率 |
|---|---|---|
| VPS 开通 / 重置密码 | 云厂商控制台操作 | 极低(首次/安全事件) |
| miniQMT 券商客户端登录 | GUI 程序 + 可能需验证码/密码 | 崩溃后 / VPS 重启后 |
| BRIDGE_TOKEN 轮换 | 需同时在 VPS + NAS + Mac 三处同步更新 | 定期(安全策略) |
| Let's Encrypt 证书异常处理 | Caddy 自动续期,但 Rate Limit / DNS 异常需人工介入 | 极低(自动续期正常时无需干预) |
| 华为光猫 / 网络配置变更 | 运营商设备,SSH 碰不到 | 极低 |
| VPS 计划任务创建/修改 | 首次配置 schtasks /create 需 RDP |
低(配置变更时) |
BRIDGE_TOKEN 轮换流程(人工)
- 生成新 token:
python -c "import secrets; print(secrets.token_urlsafe(32))" - VPS:更新
C:\run_bridge.ps1里的 BRIDGE_TOKEN(或系统环境变量) - NAS:更新 sanguo 容器环境变量 +
docker restart sanguo_vnpy_v2 - Mac:更新
sanguo_trader/bridge_client.py引用的配置或环境变量 - VPS:换 session_id + 重启 bridge(§3.2)
- 全链路验证三端点
7. 排障表
| 现象 | 根因 | 排查 / 修复 |
|---|---|---|
/health → miniqmt_connected: false |
miniQMT 未登录 / 崩溃 / userdata 路径错 | RDP 检查 miniQMT 客户端状态(§3.4),确认 MINIQMT_USERDATA 指向正确 userdata_mini |
| bridge 启动 xtquant connect 返回 -1 | BRIDGE_SESSION_ID 与旧 session 冲突 | 换 session_id 后重启(§3.2)——这是最常见坑 |
| 401 token 无效 | VPS 与 NAS/Mac 的 BRIDGE_TOKEN 不一致 |
核对三处 token 值一致 |
公网 https://bridge.mysanguo.top 502 |
Caddy 没启 或 bridge 没启 | 先验 VPS 本地 curl 127.0.0.1:8765/health;本地通=bridge OK→查 Caddy(§3.3);本地不通→查 bridge |
| 公网 DNS 解析到错误 IP(如 198.18.1.244) | Mac 本地代理(Clash/Surge)DNS 劫持到 fake-IP。公网 DNS(8.8.8.8)解析正确到 49.232.102.198,但代理层拦截 | 修复①:/etc/hosts 加 49.232.102.198 bridge.mysanguo.top;修复②:代理规则里该域名直连(bypass proxy) |
NAS 容器访问 bridge.mysanguo.top 超时 |
华为光猫拦截 NAS→VPS 的 80/443 | 走路径③:Mac SSH 隧道 http://192.168.2.101:8765(Mac 需常驻 tunnel + caffeinate) |
| Mac SSH 隧道断了 → NAS 容器连不上 bridge | Mac 睡眠 / SSH 进程退出 | Mac 跑 caffeinate -i -s & 防睡眠;用 autossh 或 launchd 守护 SSH tunnel |
下单报 [120141][证券交易未初始化] |
非交易日(miniQMT 交易日才初始化交易通道) | 等交易日。这是 miniQMT 设计,不是 bug |
| 影子下单未触发 | live.enabled 未开 / 当日无成交 / BRIDGE_TOKEN 未设 / bridge_url 不对 |
逐项检查 NAS config/data_platform.yaml + 环境变量 |
| 重复下单 | — | 不会。paper_shadow_orders 表 UNIQUE(account_id, trade_id) 幂等去重 |
| cmd 中文/emoji 乱码 | cmd 默认 GBK 编码 | 用 PowerShell + chcp 65001,或 python 加 -X utf8(§8) |
| scp 中文路径失败 | Windows 中文目录 + SSH 编码 | 用正斜杠路径 + ASCII 变量名,中文路径用搜索代替字面量 |
8. Windows 跑命令的坑
Windows SSH 远程跑命令有三个经典坑:编码、引号、中文路径。
8.1 编码(GBK → UTF8)
cmd 默认 GBK,中文输出和 emoji 会乱码。
# PowerShell 设 UTF8(代码页 65001)
ssh ... "powershell -Command \"[Console]::OutputEncoding = [Text.Encoding]::UTF8; chcp 65001; <你的命令>\""
# Python 加 -X utf8
ssh ... "C:\Python310\python.exe -X utf8 script.py"
8.2 引号嵌套 → 用 stdin
SSH → cmd → PowerShell 三层引号极易出错。复杂脚本用 stdin 喂:
# powershell -Command - 从 stdin 读脚本
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
# 这里写 PowerShell,引号无需转义
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/account
PS1
<<'PS1' 的单引号防止 Mac shell 展开变量,PowerShell 在 VPS 侧原样执行。
8.3 中文路径
C:\国金QMT交易端\ 等中文路径在 SSH 传输中可能因编码错乱。
- scp 目标用正斜杠:
C:/sanguo_qmt_bridge/(ASCII) - 需引用中文路径时,在 PowerShell 内用变量拼接或
Get-ChildItem搜索,不写字面量:
# 不写 "C:\国金QMT交易端\...",用搜索
$qmt = Get-ChildItem C:\ -Directory | Where-Object Name -like "*QMT*"
8.4 curl vs Invoke-WebRequest
PowerShell 里 curl 默认是 Invoke-WebRequest 的别名(参数语法不同)。要用的标准 curl:
curl.exe -s http://... # 显式 .exe 绕过别名
cmd 里 curl 直接就是 curl.exe,无此问题。