Files
sanguo_vnpy_v2/docs/deployment/vps-production-runbook.md
T
claude_dev 54f9ab4c4f docs(deploy): VPS生产runbook+三机环境矩阵+NAS ops脚本+修rsync危险命令
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启动).
2026-07-15 07:12:46 +08:00

401 lines
18 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.
# VPS 生产运维 Runbook
> **sanguo QMT bridge 生产环境运维手册。** VPS 已部署并在运行,本文档是"运维 + 已验证状态记录",不是从零搭建指南。
>
> 相关文档:
> - bridge 接口契约:[`sanguo_qmt_bridge/README.md`](../../sanguo_qmt_bridge/README.md)
> - 首次部署参考(历史):[`d-phase-windows-deploy.md`](./d-phase-windows-deploy.md) / [`windows-bridge-setup.md`](./windows-bridge-setup.md)
> - NAS 容器运维:[`nas-deploy-plan.md`](./nas-deploy-plan.md)
>
> **安全约定**:本文档进 git**不硬编码 BRIDGE_TOKEN**。所有命令中 `<BRIDGE_TOKEN>` 为占位符,真实值见 Claude memory `windows-vps-access.md` 或 VPS 系统环境变量 `BRIDGE_TOKEN`。
---
## 1. 已验证生产状态(2026-07-14 巡检)
> 以下事实经实地验证,直接采纳。下次巡检时更新日期并重新核实。
| 项目 | 值 | 备注 |
|------|-----|------|
| **VPS** | `49.232.102.198` | 腾讯云轻量,Win Server 20224C/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 隧道 | 跑不了 xtquantWindows only |
| **NAS** | 192.168.2.154 | 测试 + 备份 | Docker 容器跑集成测试、回测、每日备份 | 无法直连 VPS 80/443(光猫拦截) |
---
## 3. 日常运维操作
> 以下命令均从 **Mac Mini** 执行,通过 SSH 远程操控 VPS。
### 3.1 健康巡检(一条命令验三端点)
**快速健康(无需 token):**
```bash
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 坑](#8-windows-跑命令的坑)):
```bash
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 状态 + 进程 + 磁盘:**
```bash
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 连不上。
**步骤:**
1. **改 session_id** — RDP 或 SSH 编辑 `C:\run_bridge.ps1`,把 `$env:BRIDGE_SESSION_ID` 改为新值(如日期递增 `20260714``20260715` 或加后缀 `20260714b`):
```bash
# 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
```
2. **重启 schtask**
```bash
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"schtasks /end /tn \"sanguo-bridge\" & schtasks /run /tn \"sanguo-bridge\""
```
3. **等 5 秒后验证健康**(同 §3.1 快速健康命令)。
### 3.3 Caddy 重启
Caddy 无 session 状态问题,直接重启:
```bash
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
"schtasks /end /tn \"sanguo-caddy\" & schtasks /run /tn \"sanguo-caddy\""
```
验证公网:
```bash
curl -s https://bridge.mysanguo.top/health
```
### 3.4 miniQMT 崩溃恢复(human-gated
> miniQMT 是券商客户端 GUI 程序,崩溃后需要人工 RDP 登录。**无法自动化。**
**症状**`/health` 返回 `{"miniqmt_connected": false}`,或 bridge 日志 xtquant connect 报错。
**恢复步骤(人工操作):**
1. RDP 登录 VPS(远程桌面 `49.232.102.198`
2. 手动启动 miniQMT 客户端 → 登录模拟账户 `66639661`
3. 确认客户端进入极简模式/独立交易界面
4. **换 BRIDGE_SESSION_ID 后重启 bridge**(同 §3.2
5. 验证 `/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. 代码部署到 VPSMac → VPS scp + 重启)
### 5.1 scp 变更文件
bridge 代码在 Mac 的 `sanguo_qmt_bridge/` 目录,改完后 scp 到 VPS
```bash
# 只传变更的 .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 + 重启
```bash
# 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(依赖变更)
```bash
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 轮换流程(人工)
1. 生成新 token`python -c "import secrets; print(secrets.token_urlsafe(32))"`
2. VPS:更新 `C:\run_bridge.ps1` 里的 BRIDGE_TOKEN(或系统环境变量)
3. NAS:更新 sanguo 容器环境变量 + `docker restart sanguo_vnpy_v2`
4. Mac:更新 `sanguo_trader/bridge_client.py` 引用的配置或环境变量
5. VPS:换 session_id + 重启 bridge(§3.2
6. 全链路验证三端点
---
## 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/SurgeDNS 劫持到 fake-IP**。公网 DNS8.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 会乱码。
```bash
# 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 喂:
```bash
# 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` 搜索,不写字面量:
```powershell
# 不写 "C:\国金QMT交易端\...",用搜索
$qmt = Get-ChildItem C:\ -Directory | Where-Object Name -like "*QMT*"
```
### 8.4 curl vs Invoke-WebRequest
PowerShell 里 `curl` 默认是 `Invoke-WebRequest` 的别名(参数语法不同)。要用的标准 curl:
```powershell
curl.exe -s http://... # 显式 .exe 绕过别名
```
cmd 里 `curl` 直接就是 `curl.exe`,无此问题。