Wholesale 集群经典配置参考

2 节点 Wholesale 集群的完整配置参考(MySQL 或 PostgreSQL)。展示每个节点的完整配置,标注节点间差异字段。

网络拓扑

┌──────────────────────────────────────────────────────────────┐
│ SIP 客户端 / 运营商网关                                       │
│ (通过 SIP 负载均衡或直连节点 IP)                              │
└──────────┬──────────────────────────────────┬───────────────┘
           │                                  │
    ┌──────▼──────┐                    ┌──────▼──────┐
    │   Node 1    │◄── SIP MESSAGE ──►│   Node 2    │
    │ 10.0.0.11   │                    │ 10.0.0.12   │
    │  主节点      │                    │  从节点      │
    │  + SipFlow  │                    │             │
    └──────┬──────┘                    └──────┬──────┘
           │                                  │
           │         ┌──────────────┐         │
           └────────►│    MySQL     │◄────────┘
                     │  10.0.0.20   │
                     └──────────────┘
  • SipFlow:仅在 Node 1 上以本地模式运行
  • 最少 2 节点实现高可用;生产环境建议 3 节点以上

Node 1 — 10.0.0.11(主节点 + SipFlow)

config.toml

# ── 通用配置 ────────────────────────────────────────────────────
http_addr = "0.0.0.0:8080"
# https_addr = "0.0.0.0:8443"            # 启用 HTTPS 时取消注释
database_url = "mysql://rustpbx:WsDbP@ss2026@10.0.0.20:3306/rustpbx"
external_ip = "10.0.0.11"                # ← 节点特有:本节点 IP
log_level = "info"
# log_file = "/var/log/rustpbx.log"
# log_rotation = "daily"

# ── RTP 媒体端口 ────────────────────────────────────────────────
rtp_start_port = 20000                   # RTP UDP 端口范围
rtp_end_port = 40000

# ── WebRTC(可选)──────────────────────────────────────────────
# webrtc_port_start = 30000
# webrtc_port_end = 40000

# ── SIP 代理 ──────────────────────────────────────────────────
[proxy]
addr = "0.0.0.0"
udp_port = 5060
# tcp_port = 5060                        # SIP TCP 传输
# tls_port = 5061                        # SIP TLS(需 ssl_certificate)
# ws_port = 5062                         # SIP WebSocket
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto"                     # all | auto | nat | none | bypass
max_concurrency = 1000

# NAT 穿透 realms —— 列出所有节点 IP + 公网 VIP
realms = ["10.0.0.11", "10.0.0.12", "203.0.113.100"]

# 启用 Addon
addons = ["wholesale"]

# 配置文件路径
generated_dir = "/static/docs-zh/wholesale/config"
routes_files = ["config/routes/*.toml"]
trunks_files = ["config/trunks/*.toml"]
queues_files = ["config/queue/*.toml"]

# DoS 防护
[proxy.dos]
enabled = true
max_cps_per_ip = 50
max_concurrent_per_ip = 200
scan_detection = true

# ── 控制台(Web 管理)─────────────────────────────────────────
[console]
session_secret = "wholesale-cluster-secret-2026"    # ← 所有节点必须一致
allow_registration = false
# base_path = "/"                        # 反向代理路径前缀
# api_prefix = "/api"                    # API 路由前缀

# ── 核心集群(注册/在线状态同步)──────────────────────────────
[cluster]
peers = [                                # ← 节点特有:仅列出其他节点
  { addr = "10.0.0.12", sip_port = 5060, ami_port = 8080 },
]

# ── SipFlow(本节点本地模式)──────────────────────────────────
[sipflow]
[sipflow.local]
subdirs = "daily"
flush_count = 100
flush_interval_secs = 5
id_cache_size = 10000

# ── 录音(可选)───────────────────────────────────────────────
[recording]
enabled = false
# auto_start = false
# type = "local"                         # local | http | s3

# ── CDR ────────────────────────────────────────────────────────
[callrecord]
enabled = true

config/wholesale.toml

# ── Wholesale 集群 ──────────────────────────────────────────
[cluster]

[[cluster.peers]]
name = "node-2"
url = "http://10.0.0.12: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

