Wholesale 集群部署手册

本手册演示如何部署一个 3 节点 Wholesale 高可用集群,包含 MySQL 数据库、Nginx 负载均衡、SipFlow 信令采集的完整部署流程。

1. 架构总览

                     ┌─────────────────────┐
                     │   SIP Load Balancer  │
                     │  (Nginx/OpenSIPS)    │
                     └──────────┬──────────┘
                ┌───────────────┼───────────────┐
                ▼               ▼               ▼
         ┌────────────┐ ┌────────────┐ ┌────────────┐
         │  Node 1    │ │  Node 2    │ │  Node 3    │
         │ 10.0.0.11  │ │ 10.0.0.12  │ │ 10.0.0.13  │
         │ RustPBX    │◄──► RustPBX  │◄──► RustPBX  │
         │ +Wholesale │    +Wholesale │    +Wholesale│
         └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
               │              │              │
               └──────────────┼──────────────┘
                              ▼
                     ┌─────────────────┐
                     │  MySQL 8.4 LTS  │
                     │  10.0.0.20:3306 │
                     └─────────────────┘
                              │
                     ┌────────┴────────┐
                     ▼                 ▼
              ┌────────────┐   ┌────────────┐
              │  SipFlow A │   │  SipFlow B │
              │ 10.0.0.21  │   │ 10.0.0.22  │
              └────────────┘   └────────────┘

2. 节点规划

角色IP配置磁盘
Node 110.0.0.118C/16G200G SSD
Node 210.0.0.128C/16G200G SSD
Node 310.0.0.138C/16G200G SSD
MySQL10.0.0.208C/32G500G SSD(或 RDS)
SipFlow A10.0.0.214C/8G500G HDD
SipFlow B10.0.0.224C/8G500G HDD

镜像版本:docker.cnb.cool/miuda.ai/rustpbx:0.4.5

3. MySQL 部署

3.1 安装(Docker)

docker run -d --name mysql \
  --restart always \
  -e MYSQL_ROOT_PASSWORD=RootP@ss2026 \
  -e MYSQL_CHARACTER_SET_SERVER=utf8mb4 \
  -e MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci \
  -v /data/mysql:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8.4-oracle

3.2 创建数据库和用户

docker exec -it mysql mysql -uroot -pRootP@ss2026 <<'EOF'
CREATE DATABASE IF NOT EXISTS rustpbx CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'rustpbx'@'%' IDENTIFIED BY 'WsDbP@ss2026';
GRANT ALL PRIVILEGES ON rustpbx.* TO 'rustpbx'@'%';
FLUSH PRIVILEGES;

-- 优化配置(连接池)
SET GLOBAL max_connections = 500;
SET GLOBAL innodb_buffer_pool_size = 4294967296;
EOF

3.3 验证

mysql -h 10.0.0.20 -u rustpbx -pWsDbP@ss2026 -e "SELECT 1"

3.4 MySQL 参数建议

参数建议值说明
max_connections5003 节点 × ~20 连接 + 余量
innodb_buffer_pool_size4G可用内存的 50-70%
wait_timeout28800避免连接池断连
character_set_serverutf8mb4必须使用 utf8mb4

4. SipFlow 集群部署

4.1 SipFlow A(10.0.0.21)

docker run -d --name sipflow-a \
  --restart always \
  --network host \
  -v /data/sipflow:/data/sipflow \
  docker.cnb.cool/miuda.ai/rustpbx:0.4.5 \
  /app/sipflow --addr 0.0.0.0 --port 3000 --http-port 3001 --root /data/sipflow

4.2 SipFlow B(10.0.0.22)

docker run -d --name sipflow-b \
  --restart always \
  --network host \
  -v /data/sipflow:/data/sipflow \
  docker.cnb.cool/miuda.ai/rustpbx:0.4.5 \
  /app/sipflow --addr 0.0.0.0 --port 3000 --http-port 3001 --root /data/sipflow

4.3 验证

curl http://10.0.0.21:3001/health
curl http://10.0.0.22:3001/health

5. RustPBX Wholesale 节点部署

5.1 准备目录

在每个节点上执行(以 Node 1 为例):

mkdir -p /opt/rustpbx/{config/trunks,config/routes,config/acl}

5.2 config.toml(Node 1 — 10.0.0.11)

http_addr = "0.0.0.0:8080"
database_url = "mysql://rustpbx:WsDbP@ss2026@10.0.0.20:3306/rustpbx"
external_ip = "10.0.0.11"
log_level = "info"

[proxy]
addr = "0.0.0.0"
udp_port = 5060
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto"
addons = ["wholesale"]
max_concurrency = 1000

[proxy.dos]
enabled = true
max_cps_per_ip = 50

[console]
session_secret = "wholesale-cluster-secret-2026"
allow_registration = false

# 核心集群:注册/在线状态同步
[cluster]
peers = [
  { addr = "10.0.0.12", sip_port = 5060, ami_port = 8080 },
  { addr = "10.0.0.13", sip_port = 5060, ami_port = 8080 },
]

