# tradingSystem (PMS) 容器编排 # 用法见 README「Docker 部署」。.env 放在本文件同目录 (服务器上手工维护, 不入库)。 # # 双实例 (模拟/正式) 隔离 (2026-08-28): 容器名前缀、镜像标签、页面宿主机端口这三样 # 从 .env 取值、默认值与从前完全一致 —— 现有部署一个字不用改。另一套实例 (通常在 # 另一台服务器; 同机双栈也行) 在自己的 .env 里加: # PMS_STACK_PREFIX=real- → 容器名变成 real-pms-web / real-pms-ws ... # PMS_IMAGE=pms-real:latest → 镜像标签分开, 互不覆盖 # PMS_WEB_PORT_HOST=38200 → 页面宿主机端口 (容器内固定 38100, 不用改) # COMPOSE_PROJECT_NAME=pms-real → compose 项目名 (网络/卷前缀), docker 原生支持 # 这些键进容器环境也无害: settings 是 extra="ignore", 不声明就当不存在。 # 三组服务: # 默认 pms-web 管理页面 (38100) # --profile sched pms-beat / pms-worker Celery 调度与执行 # --profile ws pms-ws QMT WebSocket 常驻连接 (协议 V1.0) # # pms-ws 与 worker 的关系: worker 里的 executor 把待发委托写进 pms_qmt_order 出口表, # pms-ws 亚秒轮询取走并发出。两者**只经数据库耦合**, 谁先起谁后起都不影响 —— pms-ws 没起 # 来时 dispatcher 会因心跳陈旧而拒发, 指令保持原状, 不会静默堆在队列里。 x-pms-base: &pms-base build: . image: ${PMS_IMAGE:-pms:latest} env_file: .env environment: - TZ=Asia/Shanghai volumes: - ./logs:/app/logs # 上游选股计划服务 (:8300) 跑在**宿主机本机**上。容器里访问宿主机的物理网卡 IP # (192.168.16.155) 会 ConnectTimeout —— docker 只给 FORWARD 链放行, 宿主机 INPUT 链 # 对 docker 网桥进来的包默认不放, 表现就是丢包超时 (不是 refused)。 # host-gateway 把宿主机在网桥上的地址 (172.x.0.1) 映射成 host.docker.internal, # 走这条路一般能通 (firewalld 会把网桥放在 docker zone, 默认 ACCEPT)。 # 还不通就是宿主机 INPUT 拦了 8300, 放行网桥网段即可: # sudo firewall-cmd --permanent --zone=trusted --add-source=172.16.0.0/12 && sudo firewall-cmd --reload extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped logging: driver: json-file options: max-size: "50m" max-file: "5" services: pms-web: <<: *pms-base container_name: ${PMS_STACK_PREFIX:-}pms-web command: uvicorn app.web.main:app --host 0.0.0.0 --port 38100 ports: - "${PMS_WEB_PORT_HOST:-38100}:38100" healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:38100/health', timeout=3)"] interval: 30s timeout: 5s retries: 3 start_period: 10s pms-beat: <<: *pms-base container_name: ${PMS_STACK_PREFIX:-}pms-beat profiles: ["sched"] command: celery -A app.scheduler.celery_app beat -l info pms-worker: <<: *pms-base container_name: ${PMS_STACK_PREFIX:-}pms-worker profiles: ["sched"] command: celery -A app.scheduler.celery_app worker -l info -c 2 # QMT WebSocket 常驻连接。协议 §1 规定 PMS 只开一条连接 (多开会让指令乱序), # 所以这个服务**绝不能扩副本** —— 不要 deploy.replicas, 不要 docker compose up --scale。 # 退出流程见 app/ws/runner.py 的 _shutdown: 刷水位 → 最后一次 ack → 关连接 → 清心跳, # stop_grace_period 给足 25 秒是为了让这一串跑完; 强杀会丢掉最后一批 ack (不丢数据, # 只是让 QMT 多留一会儿, 重连后按 last_seq 补发)。 pms-ws: <<: *pms-base container_name: ${PMS_STACK_PREFIX:-}pms-ws profiles: ["ws"] command: python -m app.ws.runner stop_grace_period: 25s stop_signal: SIGTERM