> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcphub.app/llms.txt
> Use this file to discover all available pages before exploring further.

# 多副本部署

> 共享 PostgreSQL，并明确会话、配置同步和故障恢复边界

设置 `MCPHUB_REPLICA_MODE=true` 显式启用多副本模式。相同版本的 MCPHub 通过负载均衡共享 PostgreSQL。MCP 2026 普通无状态调用可分发到任意就绪节点；旧协议会话、隔离上游客户端和 OpenAPI Cookie 状态仍属于单个进程，必须配置亲和性，节点故障后重新初始化。执行中的调用不迁移、不重放。

## 先准备数据库，再启动副本

所有节点设置 `USE_DB=true` 和同一 `DB_URL`，使用一致且持久的 `JWT_SECRET`（至少 32 字节）、`MCPHUB_CREDENTIAL_ENCRYPTION_KEY`（32 字节的 base64 编码）以及对外安装地址和 OAuth 地址。每个节点设置独立的 `MCPHUB_NODE_ID`，默认使用主机名。首次部署通过 `ADMIN_PASSWORD` 指定管理员密码。

构建后，先运行一个独立建库任务：

```sh theme={null}
DB_SYNCHRONIZE=true MCPHUB_REPLICA_MODE=false pnpm db:setup
```

该任务复用现有数据库初始化和迁移流程，必要时创建管理员，并安装配置版本跟踪。升级已有数据库前先备份，避免多个建库任务并发执行。声明式 `MCPHUB_SETTINGS_SYNC` 仅在此任务中执行，接入节点必须取消该变量。

随后启动各副本：

```sh theme={null}
DB_SYNCHRONIZE=false MCPHUB_REPLICA_MODE=true MCPHUB_NODE_ID=a pnpm start
```

接入节点启动时不创建扩展、不修改表结构、不重建向量索引。缺少数据库结构、管理员种子、持久密钥，或接入节点启用了声明式同步，都会阻止启动。stdio 服务器需要各节点具有一致的程序、环境和本地文件；子进程与文件系统状态不会自动共享。

## 使用 Docker 镜像部署

