05-没有运营商怎么联调:mock_sip与我们的测试环境

没有运营商怎么联调:mock_sip 与我们的测试环境

呼叫中心开发有个天然的尴尬:核心链路是"打电话",但运营商中继不是想有就有的——签约要周期、测试号段要申请、真实外呼还要花钱和合规风险。等运营商线路到位再开始联调,黄花菜都凉了。

我们的答案是自建一套测试环境(10.0.0.26),用开源工具模拟出一个"运营商",把整个呼叫链路在内网里闭环。这套环境搭好之后,开发节奏彻底摆脱了对真实线路的依赖。

测试环境的骨架

10.0.0.26 这台机器上跑着和生产同构的双栈容器:

  • /data/cc-middleware:postgres、redis、rabbitmq、freeswitch、kamailio、rtpengine——和生产媒体栈同版本(FS 1.11.1、Kamailio 6.0.7),中间件不对齐生产的坑我们吃过,后来强制对齐;
  • /data/ccstack:web、backend 两个应用容器。

镜像是 CI 构建后推华为云 SWR(swr.cn-south-1.myhuaweicloud.com/cliffdev/...),版本号每次必须递增——这条纪律背后是血的教训:早期有次忘了递增,测试机上"新版本"的行为和旧的一模一样,排查了半小时才发现 pull 下来的还是旧 tag。

mock_sip:Asterisk 假扮运营商

2026 年 9 月 22 日,测试环境加了两个容器,组成了 mock_sip:

  • Asterisk(cc-net 内 172.19.0.30):假扮运营商程控交换机,和 Kamailio 做 IP 对接;
  • SIPp(172.19.0.31):呼叫发生器,按需起,用来打压力和批量呼叫。

用法出奇地简单。平台上建一条 IP 对接中继 mock-pstn(mode=2,peerIp 指向 172.19.0.30),后端自动把它推送到 Kamailio 的路由表里——对接"假运营商"和对接"真运营商"在系统内部走的是完全同一条代码路径,这是 mock 方案最大的价值:测的就是真实逻辑,不是绕过真实逻辑。

模拟一通"客户打进来的电话"只要一条命令:

docker exec cc-mock-asterisk asterisk -x \
  "channel originate Local/07668488523@mock-in application Wait 90"

Asterisk 就会像运营商一样往我们的 DID 发 INVITE,后面的时间策略、IVR、ACD、坐席振铃,全链路照常跑。要测按键流程,换 @mock-in-ivr,它会自动发 DTMF 1。模拟"坐席外呼",SIPp 注册成被叫侧应答即可,话单、录音、计费一条龙全部真实产生。

这套东西的价值在 9 月 15 日那次生产事故里体现得最充分(详见呼入排障那篇):当时怀疑"运营商不送号"还是"我们接不住",就是靠模拟运营商 INVITE 脚本一把区分开的。能在内网复现故障,排查就从"猜"变成了"实验"。

本地起后端,中间件用测试机

日常开发的联调形态不是"把代码发到测试机",而是反过来:后端在本地 IDE 里跑,中间件全部复用 10.0.0.26。

deploy/scripts/run-backend-inttest.sh 一键搞定:拿 inttest-override.properties 模板填好数据源、Redis、MQ、ESL、Kamailio 地址,--spring.config.additional-location 注入,本地后端直连测试机中间件。配套的 deploy/scripts/inttest.py 提供造数、签入、外呼、查话单一整套动作,改完代码重启后端就能全链路验证,不用走镜像构建发布。

但这里有一条用血换来的规矩:联调期间必须先 docker stop cc-backend(测试机上的后端容器),结束再 start。

原因:两个后端同时连着同一个 FreeSWITCH 的 ESL,FS 事件被分流——你本地代码订不到完整事件流,CDR 还被测试机后端抢着消费,现象就是你新写的逻辑"时灵时不灵"。这个坑第一次踩到的时候,我们盯着日志怀疑了半小时代码,直到发现两边都有消费记录。

其他几个联调细节,都是踩出来的:

  • FS 的 event_socket 有 ACL,要加 10.0.0.0/24 网段再 fs_cli reloadacl,否则本地后端连不上 ESL;
  • Kamailio registrar max_contacts=1,防止单分机多终端残留注册互相干扰(本地软电话和测试机容器注册同一个号时尤其重要);
  • 浏览器连 wss://10.0.0.26:8443/wss 前要先访问一次 8443 接受自签证书,否则 WSS 握手静默失败,现象是"软电话注册不上但没有任何报错"——新手几乎人人中招;
  • 测试库 schema 对齐有一套固定流程:先跑 callcenter_additive_sync.sql(只加不减),再补 incremental/V*.sql。跳过第二步的症状是接口报"列不存在",还出现在生产(那次是 owed_amount 列没跟上,后话)。

mock 不是万能的

诚实地说,mock_sip 有它测不了的东西:

它模拟不了运营商的路由行为。生产上真实运营商的 404、鉴权失败、时段禁呼、SDP 里通告私网媒体地址这些"对手方的个性",mock 一概没有——这些恰恰是后来生产排障的主角。它也模拟不了真实的 NAT 和公网网络环境,所以 WebRTC 的公网穿透问题只能在生产暴露。

所以我们的分工是:功能逻辑在测试环境闭环,网络与对接行为在生产用小流量验证。测试环境保住 90% 的确定性,剩下 10% 靠生产的冒烟脚本和排障工具箱。两个环境各干各的,不互相幻想对方能兜底。

顺带记录一个差点出事的教训:v0.3.1 之前,.dockerignore 没排除 admin_web/.env.local——本地开发配置(带密钥、验证码被关)险些打进测试镜像。从此 .dockerignore 和 .gitignore 一样进了发版前检查清单。

测试环境的另一面:它是生产的小白鼠

所有要上生产的变更,先在 10.0.0.26 过一遍:中间件配置改动(Kamailio cfg、FS dialplan)先在测试机验证再手工同步生产;镜像先推 -test tag 在测试机跑通,才允许打正式 tag。

这套"生产之前先过测试机"的流程没有多自动化,但足够有效。毕竟对呼叫中心这种系统,最贵的从来不是发版时间,而是发版之后的事故时间。


本文是「云呼叫中心技术内幕」系列第 5 篇。

posted @ 2026-10-25 21:15  cliffdev  阅读(3)  评论(0)    收藏  举报