From c33c12c5e9171c0a544dd05411e35e0ba0629d7f Mon Sep 17 00:00:00 2001 From: k2safe Date: Tue, 22 Sep 2026 02:43:51 +0800 Subject: [PATCH] feat: add two-node compose deployment --- deploy/docker-compose.ha.yml | 51 +++++++++++++++++++++++ docs/08-development-plan.md | 4 ++ docs/22-compose-deployment.md | 40 +++++++++++++++++- docs/README.md | 2 + docs/reports/phase-11-two-node-compose.md | 42 +++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 deploy/docker-compose.ha.yml create mode 100644 docs/reports/phase-11-two-node-compose.md diff --git a/deploy/docker-compose.ha.yml b/deploy/docker-compose.ha.yml new file mode 100644 index 0000000..ff04144 --- /dev/null +++ b/deploy/docker-compose.ha.yml @@ -0,0 +1,51 @@ +# 与 deploy/docker-compose.yml 一起使用: +# docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.ha.yml up -d mysql verify orbit-a orbit-b +# 默认不启动 base 文件中的 orbit 单节点服务,避免重复实例。 + +x-orbit-environment: &orbit-environment + ORBIT_HTTP_ADDR: 0.0.0.0:8080 + ORBIT_MYSQL_DSN: "${ORBIT_DB_USER}:${ORBIT_DB_PASSWORD}@tcp(mysql:3306)/${ORBIT_DB_NAME:-orbit}?parseTime=true&multiStatements=true" + ORBIT_DB_MAX_OPEN: ${ORBIT_DB_MAX_OPEN:-20} + ORBIT_DB_MAX_IDLE: ${ORBIT_DB_MAX_IDLE:-5} + ORBIT_WORKFLOW_TICK_INTERVAL: ${ORBIT_WORKFLOW_TICK_INTERVAL:-1s} + ORBIT_SCHEDULE_TICK_INTERVAL: ${ORBIT_SCHEDULE_TICK_INTERVAL:-1s} + ORBIT_RECOVERY_TICK_INTERVAL: ${ORBIT_RECOVERY_TICK_INTERVAL:-2s} + ORBIT_RUNTIME_BATCH_LIMIT: ${ORBIT_RUNTIME_BATCH_LIMIT:-100} + ORBIT_SHUTDOWN_TIMEOUT: ${ORBIT_SHUTDOWN_TIMEOUT:-10s} + ORBIT_WORKER_CONNECTION_LIMIT: ${ORBIT_WORKER_CONNECTION_LIMIT:-128} + ORBIT_EVENT_CONNECTION_LIMIT: ${ORBIT_EVENT_CONNECTION_LIMIT:-128} + ORBIT_DISPATCH_WORKERS: ${ORBIT_DISPATCH_WORKERS:-4} + ORBIT_DISPATCH_CAPACITY: ${ORBIT_DISPATCH_CAPACITY:-128} + ORBIT_CONSOLE_QUEUE_ID: ${ORBIT_CONSOLE_QUEUE_ID:-} + ORBIT_METRICS_ADDR: ${ORBIT_METRICS_ADDR:-} + +services: + orbit: + profiles: + - single + + orbit-a: + image: ${ORBIT_IMAGE:-orbit:dev} + restart: unless-stopped + init: true + depends_on: + verify: + condition: service_completed_successfully + environment: + <<: *orbit-environment + ports: + - "${ORBIT_HTTP_BIND:-127.0.0.1}:${ORBIT_HTTP_PORT_A:-8081}:8080" + stop_grace_period: 30s + + orbit-b: + image: ${ORBIT_IMAGE:-orbit:dev} + restart: unless-stopped + init: true + depends_on: + verify: + condition: service_completed_successfully + environment: + <<: *orbit-environment + ports: + - "${ORBIT_HTTP_BIND:-127.0.0.1}:${ORBIT_HTTP_PORT_B:-8082}:8080" + stop_grace_period: 30s diff --git a/docs/08-development-plan.md b/docs/08-development-plan.md index 122df41..5731a94 100644 --- a/docs/08-development-plan.md +++ b/docs/08-development-plan.md @@ -52,6 +52,10 @@ 新增 `deploy/nginx/orbit.conf.example` 作为可选 HTTPS/WSS 入口模板:Compose 默认只将业务端口绑定到 `127.0.0.1`,Nginx 负责 TLS 终止、WebSocket 升级、健康探针代理和控制台登录来源限速;配置语法及端口边界证据见[HTTPS/WSS 入口基线验证](reports/phase-10-ingress.md)。Nginx 是部署入口,不会成为 Orbit 服务端的业务依赖,真实域名、正式证书、入口故障和公网重连仍需目标环境验收。 +新增 `deploy/docker-compose.ha.yml` 作为两节点隔离验收覆盖:节点共享持久化 MySQL 状态但使用独立 HTTP 端口,base 服务中的单节点 Orbit 通过 profile 关闭;两个 `ready` 节点均通过后才能接入入口,滚动停止和重新拉起按[Compose 部署基线](22-compose-deployment.md)执行。该覆盖不替代跨故障域节点、数据库自动切换、负载均衡器和目标容量验收。 + +两节点覆盖已完成一次真实 Docker 回归:空库迁移和权限核验后两个节点均就绪,停止 A 后 B 保持就绪,A 重新拉起并重新核验后恢复;证据见[两节点 Compose 覆盖验证](reports/phase-11-two-node-compose.md)。该结果只关闭单机双进程启动/滚动动作,不关闭生产 HA。 + ## 2. 持续推进方式 每个阶段按以下顺序推进: diff --git a/docs/22-compose-deployment.md b/docs/22-compose-deployment.md index 0828bdb..d5617d4 100644 --- a/docs/22-compose-deployment.md +++ b/docs/22-compose-deployment.md @@ -111,6 +111,44 @@ docker compose -f deploy/docker-compose.yml down Compose 没有为 scratch 镜像伪造容器内 HTTP healthcheck;编排器或入口网关应调用 `/health/live`、`/health/ready` 和 `/version`。`ready` 返回 `503` 时摘除节点,保留原幂等键并等待数据库恢复。只有明确的废弃环境才使用 `docker compose down -v`,生产数据卷不得随服务停止删除。 -## 5. 当前边界 +## 5. 两节点覆盖与滚动替换 + +需要在同一台主机验证多节点接管时,叠加 `deploy/docker-compose.ha.yml`。两个 Orbit 节点共享 MySQL、迁移账本、租约和流程检查点;每个进程仍生成独立节点标识,停止一个节点不会把另一个节点的持久化执行权一并停止: + +```sh +export ORBIT_HTTP_BIND=127.0.0.1 +export ORBIT_HTTP_PORT_A=8081 +export ORBIT_HTTP_PORT_B=8082 + +docker compose \ + -f deploy/docker-compose.yml \ + -f deploy/docker-compose.ha.yml \ + up -d mysql verify orbit-a orbit-b + +curl --fail http://127.0.0.1:8081/health/ready +curl --fail http://127.0.0.1:8082/health/ready +``` + +入口层把 Nginx `upstream orbit_backend` 改为两个后端: + +```nginx +upstream orbit_backend { + server 127.0.0.1:8081; + server 127.0.0.1:8082; + keepalive 64; +} +``` + +滚动替换时先让入口健康检查摘除一个节点,再停止并更新它;确认另一节点 `ready` 后再恢复流量,最后启动更新后的节点并等待两个后端都通过健康检查: + +```sh +docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.ha.yml stop orbit-a +# 更新镜像标签后重新拉起 orbit-a;迁移和 verify 仍必须先成功 +docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.ha.yml up -d orbit-a +``` + +这个覆盖配置用于单机和隔离验收;生产环境应把两个 Orbit 节点放在不同故障域,前置负载均衡器负责健康摘除,MySQL 由独立数据库高可用方案提供,不把单机 Compose 多节点样本当成生产 HA 结论。 + +## 6. 当前边界 该文件是可重复的单节点启动基线,不代表自动数据库故障切换、跨故障域多副本、备份恢复或容量目标已经验收。生产上线还需要真实 MySQL 拓扑、滚动替换、凭据轮换、备份恢复、压测和 RPO/RTO 证据;对应门槛以[交付核对清单](release-checklist.md)为准。 diff --git a/docs/README.md b/docs/README.md index d0f27e9..69627e2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,6 +27,8 @@ | [21 容器发布说明](21-container-deployment.md) | 最小镜像、迁移/核验/启动顺序、探针和发布边界 | | [22 Compose 部署基线](22-compose-deployment.md) | MySQL、迁移、核验、Orbit 服务和首次管理员初始化的可重复启动链路 | | [Nginx HTTPS/WSS 入口示例](../deploy/nginx/orbit.conf.example) | 域名、TLS、WebSocket 升级、健康探针和控制台登录限速 | +| [Compose 两节点覆盖](../deploy/docker-compose.ha.yml) | 共享 MySQL 状态、双节点就绪和滚动替换验收入口 | +| [两节点 Compose 覆盖验证](reports/phase-11-two-node-compose.md) | 双节点启动、单节点停止后的对等节点可用和重新加入 | | [09 可靠结果订阅](09-result-subscription-core.md) | 提交水位、冻结结果与可靠交付 | | [10 Weave决策与交付](10-workflow-engine-core.md) | 有限步骤决策和内部结果Inbox | | [11 Weave实例受理](11-workflow-admission.md) | 固定版本、初始图和首批任务事务 | diff --git a/docs/reports/phase-11-two-node-compose.md b/docs/reports/phase-11-two-node-compose.md new file mode 100644 index 0000000..70d6a3f --- /dev/null +++ b/docs/reports/phase-11-two-node-compose.md @@ -0,0 +1,42 @@ +# 两节点 Compose 覆盖验证 + +验证日期:2026-09-22 +验证范围:两个 Orbit 节点共享 MySQL、独立节点就绪、停止单节点后的对等节点可用、停止节点重新加入。 +验证环境:Docker Desktop、MySQL 8.4、当前 `orbit:dev` 镜像、Go 1.27.1 构建产物。 + +## 验证入口 + +```sh +docker compose \ + -f deploy/docker-compose.yml \ + -f deploy/docker-compose.ha.yml \ + up -d mysql verify orbit-a orbit-b +``` + +本机已有大量 Docker 自定义网络,本次只在临时测试覆盖文件中使用默认 `bridge` 网络和 MySQL 链接,以避开本机地址池耗尽;正式的 `deploy/docker-compose.ha.yml` 保持 Compose 项目隔离网络。 + +## 结果 + +一次性 MySQL 空库完成健康检查、迁移、权限收紧和 schema 核验后,两个 Orbit 节点均启动并通过就绪探针: + +```text +ha-e2e=both_ready +``` + +停止 `orbit-a` 后,`orbit-b` 继续返回就绪: + +```text +ha-e2e=peer_ready_after_orbit_a_stop +``` + +重新拉起 `orbit-a`,它重新完成迁移/核验依赖并恢复就绪: + +```text +ha-e2e=orbit_a_rejoined +``` + +两节点分别绑定临时主机端口 `18087` 和 `18088`;正式入口由 Nginx upstream 或负载均衡器同时指向两个节点,并以 `/health/ready` 作为摘流条件。 + +## 验收边界 + +该回归证明节点无本地权威状态、停止/重加入顺序和 Compose 依赖正确;它没有证明数据库自动切换、跨故障域、负载均衡器故障、生产证书轮换、容量余量或 RPO/RTO。任务执行和租约接管仍需在目标拓扑中按真实 Worker、慢消费者和数据库故障场景留证。