Node 2 — 10.0.0.12(从节点)

config.toml

# ── 通用配置 ────────────────────────────────────────────────────
http_addr = "0.0.0.0:8080"
database_url = "mysql://rustpbx:WsDbP@ss2026@10.0.0.20:3306/rustpbx"
external_ip = "10.0.0.12"                # ← 不同:Node 2 的 IP
log_level = "info"

# ── RTP 媒体端口 ────────────────────────────────────────────────
rtp_start_port = 20000
rtp_end_port = 40000

# ── SIP 代理 ──────────────────────────────────────────────────
[proxy]
addr = "0.0.0.0"
udp_port = 5060
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto"
max_concurrency = 1000

realms = ["10.0.0.11", "10.0.0.12", "203.0.113.100"]

addons = ["wholesale"]

generated_dir = "/static/docs-zh/wholesale/config"
routes_files = ["config/routes/*.toml"]
trunks_files = ["config/trunks/*.toml"]
queues_files = ["config/queue/*.toml"]

[proxy.dos]
enabled = true
max_cps_per_ip = 50
max_concurrent_per_ip = 200
scan_detection = true

# ── 控制台 ────────────────────────────────────────────────────
[console]
session_secret = "wholesale-cluster-secret-2026"    # ← 一致
allow_registration = false

# ── 核心集群 ──────────────────────────────────────────────────
[cluster]
peers = [                                # ← 不同:列出其他节点
  { addr = "10.0.0.11", sip_port = 5060, ami_port = 8080 },
]

# ── SipFlow(远程模式 —— 连接到 Node 1)─────────────────────
[sipflow]
[sipflow.remote]
flush_interval_secs = 5

[[sipflow.remote.nodes]]
udp = "10.0.0.11:6060"
http = "http://10.0.0.11:6060"

# ── 录音 ──────────────────────────────────────────────────────
[recording]
enabled = false

# ── CDR ────────────────────────────────────────────────────────
[callrecord]
enabled = true

config/wholesale.toml

[cluster]

[[cluster.peers]]
name = "node-1"
url = "http://10.0.0.11: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]

[sliding_window]
enabled = true
window_secs = 300
max_events_per_trunk = 10000
asr_alert_threshold = 30.0
min_calls_for_stats = 10

节点间差异汇总

字段Node 1 (10.0.0.11)Node 2 (10.0.0.12)Node N
external_ip10.0.0.1110.0.0.12本节点 IP
[cluster].peers[10.0.0.12][10.0.0.11]所有其他节点
[sipflow]local 本地模式remote → Node 1remote → Node 1
wholesale [cluster].peersnode-2 @ 10.0.0.12node-1 @ 10.0.0.11所有其他节点

所有节点必须一致的配置:

字段原因
database_url共享数据库
console.session_secretSession Cookie 验证
wholesale api_key节点间 API 认证
realmsNAT 穿透需包含所有节点 IP
rtp_start_port / rtp_end_port集群内端口必须一致
addons每个节点功能集相同
中继/路由 TOML 文件呼叫路由必须一致

PostgreSQL 变体

将各节点 config.toml 中的 database_url 替换为:

database_url = "postgres://rustpbx:WsDbP@ss2026@10.0.0.20:5432/rustpbx"

PostgreSQL 安装

# 安装
docker run -d --name postgres \
  --restart always \
  -e POSTGRES_DB=rustpbx \
  -e POSTGRES_USER=rustpbx \
  -e POSTGRES_PASSWORD=WsDbP@ss2026 \
  -v /data/postgres:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:16

# 验证
psql -h 10.0.0.20 -U rustpbx -d rustpbx -c "SELECT 1"

# 调优参数(postgresql.conf)
# shared_buffers = 4GB
# max_connections = 300
# effective_cache_size = 12GB
# work_mem = 64MB

PostgreSQL CDR 按月分区