# SipFlow 远程集群
[sipflow]
[sipflow.remote]
flush_interval_secs = 5

[[sipflow.remote.nodes]]
udp = "10.0.0.21:3000"
http = "http://10.0.0.21:3001"

[[sipflow.remote.nodes]]
udp = "10.0.0.22:3000"
http = "http://10.0.0.22:3001"

# 录音上传(可选)
[recording]
enabled = false

# CDR 配置
[callrecord]
enabled = true

5.3 config/wholesale.toml(Node 1)

# Wholesale 集群配置
[cluster]

[[cluster.peers]]
name = "node-2"
url = "http://10.0.0.12:8080"
api_key = "wholesale-cluster-api-key-2026"

[[cluster.peers]]
name = "node-3"
url = "http://10.0.0.13:8080"
api_key = "wholesale-cluster-api-key-2026"

# 路由缓存
[route_cache]
capacity = 10000
ttl_secs = 30

# 熔断器默认参数
[circuit_breaker]
failure_threshold = 5
open_duration_secs = 30
half_open_probes = 1
failure_codes = [503, 408, 504]

# 滑动窗口 ASR/ACD 监控
[sliding_window]
enabled = true
window_secs = 300
max_events_per_trunk = 10000
asr_alert_threshold = 30.0
min_calls_for_stats = 10

5.4 其他节点配置

Node 2(10.0.0.12):复制 Node 1 的配置,修改以下字段:

# config.toml
external_ip = "10.0.0.12"

[cluster]
peers = [
  { addr = "10.0.0.11", sip_port = 5060, ami_port = 8080 },
  { addr = "10.0.0.13", sip_port = 5060, ami_port = 8080 },
]
# config/wholesale.toml
[[cluster.peers]]
name = "node-1"
url = "http://10.0.0.11:8080"
api_key = "wholesale-cluster-api-key-2026"

[[cluster.peers]]
name = "node-3"
url = "http://10.0.0.13:8080"
api_key = "wholesale-cluster-api-key-2026"

Node 3(10.0.0.13):同理,external_ip = "10.0.0.13",peers 指向 Node 1 和 Node 2。

5.5 启动节点

每个节点:

docker run -d --name rustpbx \
  --restart always \
  --network host \
  -v /opt/rustpbx/config.toml:/app/config.toml \
  -v /opt/rustpbx/config:/app/config \
  docker.cnb.cool/miuda.ai/rustpbx:0.4.5

使用 --network host 确保 SIP UDP 和 RTP 端口正常工作。如果无法使用 host 网络,需要额外映射 RTP 端口范围(12000-42000/udp)。

5.6 验证启动

# 检查 HTTP 服务
curl http://10.0.0.11:8080/api/sbc/data
curl http://10.0.0.12:8080/api/sbc/data
curl http://10.0.0.13:8080/api/sbc/data

# 查看日志
docker logs -f rustpbx 2>&1 | head -50

首次启动时 Node 1 会自动执行数据库 migration,后续节点启动会跳过。

6. 负载均衡配置

6.1 SIP 负载均衡(Nginx Stream)

在 SIP Load Balancer 节点上:

# /etc/nginx/nginx.conf
stream {
    upstream sip_backend {
        # 源 IP 哈希:同一客户端的 REGISTER/INVITE 路由到同一节点
        hash $remote_addr consistent;

        server 10.0.0.11:5060;
        server 10.0.0.12:5060;
        server 10.0.0.13:5060;
    }

    server {
        listen 5060 udp;
        proxy_pass sip_backend;
        proxy_timeout 30s;
        proxy_responses 1;
    }
}

6.2 HTTP 负载均衡(Nginx HTTP)