仓库的 [`examples/replicas/`](https://github.com/samanhappy/mcphub/tree/main/examples/replicas) 提供可直接使用的 Compose 示例，包含两个 MCPHub 副本、持久化 PostgreSQL、一次性建库任务和 Nginx。需要支持 `--wait` 的 Docker Compose v2，直接使用官方 `samanhappy/mcphub` 镜像。示例使用 `latest`，生产环境应将 `MCPHUB_IMAGE` 固定为已发布版本标签或 digest。

```sh theme={null}
# 在仓库根目录执行
cd examples/replicas

# 仅在全新安装时生成一次；保留此文件并备份其中的密钥。
umask 077
cat > .env <<EOF
MCPHUB_IMAGE=samanhappy/mcphub:latest
POSTGRES_PASSWORD=$(openssl rand -hex 24)
JWT_SECRET=$(openssl rand -hex 32)
MCPHUB_CREDENTIAL_ENCRYPTION_KEY=$(openssl rand -base64 32)
ADMIN_PASSWORD=$(openssl rand -hex 24)
INSTALL_BASE_URL=http://localhost:3080
EOF

docker compose pull
docker compose up -d --wait postgres
docker compose run --rm setup
docker compose up -d --wait hub-a hub-b proxy
docker compose ps
curl --fail http://localhost:3080/nodes/a/readyz
curl --fail http://localhost:3080/nodes/b/readyz
```

打开 `http://localhost:3080`，使用 `admin` 和 `.env` 中的 `ADMIN_PASSWORD` 登录。建库任务和两个副本始终使用同一镜像；`node dist/db/setup.js` 在容器内执行，宿主机无需安装 Node.js 或 pnpm。只读 `settings.json` 用于初始化空安装，默认关闭 OAuth 服务；启动后通过管理界面/API 管理共享配置。共享配置和凭据存入 PostgreSQL，不共用可写设置文件或 stdio 数据卷。

仅在验证未发布源码时，才需要在仓库根目录执行 `docker build -t mcphub:replicas .`，将 `.env` 中的镜像改为 `MCPHUB_IMAGE=mcphub:replicas`，并跳过该本地镜像的拉取。

网关仅绑定宿主机回环地址；对外部署时通过 HTTPS 入口暴露，并在**首次建库前**将 `INSTALL_BASE_URL` 改为实际 HTTPS 地址，确保所有节点使用相同的安装及 OAuth 对外地址。示例 Nginx 已配置流式响应、亲和性请求头及固定节点入口。Compose 健康检查仅控制首次启动顺序，Nginx 不主动轮询 `/readyz`，也不会自动摘除未就绪节点；生产流量入口需要单独实现。重建容器可能改变 IP，更新后端后应重建或重载 Nginx。此单机示例不提供宿主机、数据库或负载均衡自身的容灾。

已有安装应复用原数据库、JWT 密钥和凭据加密密钥，并在建库前备份数据库及密钥。升级应用时不要重新生成 `.env` 或顺便修改数据库密码。涉及表结构变更时先停止接流量和副本；本示例采用维护窗口，不假定所有结构变更都支持滚动升级：

```sh theme={null}
docker compose stop proxy hub-a hub-b
# 将 .env 中 MCPHUB_IMAGE 改为新固定版本后拉取。
docker compose pull setup hub-a hub-b
docker compose run --rm setup
docker compose up -d --wait hub-a hub-b
docker compose up -d --force-recreate proxy
```

建库失败时保持服务停止，先检查错误再继续。`docker compose down` 保留数据库命名卷；`docker compose down -v` 会删除它。不要将 `.env` 提交到版本库，数据库备份必须同时保留对应密钥。此 Compose 文件不会自动迁移已有文件模式安装，迁移时需要先将原设置文件及凭据 sidecar 挂载到建库任务中。

## Kubernetes 部署

[`examples/kubernetes/replicas/`](https://github.com/samanhappy/mcphub/tree/main/examples/kubernetes/replicas) 提供独立建库 Job、双副本 Deployment、共享 ConfigMap、Service/Ingress 和 PodDisruptionBudget。默认使用官方 `samanhappy/mcphub:latest` 镜像；生产部署前，将 `setup-job.yaml` 和 `deployment.yaml` **两处镜像**固定为同一版本或 digest。

先准备可访问且支持 pgvector 的 PostgreSQL，并为建库任务提供相应权限；数据库部署和备份不包含在清单中。对外暴露示例 Ingress 还需已有 Traefik 控制器、`websecure` 入口、DNS，以及 `mcphub` 命名空间中的 `mcphub-tls` TLS Secret。建库前，将 `configmap.yaml` 和 `service.yaml` 中的 `mcphub.example.com` 改为实际对外域名。只读初始配置不包含服务器，默认关闭 OAuth；建库后通过管理界面/API 管理共享配置。

```sh theme={null}
# 在仓库根目录执行；仅在全新安装时创建密钥。
cd examples/kubernetes/replicas
kubectl create namespace mcphub
umask 077
cat > .env <<EOF
DB_URL=postgresql://mcphub:REPLACE_ME@postgres.example.com:5432/mcphub
JWT_SECRET=$(openssl rand -hex 32)
MCPHUB_CREDENTIAL_ENCRYPTION_KEY=$(openssl rand -base64 32)
EOF
cat > .env.local <<EOF
ADMIN_PASSWORD=$(openssl rand -hex 24)
EOF
# 创建 Secret 前，先将 .env 中 DB_URL 改为实际数据库连接串。
kubectl -n mcphub create secret generic mcphub-secrets --from-env-file=.env
kubectl -n mcphub create secret generic mcphub-admin --from-env-file=.env.local
kubectl -n mcphub apply -f configmap.yaml
kubectl -n mcphub apply -f setup-job.yaml
kubectl -n mcphub wait --for=condition=complete job/mcphub-setup --timeout=600s && \
  kubectl -n mcphub apply -f deployment.yaml
```

仅在 Job 成功后继续。失败时先通过 `kubectl -n mcphub logs job/mcphub-setup` 排查，再创建接入 Pod；`backoffLimit: 0` 禁止自动重试表结构变更。同一时间只运行一个建库 Job，不要放进每个 Pod 的 init container。升级时复用数据库和共享密钥。首次管理员密码保存在 `.env.local`，仅建库 Job 接收该 Secret。妥善保存这两个本地密钥文件，并随数据库备份保留对应的 JWT/加密密钥。

```sh theme={null}
# 对外暴露 HTTPS 前，确保当前命名空间中已有 mcphub-tls。
kubectl -n mcphub rollout status deployment/mcphub --timeout=600s
kubectl -n mcphub apply -f service.yaml
kubectl -n mcphub get pods -l app.kubernetes.io/name=mcphub -o wide
kubectl -n mcphub get endpointslices -l kubernetes.io/service-name=mcphub
curl --fail https://mcphub.example.com/readyz
```

[启动和存活探针](https://kubernetes.io/docs/concepts/workloads/pods/probes/)使用 `/livez`，就绪探针使用 `/readyz`，使数据库故障导致摘流，而非因存活检查触发重启。按上游需求调整十分钟启动预算和资源限制。节点标识取 Pod 名；调度器优先跨主机分散副本，宿主机冗余至少需要两个工作节点。PDB 在主动驱逐时保留一个副本，不能防止崩溃或显式缩容。preStop 等待五秒，让端点变化先传播，再接收 SIGTERM；30 秒退出宽限期包含该等待及 MCPHub 最多十秒的 HTTP 排空。应根据入口摘流耗时调整这些值，Pod 丢失仍会中断原会话。

示例使用 [Traefik 的 Service Cookie 亲和性](https://doc.traefik.io/traefik/reference/routing-configuration/kubernetes/ingress/#on-service)，直接路由到已就绪 Pod 端点。有状态客户端必须从初始化开始保存并回传 `mcphub_affinity`；Cookie 带 Secure 标记，因此必须使用 HTTPS。不能保留 Cookie 的客户端需另配控制器支持的稳定请求头路由或固定 Pod 入口。其他 Ingress 控制器需要替换对应亲和性配置，仅修改 `ingressClassName` 不够。不要为 MCP 请求添加响应缓冲或重试中间件，并按长连接需求配置控制器的请求/响应超时；这些清单不负责安装和配置控制器本身。

涉及表结构变更的升级，应先备份数据库、停止接入 Pod，并将两处镜像改为同一个新固定版本，在维护窗口内执行：

```sh theme={null}
kubectl -n mcphub scale deployment/mcphub --replicas=0
kubectl -n mcphub wait --for=delete pod -l app.kubernetes.io/name=mcphub --timeout=120s
kubectl -n mcphub delete job mcphub-setup --ignore-not-found
kubectl -n mcphub apply -f setup-job.yaml
kubectl -n mcphub wait --for=condition=complete job/mcphub-setup --timeout=600s && \
  kubectl -n mcphub apply -f deployment.yaml
kubectl -n mcphub rollout status deployment/mcphub --timeout=600s
```

建库失败时保持副本停止。RollingUpdate 参数用于表结构及配置兼容时的镜像滚动更新，不代表任意表结构变更都能混跑。Kubernetes 不会共享 Pod 间的 stdio 进程、Cookie 状态或本地文件；集群、数据库及入口本身的高可用仍需部署方保障。

## 配置与授权

数据库触发器在配置写入的同一事务中递增版本，覆盖后台、CLI 和导入等入口。副本每 5 秒检查版本，刷新变化的服务器并保留未变化的连接。已应用版本表示配置已加载，不代表所有上游均已连接；上游连接失败单独报告为降级。

配置 API 读取响应包含 `X-MCPHub-Config-Version` 和 `ETag`。保存时携带读取到的 `If-Match`，版本过期返回 `412`。没有前置条件的并发写入仍会串行处理，等待期间版本改变返回 `409`；未提供 `If-Match` 时无法识别请求开始之前发生的修改。发生冲突后应重新读取并人工检查，不自动重提。失败的配置事务在响应前回滚。

工具调用独立于轮询，读取 PostgreSQL 中最新的服务器权限和启停状态；读取失败拒绝调用。OAuth 授权码和注册管理 token 共享存储、按哈希保存并检查过期，授权码原子消费。可能轮换 token 的 OAuth 客户端操作使用凭据范围的 PostgreSQL 互斥锁，过期写入通过条件更新拒绝。此类操作会占用额外连接，连接池应预留余量。

配置应用失败保留原已应用版本，就绪检查失败，后续轮询重试。数据库故障时也不接收新的工具调用，不提供离线授权模式。

## 负载均衡与状态

关闭 SSE 响应缓冲并设置足够长的超时，不为结果未知的工具调用开启代理重试。多副本模式关闭 MCPHub 的重连后工具重放和 OpenAPI 401 重放；上游协议自身仍可能执行认证处理。

从初始化开始使用稳定的工作流标识。参考 Nginx 配置使用 `X-MCPHub-Affinity`，缺省时使用 `X-MCPHub-State-Id` 进行一致性哈希。这些值只用于选节点，不参与身份验证。仅按 `Mcp-Session-Id` 哈希无法覆盖首次无会话握手。不能设置亲和性请求头的客户端可使用参考配置中的 `/nodes/a/`、`/nodes/b/` 固定入口；节点故障后选择存活节点重新初始化。对外 OAuth 回调及资源地址保持使用安装地址。

现代通知订阅也属于单个节点。节点应用配置时向本地客户端广播变化；此版本不提供持久的跨节点上游通知总线，客户端重连后应重新发现能力。

| 入口 | 用途 |
| - | - |
| `/livez` | 存活检查，数据库故障不触发重启。 |
| `/readyz` | 初始化完成、仍接流量、配置已应用且数据库可达。 |
| `/health` | 保留原数据库与上游健康摘要。 |
| `/api/cluster` | 管理员查看节点、期望/已应用版本及应用错误。 |

响应通过 `X-MCPHub-Node-Id`、`X-Request-Id` 标识节点与请求；日志携带上下文，指标包含节点与配置版本。停机时停止接流量，等待 HTTP 请求最多 10 秒，再关闭上游客户端。编排器退出宽限期应超过 10 秒，并先摘流。超时的订阅连接断开后由客户端重连；断流不代表上游副作用未执行。

## Docker 实测

```sh theme={null}
pnpm test:replicas
```

脚本使用锁定依赖构建 Node 22 后端镜像，启动两个节点、PostgreSQL、Nginx 和受控 OpenAPI 上游，验证配置收敛、并发冲突、跨节点 OAuth、撤销、亲和性、断流不重放、停库、杀节点与排空，结束时清理容器和临时数据库。`tests/docker/replicas/` 中的固定密码及 HTTP 地址仅供本机隔离测试。测试镜像不包含仪表盘、Python 和 uv；需要这些能力时使用项目正式 Dockerfile。

此版本不包含跨地域容灾、会话无损接管、工具恰好执行一次、独立控制面或本地状态共享。PostgreSQL 与负载均衡本身也需要独立的高可用安排。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.