部署到生产¶
一套"上线清单",把前面所有能力串起来。推荐:Docker + systemd + Prometheus。
架构¶
flowchart LR
LLM[LLM 客户端<br/>Claude/GPT] -->|HTTPS<br/>Bearer| MCP[futu-mcp<br/>HTTP :38765]
Bot[交易 bot<br/>Python/Go] -->|gRPC<br/>Bearer metadata| OpenD
Web[网页前端] -->|WS<br/>?token=| OpenD
Script[运维脚本] -->|REST<br/>Bearer| OpenD[futu-opend<br/>TCP :11111<br/>REST :22222<br/>gRPC :33333]
OpenD -->|FTAPI TCP| Futu[Futu 后端]
MCP -->|FTAPI TCP| OpenD
OpenD --> Audit[(/var/log/futu/<br/>audit JSONL)]
MCP --> Audit
Prom[Prometheus] -->|scrape| OpenD
Prom -->|scrape| MCP
Graf[Grafana] --> Prom
1. 机器准备¶
- OS:Ubuntu 22.04 / Debian 12 / RHEL 9 都行
- 资源:2 vCPU + 2 GB RAM 够单实例用;高并发推 4 vCPU + 4 GB
- 网络:能出访 Futu 后端(
*.futunn.com),入访只需开你决定暴露的端口 - 用户:
futu:futu非 root,/var/lib/futu/var/log/futu两个目录归它
sudo useradd --system --shell /usr/sbin/nologin futu
sudo install -d -o futu -g futu -m 0750 /var/lib/futu /var/log/futu
sudo install -d -o root -g futu -m 0750 /etc/futu-opend
2. 放配置¶
/etc/futu-opend/env(chmod 0640 root:futu):
/etc/futu-opend/env
FUTU_ACCOUNT=12345678
FUTU_PWD=your_login_password
FUTU_MCP_API_KEY=fc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
/etc/futu-opend/keys.json(chmod 0640 root:futu):用 futucli gen-key 生成。
3. 装二进制¶
cd /tmp
curl -LO https://futuapi.com/releases/rs-v1.4.26/futu-opend-rs-1.4.26-linux-x86_64.tar.gz
curl -LO https://futuapi.com/releases/rs-v1.4.26/futu-opend-rs-1.4.26-linux-x86_64.tar.gz.sha256
sha256sum -c futu-opend-rs-1.4.26-linux-x86_64.tar.gz.sha256
tar xf futu-opend-rs-1.4.26-linux-x86_64.tar.gz
sudo install -m 0755 futu-opend /usr/local/bin/
sudo install -m 0755 futu-mcp /usr/local/bin/
sudo install -m 0755 futucli /usr/local/bin/
参考下面"docker-compose 版本"前的 Dockerfile 小节,用那份自己 build 就好。目前不发布公开 Docker image。
4. systemd unit¶
futu-opend.service¶
存 /etc/systemd/system/futu-opend.service:
[Unit]
Description=FutuOpenD-rs Gateway (TCP/REST/gRPC/WS)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=futu
Group=futu
# 凭证从 EnvironmentFile 读,避免命令行 ps 暴露:
EnvironmentFile=/etc/futu-opend/env
ExecStart=/usr/local/bin/futu-opend \
--login-account ${FUTU_ACCOUNT} \
--login-pwd ${FUTU_PWD} \
--rest-port 22222 \
--grpc-port 33333 \
--rest-keys-file /etc/futu-opend/keys.json \
--grpc-keys-file /etc/futu-opend/keys.json \
--audit-log /var/log/futu/
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=futu-opend
# 加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/futu /var/lib/futu
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
RestrictRealtime=true
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
futu-mcp.service¶
存 /etc/systemd/system/futu-mcp.service(HTTP transport 模式,多 LLM 共享):
[Unit]
Description=FutuOpenD-rs MCP server (HTTP transport)
After=futu-opend.service network-online.target
Wants=futu-opend.service network-online.target
[Service]
Type=simple
User=futu
Group=futu
EnvironmentFile=/etc/futu-opend/env
ExecStart=/usr/local/bin/futu-mcp \
--gateway 127.0.0.1:11111 \
--http-listen 127.0.0.1:38765 \
--keys-file /etc/futu-opend/keys.json \
--audit-log /var/log/futu/mcp/
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=futu-mcp
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/futu /var/lib/futu
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
启用¶
sudo systemctl daemon-reload
sudo systemctl enable --now futu-opend.service
sudo systemctl enable --now futu-mcp.service
# 看日志
sudo journalctl -u futu-opend -f
关键加固字段:
EnvironmentFile=/etc/futu-opend/env— 凭证不进ps输出User=futu / Group=futu— 非 root 运行NoNewPrivileges / ProtectSystem=strict / ReadWritePaths=...— systemd 加固Restart=on-failure— 崩了自动重启
5. Dockerfile + docker-compose¶
目前不发布公开 image,自己打。把 tarball 下下来后,放一个 Dockerfile(或者用解压后的 binary 直接进镜像):
Dockerfile
# runtime-only:假设你已经从 futuapi.com/releases/... 下好 linux-x86_64 tarball
# 并解压到构建目录
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates tzdata \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 10001 futu \
&& useradd --system --uid 10001 --gid futu --no-create-home --shell /usr/sbin/nologin futu
COPY futu-opend /usr/local/bin/
COPY futu-mcp /usr/local/bin/
COPY futucli /usr/local/bin/
RUN install -d -o futu -g futu -m 0750 /var/lib/futu /var/log/futu
USER futu:futu
EXPOSE 11111 22222 33333 38765
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:22222/health || exit 1
ENTRYPOINT ["futu-opend"]
docker-compose.yml:
docker-compose.yml
version: "3.9"
services:
opend:
image: futu-opend-rs:1.2 # 本地 build 的
build: .
restart: unless-stopped
env_file: /etc/futu-opend/env
command:
- futu-opend
- --login-account=${FUTU_ACCOUNT}
- --login-pwd=${FUTU_PWD}
- --rest-port=22222
- --grpc-port=33333
- --rest-keys-file=/etc/futu/keys.json
- --grpc-keys-file=/etc/futu/keys.json
- --audit-log=/var/log/futu/
volumes:
- /etc/futu-opend/keys.json:/etc/futu/keys.json:ro
- /var/log/futu:/var/log/futu
ports:
- "11111:11111"
- "127.0.0.1:22222:22222" # REST 只允许本机 / LB 访问
- "33333:33333"
mcp:
image: futu-opend-rs:1.2
restart: unless-stopped
depends_on: [opend]
env_file: /etc/futu-opend/env
command:
- futu-mcp
- --gateway=opend:11111
- --keys-file=/etc/futu/keys.json
- --http-listen=0.0.0.0:38765
- --audit-log=/var/log/futu/mcp/
volumes:
- /etc/futu-opend/keys.json:/etc/futu/keys.json:ro
- /var/log/futu:/var/log/futu
ports:
- "127.0.0.1:38765:38765"
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "127.0.0.1:9090:9090"
prometheus.yml:
scrape_configs:
- job_name: futu-opend
static_configs: [{ targets: ['opend:22222'] }]
- job_name: futu-mcp
static_configs: [{ targets: ['mcp:38765'] }]
6. 反向代理(nginx / Caddy)¶
对外暴露 HTTPS 时必须加反代。推荐 Caddy 自动 Let's Encrypt:
/etc/caddy/Caddyfile
api.your-domain.com {
reverse_proxy localhost:22222
}
mcp.your-domain.com {
reverse_proxy localhost:38765
}
Caddy 会自动申请证书、处理 renewal。
7. 监控 + 告警¶
- Prometheus 抓
<gateway>:22222/metrics和<mcp>:38765/metrics - Grafana dashboard 见 审计与可观察性 里的 Grafana 示例
- 告警:
futu_auth_events_total{outcome="reject"}突增 → 攻击 / 配置错;futu_auth_limit_rejects_total{reason="rate"}长期非零 → key 被 spray
8. 日志 + 审计¶
journalctl -u futu-opend看常规日志/var/log/futu/futu-audit.log.*每日滚动的审计 JSONL(--audit-log /var/log/futu/)- 用 logrotate 做归档压缩:
/etc/logrotate.d/futu
9. 升级¶
# 新版 binary 覆盖到 /usr/local/bin(同名)
sudo cp futu-opend-new /usr/local/bin/futu-opend
# 重启
sudo systemctl restart futu-opend
sudo systemctl restart futu-mcp
SIGHUP 热重载 keys.json(不重启):
sudo systemctl kill -s HUP futu-opend
sudo systemctl kill -s HUP futu-mcp
# 或
sudo pkill -HUP futu-opend
10. 备份¶
/etc/futu-opend/keys.json—— 有 key 的 SHA-256 hash,丢了所有 key 全失效。定期 encrypted backup/var/log/futu/—— 审计日志,合规场景要长期保存~futu/.config/futu/—— Futu 后端的 session 缓存,丢了首次启动要重走 SMS
上线检查单¶
-
keys.json里没有 wildcard 权限的 key,每把都有明确 scope + 限额 -
--audit-log配了,日志在轮转 -
/metrics被 Prometheus 抓了,Grafana dashboard 能看到数据 -
/health打得通,LB / k8s probe 指向它 -
FUTU_TRADE_PWD的交易密码只在futucli unlock-trade里用,不进 systemd env - 对外端口只开反代后的 443,网关端口 bind 127.0.0.1
- 对接方有 runbook:key 丢了怎么办、怎么吊销、怎么 SIGHUP
-
allowed_machines给关键 key 绑机器 - 新 binary 上线前跑
scripts/run_test.sh回归
搞定。