http {
    upstream rustpbx_http {
        ip_hash;  # Session 粘性(控制台登录需要)
        server 10.0.0.11:8080;
        server 10.0.0.12:8080;
        server 10.0.0.13:8080;
    }

    server {
        listen 80;
        client_max_body_size 50m;

        location / {
            proxy_pass http://rustpbx_http;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

6.3 RTP 端口

RTP 流量不经过负载均衡,直接到达实际处理通话的节点。因此:

  • 每个节点的 external_ip 必须配置为节点自身的可达 IP
  • 防火墙需放通每个节点的 RTP 端口范围(默认 12000-42000/udp)

7. Wholesale 业务配置

7.1 创建超级用户

在任一节点上创建(数据库共享,只需一次):

docker exec -it rustpbx /app/rustpbx --create-superuser admin Admin@2026

7.2 登录控制台

通过负载均衡地址访问:http://<lb-ip>/console/login

7.3 配置运营商中继

config/trunks/carrier_a.toml(在所有节点同步):

# 在每个节点上创建相同的中继配置
cat > /opt/rustpbx/config/trunks/carrier_a.toml << 'EOF'
[[trunk]]
name = "carrier-a"
dest = "sip:10.0.1.100:5060"
direction = "outbound"
codec = ["pcmu", "pcma", "g729"]
max_calls = 300
max_cps = 50
weight = 100
EOF

建议使用 rsync 或 Git 同步:

# 从 Node 1 同步到其他节点
rsync -avz /opt/rustpbx/config/ 10.0.0.12:/opt/rustpbx/config/
rsync -avz /opt/rustpbx/config/ 10.0.0.13:/opt/rustpbx/config/

7.4 费率表 / 路由策略 / 租户

这些通过 Web 控制台在数据库中管理,所有节点共享同一数据库,无需逐节点配置。

配置完成后在控制台点击 “重新加载” 按钮,各节点从数据库重新加载到内存 Trie。

8. 集群验证

8.1 集群状态

Wholesale → 集群管理:应显示 node-2、node-3 在线。

8.2 通话测试

通过 SIP LB 发起测试呼叫,检查:

  1. 通话正常建立
  2. Wholesale → 话单管理:CDR 已生成
  3. Wholesale → 集群 → 活跃通话:能看到跨节点通话列表

8.3 故障转移测试

  1. 停止 Node 2:docker stop rustpbx(在 10.0.0.12 上)
  2. 通过 SIP LB 发起呼叫 → 应自动路由到 Node 1 或 Node 3
  3. 恢复 Node 2:docker start rustpbx
  4. 检查集群页面 Node 2 恢复在线

8.4 数据一致性验证

  1. 在 Node 1 的控制台创建一个费率条目
  2. 在 Node 2 的控制台点击“重新加载“
  3. 在 Node 2 使用价格计算器验证新费率可见

9. 限额分配策略

租户限额3 节点分配说明
最大并发 300每节点 100300 / 3
最大 CPS 30每节点 1030 / 3

由于 SIP LB 使用源 IP 哈希,实际流量不会完全均匀,建议限额留 20% 余量。

实际配置在 Wholesale → 租户详情 → 设置 中按节点调整。

10. 监控体系

10.1 Prometheus 采集

# prometheus.yml
scrape_configs:
  - job_name: 'rustpbx-wholesale'
    scrape_interval: 15s
    static_configs:
      - targets:
          - '10.0.0.11:8080'
          - '10.0.0.12:8080'
          - '10.0.0.13:8080'

10.2 关键指标

指标说明告警阈值
wholesale_calls_total呼叫总量-
wholesale_revenue_microcurrency_total收入-
wholesale_concurrent_limit_rejected_total并发拒绝 > 0需扩容
wholesale_cps_limit_rejected_totalCPS 拒绝 > 0需调整
wholesale_circuit_breaker_state熔断状态变化Open 时告警
wholesale_routing_no_routes_total无路由> 0 需检查配置

10.3 健康检查

# 每个节点
curl http://10.0.0.11:8080/healthz
curl http://10.0.0.12:8080/healthz
curl http://10.0.0.13:8080/healthz

11. 日常运维

11.1 配置同步

配置类型同步方式说明
config.toml手动/rsync/Git各节点略有不同(IP、peers)
config/trunks/*.tomlrsync/Git所有节点相同
config/routes/*.tomlrsync/Git所有节点相同
config/wholesale.toml手动各节点 peers 不同
费率表/路由策略/租户数据库(自动)所有节点共享
熔断器状态每节点独立需分别查看

11.2 滚动重启

# 逐节点重启,确保至少 2 个节点在线
ssh 10.0.0.11 "docker restart rustpbx"
sleep 30  # 等待启动完成
ssh 10.0.0.12 "docker restart rustpbx"
sleep 30
ssh 10.0.0.13 "docker restart rustpbx"

11.3 数据库备份

# 每日全量备份
mysqldump -h 10.0.0.20 -u root -pRootP@ss2026 \
  --single-transaction --quick \
  rustpbx | gzip > /backup/rustpbx_$(date +%Y%m%d).sql.gz

# 保留 30 天
find /backup -name "rustpbx_*.sql.gz" -mtime +30 -delete

11.4 CDR 数据维护

-- 检查 CDR 数据量
SELECT COUNT(*), DATE(call_start) FROM wholesale_cdrs
GROUP BY DATE(call_start) ORDER BY DATE(call_start) DESC LIMIT 7;

-- 建议按月分区(MySQL 8.4)
ALTER TABLE wholesale_cdrs PARTITION BY RANGE (TO_DAYS(call_start)) (
    PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')),
    PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

12. 版本升级

# 1. 拉取新版本
docker pull docker.cnb.cool/miuda.ai/rustpbx:0.4.5

# 2. 逐节点升级
ssh 10.0.0.11 "docker stop rustpbx && docker rm rustpbx"
ssh 10.0.0.11 "docker run -d --name rustpbx --restart always --network host \
  -v /opt/rustpbx/config.toml:/app/config.toml \
  -v /opt/rustpbx/config:/app/config \
  docker.cnb.cool/miuda.ai/rustpbx:0.4.5"

# 等待启动完成,验证健康
curl http://10.0.0.11:8080/healthz

# 3. 重复 Node 2、Node 3

新版本首次启动可能执行数据库 migration。确保同时只有一个节点执行 migration(先启动一个节点,等 migration 完成后再启动其他节点)。