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 1 | 10.0.0.11 | 8C/16G | 200G SSD |
| Node 2 | 10.0.0.12 | 8C/16G | 200G SSD |
| Node 3 | 10.0.0.13 | 8C/16G | 200G SSD |
| MySQL | 10.0.0.20 | 8C/32G | 500G SSD(或 RDS) |
| SipFlow A | 10.0.0.21 | 4C/8G | 500G HDD |
| SipFlow B | 10.0.0.22 | 4C/8G | 500G 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_connections | 500 | 3 节点 × ~20 连接 + 余量 |
innodb_buffer_pool_size | 4G | 可用内存的 50-70% |
wait_timeout | 28800 | 避免连接池断连 |
character_set_server | utf8mb4 | 必须使用 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 发起测试呼叫,检查:
- 通话正常建立
- Wholesale → 话单管理:CDR 已生成
- Wholesale → 集群 → 活跃通话:能看到跨节点通话列表
8.3 故障转移测试
- 停止 Node 2:
docker stop rustpbx(在 10.0.0.12 上) - 通过 SIP LB 发起呼叫 → 应自动路由到 Node 1 或 Node 3
- 恢复 Node 2:
docker start rustpbx - 检查集群页面 Node 2 恢复在线
8.4 数据一致性验证
- 在 Node 1 的控制台创建一个费率条目
- 在 Node 2 的控制台点击“重新加载“
- 在 Node 2 使用价格计算器验证新费率可见
9. 限额分配策略
| 租户限额 | 3 节点分配 | 说明 |
|---|---|---|
| 最大并发 300 | 每节点 100 | 300 / 3 |
| 最大 CPS 30 | 每节点 10 | 30 / 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_total | CPS 拒绝 > 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/*.toml | rsync/Git | 所有节点相同 |
config/routes/*.toml | rsync/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 完成后再启动其他节点)。