-- 按月分区(PG 10+)
CREATE TABLE wholesale_cdrs (
    id BIGSERIAL,
    call_id TEXT,
    tenant_id BIGINT,
    trunk_id BIGINT,
    caller TEXT,
    callee TEXT,
    call_start TIMESTAMPTZ,
    call_end TIMESTAMPTZ,
    talk_duration INTEGER,
    billable_duration INTEGER,
    sell_rate DOUBLE PRECISION,
    sell_price DOUBLE PRECISION,
    buy_rate DOUBLE PRECISION,
    buy_price DOUBLE PRECISION,
    profit DOUBLE PRECISION,
    sip_code INTEGER,
    answered BOOLEAN
) PARTITION BY RANGE (call_start);

CREATE TABLE wholesale_cdrs_202605 PARTITION OF wholesale_cdrs
    FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE wholesale_cdrs_202606 PARTITION OF wholesale_cdrs
    FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
CREATE TABLE wholesale_cdrs_default PARTITION OF wholesale_cdrs DEFAULT;

SipFlow 在 Node 1 上的说明

SipFlow 在 Node 1 上以本地模式运行,使用 RustPBX 进程内嵌的 SipFlow —— 无需独立部署二进制。

Node 2(及其他节点)通过远程后端连接到 Node 1 的 SipFlow:

# Node 2 的 config.toml —— 远程 SipFlow 指向 Node 1
[sipflow]
[sipflow.remote]
flush_interval_secs = 5

[[sipflow.remote.nodes]]
udp = "10.0.0.11:6060"
http = "http://10.0.0.11:6060"

Node 1 的 SipFlow 本地后端默认监听,远程节点只需知道 Node 1 的 SipFlow UDP 和 HTTP 端口即可。

如需更高的 SipFlow 可用性,可在独立服务器上部署 sipflow 二进制,所有节点均配置为 remote 模式。详见 SipFlow 集群。


Docker Compose 参考

2 节点集群的 docker-compose.yml:

version: "3.8"

services:
  mysql:
    image: mysql:8.4-oracle
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: RootP@ss2026
      MYSQL_DATABASE: rustpbx
      MYSQL_CHARACTER_SET_SERVER: utf8mb4
      MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
    command: >
      --max-connections=500
      --innodb-buffer-pool-size=4G
      --character-set-server=utf8mb4

  node1:
    image: docker.cnb.cool/miuda.ai/rustpbx:0.4.5
    restart: always
    network_mode: host
    volumes:
      - /static/docs-zh/wholesale/node1/config.toml:/app/config.toml
      - /static/docs-zh/wholesale/node1/config:/app/config
    depends_on:
      - mysql

  node2:
    image: docker.cnb.cool/miuda.ai/rustpbx:0.4.5
    restart: always
    network_mode: host
    volumes:
      - /static/docs-zh/wholesale/node2/config.toml:/app/config.toml
      - /static/docs-zh/wholesale/node2/config:/app/config
    depends_on:
      - mysql

volumes:
  mysql_data:

目录结构:

/static/docs-zh/wholesale/
├── docker-compose.yml
├── node1/
│   ├── config.toml          ← Node 1 配置(external_ip=10.0.0.11, SipFlow local)
│   └── config/
│       ├── wholesale.toml   ← Wholesale 集群配置
│       ├── trunks/          ← 共享中继定义
│       ├── routes/          ← 共享路由定义
│       └── acl/             ← 共享 ACL 规则
└── node2/
    ├── config.toml          ← Node 2 配置(external_ip=10.0.0.12, SipFlow remote)
    └── config/
        ├── wholesale.toml
        ├── trunks/          ← 与 node1 相同
        ├── routes/
        └── acl/

快速启动命令

# 1. 创建目录结构
mkdir -p node1/config/{trunks,routes,acl} node2/config/{trunks,routes,acl}

# 2. 写入配置(使用上面完整示例)
# ... 为每个节点写入 config.toml 和 wholesale.toml ...

# 3. 同步共享配置文件
cp -r node1/config/trunks/* node2/config/trunks/
cp -r node1/config/routes/* node2/config/routes/
cp -r node1/config/acl/*    node2/config/acl/

# 4. 启动集群
docker compose up -d

# 5. 等待 Node 1 完成 migration,验证
sleep 10
curl http://10.0.0.11:8080/healthz
curl http://10.0.0.12:8080/healthz

# 6. 创建超级用户(任一节点执行一次)
docker compose exec node1 /app/rustpbx --create-superuser admin Admin@2026