首页 / 资讯中心 / 文章详情

容器化 FreeSWITCH 上 K8s:StatefulSet 网络模型与端口规划

容器化 FreeSWITCH 上 K8s:StatefulSet 网络模型与端口规划 ★ FEATURED ARTICLE
很多人一听到“把 FreeSWITCH 搬到 Kubernetes 上”第一反应就是语音这么敏感的业务容器网络绕一圈还能通吗实际我做的项目里已经跑了几年几百路并发、对接运营商和中继网关都有。关键不在于“能不能部署”而在于你愿不愿意把媒体面、端口、NAT 这些“裸机时代靠人工维护的东西”全部变成可声明、可编排的资源。Kubernetes 本身解决的是调度、伸缩、自愈和交付一致性问题而 FreeSWITCH 是一个强状态、强端口依赖的实时通信进程。这两者结合起来网上的资料大多只给一个StatefulSet模板就完事真正决定成败的其实是网络模型选择、RTP 端口规划、配置持久化和排障手段。这篇文章是我在实际迁移过程中反复验证过的方案从镜像构建到 StatefulSet 部署、从端口计算到 SBC 对接再到我踩过的那些坑尽量一次讲透。适合已经掌握 K8s 基础、准备把 VoIP 平台容器化的朋友如果你还在困惑“FS 装 Windows 还是 Linux”那先补一下 FreeSWITCH 的基本概念再回来看。1. 为什么要把 FreeSWITCH 塞进 Kubernetes以及什么场景不适合先说结论能上但别照着部署 Nginx 的思路无脑上。FreeSWITCH 是典型的“有状态实时服务”它对外提供 SIP 注册、RTP 媒体转发、录音、IVR、会议等功能任何一个 Pod 被重建都会影响通话状态。理解清楚它的状态性后面做架构选择才不会翻车。1.1 适合容器化的典型场景我见过最值得上 K8s 的场景是公司已经有了一套完整 K8s 体系并且 FreeSWITCH 不只是单独跑语音它还要和周边系统协作REST API 下发呼叫、CDR 入库、计费系统扣费、AI 对话服务的接入。这种情况下FreeSWITCH 从“一台独立服务器”变成“语音服务集群中的一组节点”好处非常明显。交付一致镜像固化了编译参数、依赖和模块开发、测试、生产可以做到完全同构。弹性伸缩按业务高峰增加 Pod 数量低谷缩容资源利用率比固定物理机高。故障自愈Pod 异常崩溃后自动重建配合 Readiness 探测还可以把故障实例从负载均衡中摘掉。统一管理日志、监控、告警都走 K8s 生态的 Prometheus、Loki 这套不用再单独维护一套 CMDB。另外如果你做的是纯软件业务落地比如云 PBX、呼叫中心中继客户手里没有物理机只给你一个 K8s 集群那这更是唯一选项。把 FS 做成 Helm Chart 发出去客户一条命令就能拉起一套语音平台这种交付效率是传统部署完全没法比的。1.2 不适合容器化的场景不过我也要泼点冷水。以下几种情况强行上 K8s 只会让运维复杂度和故障率一起上去单实例媒体并发非常高比如单机几千路。容器网络的转发性能虽然不像以前那么差但毕竟多一层封装而且 K8s 调度器不会感知 RTP 端口占用调度结果可能把多个高并发实例压到同一台节点上。对接传统 PSTN 网关时对方只认死一个固定 IP 和固定端口段且不允许 NAT。这种情况下你需要在集群里给网关专门预留固定节点其实等于把“部署”变成“手动绑核”K8s 的优势大打折扣。团队没有 K8s 运维能力只是为了“别人都有容器化所以我也要”。FreeSWITCH 的排障本身就需要网络抓包、实时抓 SIP 信令这些操作容器化之后如果不会看日志、不会看网络排查问题会非常痛苦。还有一个判断标准如果你的规模只有一两台物理机就能覆盖业务也不需要弹性伸缩那建议保留传统部署。容器化不是目的降低交付和运维成本才是。我见过不少项目花了大量时间迁移到 K8s最后发现最累的是处理 hostNetwork 下端口冲突和节点绑定问题。2. 镜像构建基础镜像、模块裁剪和运行时配置一个能稳定运行的 FS 镜像是整套部署的地基。网上很多教程让你直接docker pull signalwire/freeswitch拿来测试没问题生产环境我建议自己构建。原因不只是版本可控更关键的是官方镜像里调试工具不齐全出问题你连tcpdump和sngrep都用不了那时候你就知道什么叫叫天天不应。2.1 自建镜像还是用官方镜像官方镜像的优势是省事signalwire/freeswitch:1.10.11这样的 tag 拉下来就能跑适合快速验证。但生产环境会有几个痛点官方镜像基于固定发行版模块组合不完全匹配你的业务比如你要加mod_av做转码、加mod_tts_commandline调外部 TTS官方镜像不一定带。镜像里没有调试工具。遇到媒体不通、只听见一端声音这种问题没有抓包工具基本靠猜。第三方依赖版本不可控安全漏洞无法及时跟进。所以我一般建议用 Debian slim 做基础镜像自己编译或从官方仓库拆包。FreeSWITCH 对运行环境要求不复杂但依赖需要完整特别是libssl、libsofia-sip-ua、libavcodec这类少了之后部分模块加载直接报错。使用 Alpine 虽然镜像小但 glibc 相关兼容性和调试工具都麻烦不建议在语音场景里省这几百 MB。2.2 Dockerfile 关键点与模块选择下面是一个非常典型的镜像构建片段结合我实际项目中的做法来说明FROM debian:bullseye-slim AS build RUN apt-get update apt-get install -y \ build-essential cmake autoconf automake libtool \ libssl-dev libsofia-sip-ua-dev libspeex-dev libldap2-dev \ libpq-dev libcurl4-openssl-dev liblua5.3-dev \ libopus-dev libsndfile1-dev libavformat-dev \ libavcodec-dev libavutil-dev libswresample-dev \ pkg-config uuid-dev zlib1g-dev libjpeg62-turbo-dev \ libsqlite3-dev libpcre2-dev libedit-dev WORKDIR /usr/src/freeswitch COPY . . RUN ./bootstrap.sh -j \ ./configure --prefix/usr/local/freeswitch \ --with-modulesmod_sofia,mod_console,mod_dptools,mod_commands,mod_dialplan_xml,mod_g711,mod_opus,mod_amr,mod_av,mod_loopback,mod_event_socket,mod_cdr_csv,mod_curl,mod_json_cdr,mod_sndfile,mod_tone_stream,mod_local_stream,mod_tts_commandline,mod_spandsp,mod_dtmf,mod_ilbc,mod_h26x,mod_vlc \ make -j $(nproc) \ make install \ make samples FROM debian:bullseye-slim RUN apt-get update apt-get install -y \ libssl1.1 libsofia-sip-ua0 libspeex1 libldap-2.4-2 \ libcurl4 liblua5.3-0 libopus0 libsndfile1 libavformat58 \ libavcodec58 libavutil56 libswresample3 libsqlite3-0 \ libpq5 uuid-runtime procps iproute2 tcpdump sngrep curl \ ca-certificates rm -rf /var/lib/apt/lists/* COPY --frombuild /usr/local/freeswitch /usr/local/freeswitch RUN useradd --system --uid 998 --home-dir /usr/local/freeswitch freeswitch WORKDIR /usr/local/freeswitch USER freeswitch EXPOSE 5060/udp 5061/udp 5080/udp 16384-32768/udp CMD [/usr/local/freeswitch/bin/freeswitch, -nonat]这里有几个关键点第一调试工具procps提供ss、iproute2、tcpdump、sngrep在生产镜像里要保留。容器里没有 systemd很多排障手段都是靠命令工具不齐会非常痛苦。第二运行时用户用固定 UID 而不是随机 UID方便后续挂载 PVC 时控制权限。第三-nonat这个启动参数不是可有可无的。FreeSWITCH 启动时会尝试检测 NAT容器环境里检测结果经常是错误的显式关掉让外部 IP 统一通过配置文件管理。2.3 运行时配置中的关键参数镜像里装好的 FreeSWITCH 默认配置基本能用但容器环境必须重点检查几个变量最重要的就是external_media_ip和external_sip_ip这两个值决定了 SIP 消息中的地址和媒体连接的目标地址。在裸机上你可能直接填固定公网 IP但在 K8s 里 Pod 的 IP 是动态的。如果和你一样采用 hostNetwork 方案最简单的是把这两个值指向$${local_ip}FreeSWITCH 会自动取到宿主机 IP。如果你的节点有多块网卡或者存在 bond就必须显式设置成业务网卡地址否则通话单通是必然的。另外注意 RTP 端口范围默认的16384-32768范围很大容器化后要按每实例容量收紧不然多个 Pod 挤在同一节点时端口胡乱占用后面很难排查。这部分我在下一节详细算给你看。3. 部署方案StatefulSet 还是 DaemonSet网络模型怎么选这一节是整个部署方案的核心。选错网络模型后面加再多的监控和调优都救不回来。FreeSWITCH 不是普通的 HTTP 应用它同时依赖 TCP/UDP有信令端口 5060还有一段巨大的 RTP 媒体端口区间。K8s 默认的 ClusterIP/NodePort 模型是为“少量固定端口 大量连接复用”设计的套到 RTP 上会产生严重的端口规划和负载均衡问题。3.1 为什么选 StatefulSet 而不是 Deployment理论上 Deployment 也能跑 FreeSWITCH但实际不推荐。FreeSWITCH 是有状态服务每个实例有独立的呼叫上下文、注册状态、录音文件、CDR 缓存。如果 Pod 重建后 IP 变化所有注册到它上面的终端会全部掉线重连这是 Deployment 随机 Hash 名称带来的副作用。StatefulSet 提供三个关键能力稳定的 Pod 名称freeswitch-0、freeswitch-1、稳定的网络标识配合 Headless Service、按序号创建的存储卷volumeClaimTemplates。这样至少能做到同一实例重建后名字不变PVC 也能重新挂载回同一份历史数据。虽然 IP 还是可能变但配合 hostNetwork 或固定节点标签我们可以让 IP 基本不跳变。下面是一个精简但可落地的 StatefulSet 核心配置apiVersion: apps/v1 kind: StatefulSet metadata: name: freeswitch spec: serviceName: freeswitch-headless replicas: 2 selector: matchLabels: app: freeswitch template: metadata: labels: app: freeswitch spec: nodeSelector: voip-node: true hostNetwork: true dnsPolicy: ClusterFirstWithHostNet containers: - name: freeswitch image: registry.example.com/freeswitch:1.10.11 args: [-nonat] ports: - containerPort: 5060 protocol: UDP hostPort: 5060 env: - name: TZ value: Asia/Shanghai resources: requests: cpu: 2 memory: 2Gi limits: cpu: 4 memory: 4Gi securityContext: runAsUser: 998 capabilities: add: [SYS_NICE, NET_BIND_SERVICE] readinessProbe: exec: command: - /usr/bin/fs_cli - -x - status initialDelaySeconds: 20 periodSeconds: 10 volumeMounts: - name: fs-data mountPath: /usr/local/freeswitch/storage - name: freeswitch-config mountPath: /usr/local/freeswitch/conf volumes: - name: freeswitch-config configMap: name: freeswitch-config volumeClaimTemplates: - metadata: name: fs-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 100Gi这里我故意把conf目录用 ConfigMap 挂载storage用 PVC。因为配置文件是“可声明”的跟着镜像走没问题而录音、语音信箱、CDR 是运行时产生的数据必须持久化。注意hostNetwork: true必须搭配dnsPolicy: ClusterFirstWithHostNet否则 Pod 里解析集群内部域名会失败。3.2 三种网络模型对比hostNetwork、NodePort、LoadBalancer我做了个项目实测把三种网络模型的优劣梳理成一张表方便你直接对照自己的场景网络模型优点缺点适合场景hostNetwork性能损耗最小、端口规划直观、便于对接传统网关需要节点打标签、端口冲突风险高、Pod 直接暴露节点网络中大规模媒体、对接 PSTN/运营商NodePort LoadBalancer不占用节点端口、更“K8s化”、调度更自由RTP 端口范围与 NodePort 默认范围冲突、LB 可能成媒体转发瓶颈小并发、纯 SIP 注册数量不高CNI overlay 网络Flannel/Calico安全隔离、IP 自由分配性能损耗、NAT 处理复杂、排障困难非常不推荐在生产中直接跑 FS先说明为什么 NodePort 不方便。K8s 默认service-node-port-range是30000-32767只有 2768 个端口可用。而 FreeSWITCH 的 RTP 端口范围随并发量增长很快假设你有 10 个 Pod、每个需要 1024 个媒体端口Port 根本无法满足。而且 NodePort 在 UDP 媒体长连接场景下很多云厂商的 LB 四层转发会做会话保持或超时回收媒体流一旦被切断通话就会断断续续。所以我个人最推荐的是hostNetwork 固定节点标签。让 FreeSWITCH 直接监听节点 IP 和节点端口K8s 负责进程编排和健康检查网络层面跟裸机几乎一样。配合daemonSet或nodeSelector你可以把 2-3 个 FS 实例分别钉在不同节点上形成一个小型语音资源池。3.3 媒体端口的规划与计算这一节请你认真看因为端口算错了业务一上来就会爆。FreeSWITCH 中一路通话通常会涉及两个 channel会话腿每个 channel 占一个本地 RTP 端口。也就是说100 路并发通话约需要 200 个 RTP 端口。还要预留缓冲避免瞬时峰值和端口释放延迟导致分配失败。我一般按“1 路并发 ≈ 2 个 RTP 端口”再加 30% 余量来规划。举个例子你给每个 Pod 定的目标是 300 路并发那么 RTP 端口数至少是300 × 2 × 1.3 780向上取整到 1024 个端口。在vars.xml里设置X-PRE-PROCESS cmdset datartp_start_port20000/ X-PRE-PROCESS cmdset datartp_end_port21023/这样每个 Pod 有 1024 个端口可用。三个 Pod 就规划成Pod 020000-21023Pod 121024-22047Pod 222048-23071为什么推荐按 64 或 1024 对齐因为防火墙和云安全组规则一般按 CIDR 段放行比如20000/1024这种写法端口范围越规整越容易把放行规则写清楚。如果你让所有 Pod 都用同一段范围并且调度到同一节点那一定会冲突。解决方式是给每个 Pod 注入独立的环境变量再在 FreeSWITCH 启动时基于环境变量渲染vars.xml或者干脆每个 StatefulSet 单独配一个 ConfigMap。实战中我用的是 initContainer 渲染模板核心逻辑就是读取POD_NAME后缀数字自动计算起始端口然后写入共享空目录。这样只维护一个模板扩展 Pod 数量时不需要手工改配置。4. 配置持久化与配置管理FreeSWITCH 有大量的配置文件vars.xml、sip_profiles、dialplan、acl.conf.xml、directory等。在容器里把它们全部打进镜像没问题但一旦要调并发、改 SIP profile 参数就得重新构建镜像太慢了。更合理的拆分是静态配置走 ConfigMap动态数据走 PVC。4.1 数据目录拆分录音、CDR、语音信箱分开挂FreeSWITCH 默认把数据放在/usr/local/freeswitch/storage和/usr/local/freeswitch/recordings等目录。很多教程直接挂载整个/usr/local/freeswitch这会把配置文件也带到 PVC 里一旦 ConfigMap 和 PVC 里的配置冲突启动行为会变得很诡异。我习惯只挂这两个东西/usr/local/freeswitch/conf只读挂载 ConfigMap包含所有 XML 配置。/usr/local/freeswitch/storage读写挂载 PVC保存录音、语音信箱、CDR 缓存、mod_voicemail 数据。日志不建议写到 PVC而是输出到 stdout。FreeSWITCH 可以通过logfile配置把日志打到/dev/stdout这样kubectl logs直接就能看。如果日志和录音混在一个 PVC排障时既要翻日志又要翻录音很混乱。录音和 CDR 如果量很大也可以再单独拆两个 PVC方便不同生命周期管理CDR 可能想保留 180 天录音可能想归档到对象存储。4.2 ConfigMap 挂载配置文件的具体做法把conf整个目录变成一个 ConfigMap里面至少包含以下文件conf/ ├── vars.xml ├── dialplan/ │ └── default.xml ├── sip_profiles/ │ ├── external.xml │ └── internal.xml ├── autoload_configs/ │ ├── modules.conf.xml │ ├── event_socket.conf.xml │ └── cdr_csv.conf.xml └── directory/ └── default.xml但要注意K8s 的 ConfigMap 挂载默认是“文件替换”也就是说你挂载/usr/local/freeswitch/conf那整个目录下面的原有内容都会被覆盖。所以 ConfigMap 里必须提供完整目录结构缺失任何一个文件都会导致服务异常。如果你只是调整一两个参数也可以只挂载单个文件到已存在目录比如volumeMounts: - name: fs-vars mountPath: /usr/local/freeswitch/conf/vars.xml subPath: vars.xmlsubPath挂载有个小坑后续 ConfigMap 更新不会自动同步到容器内且必须重启 Pod 才生效。在语音场景里我不建议使用 ConfigMap 热更新因为配置变更改拨号计划、改 SIP profile本就应该是一次计划内的割接滚动重启是可控且安全的方式。你可以用一个简单的脚本kubectl rollout restart statefulset/freeswitch完成更新。4.3 备份、恢复与容量规划PVC 建议自动开启快照或者用 Velero 做定时备份。FreeSWITCH 的数据有一个特点录音和 CDR 是追加写很少修改但如果在通话进行中直接kubectl delete pvc数据肯定没救。另外生产环境千万别用ReadWriteOnce之外的模式多节点共享同一块存储因为多个 FS 同时写录音文件时文件系统锁会让数据库类型的存储直接崩。容量方面按你的业务算1 分钟录音大约 0.5MBG.711 格式一路通话 10 分钟就是 5MB1000 路/天就是 5GB/天。对象存储归档建议在 K8s 外处理比如挂载一个 sidecar 容器定时把storage里的录音推送到 S3PVC 只保留最近 N 天。5. 高可用与对外对接注册、NAT 处理和 SBC单体 FreeSWITCH 好理解端口都监听在一台机器上变成多 Pod 后外部怎么找到你、话机注册到哪个实例、中继网关的固定 IP 怎么处理这些是团队摸索很久才跑通的。最简单的路线是对外统一走 SBCFreeSWITCH 躲在后面如果你没有 SBC直接让 FS 暴露在 K8s 节点上那就必须把节点打标签固定好避免漂移。5.1 注册场景话机 / 软终端如何找到 FreeSWITCH如果是企业内网话机注册到 FreeSWITCH通常走 internal SIP profile端口 5060。在有多个 Pod 的情况下最简单的做法是在每个节点上用 hostNetwork 监听 5060再通过 DNS 轮询或负载均衡把话机分流。但很多话机并不支持多地址 failover当它注册到freeswitch-0freeswitch-0挂掉后话机不会自动切到freeswitch-1要等到注册过期重试。所以如果你追求高可用而不是只有“多个实例”建议在 FS 前面加一个 SBC 或者 Kamailio。话机只注册到 SBC 的固定虚拟 IPSBC 再在后端多个 FS 之间分发注册和呼叫。FS 和 SBC 之间的信令用externalprofile媒体也可以由 SBC 中转或直接走 FS。这就是经典的“注册分离”模型对外是一个 IP对内是 N 个 FS。K8s 里的 FS 可以只分配 ClusterIP完全不用暴露公网安全面也小很多。5.2 与 SBC / 传统语音网关对接的固定 IP 问题传统语音网关VoIP GW、对接运营商的中继通常只允许配置一个对端 IP 和端口而且会做 IP 白名单。这种场景下纯粹的 Pod IP 漂移是致命的。我的建议是把 FS 的媒体节点设为固定节点用nodeSelector锁住再用hostNetwork让它直接使用节点 IP。具体做法是给节点打标签kubectl label node node-voip-01 voip-nodetrue然后 StatefulSet 里加nodeSelector并规划好哪个 Pod 部署在哪台节点。这样即使 Pod 重建它也会被调度回同一个节点外部 IP 不会变。如果你有多个 FS 实例就用多个nodeSelector或者在同一个节点上分别使用不同的 SIP 端口比如 5060、5062。后者会导致对接配置复杂所以尽量一个节点只跑一个 FS 实例除非你仔细算过 RTP 端口不冲突。另外如果中继要求 FS 在注册或 OPTIONS 探测上保持活跃记得在 SBC 上关闭 NAT 检测或者对 FS 的源地址做白名单放行。K8s 节点上的 SNAT 规则有时会让源 IP 变来变去你需要在external.xml的 SIP profile 里显式指定 SIP 源 IP 为节点 IP避免被对端拒绝。5.3 readinessProbe、PodDisruptionBudget 和优雅退出没有优雅退出一个 FS Pod 迁移就是一次通话事故。K8s 默认在 Pod 终止时给容器发 SIGTERM但 FreeSWITCH 如果不处理连接中的通话会直接断掉。建议在容器里加一段启动脚本接收 SIGTERM 后先执行fs_cli -x shutdown让 FS 主动释放所有呼叫再退出进程。更简单的方式是使用preStop钩子lifecycle: preStop: exec: command: - /bin/sh - -c - /usr/bin/fs_cli -x shutdown ; sleep 5还需要配置readinessProbe我给出的是fs_cli -x status这个命令返回非 0 时说明 FS 还没准备好。再配置 PodDisruptionBudgetapiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: freeswitch-pdb spec: minAvailable: 1 selector: matchLabels: app: freeswitchminAvailable: 1表示同一时间最多只有一个 Pod 被主动驱逐避免节点维护时两个 FS 同时消失。6. 常见问题与排查技巧实录最后这部分是我实际运维中遇到的高频问题也是网上文档基本不写的内容。遇到问题不要慌先按“信令通不通、媒体通不通、端口通不通”三步走基本上 80% 的问题都能定位。6.1 单通、SIP 403/401、RTP 端口不通的定位方法单通是最典型的 FS 容器化故障现象是 A 能听到 B 的说话声B 听不到 A。90% 的原因是external_media_ip设置错误。确认方法fs_cli -x global getvar external_media_ip fs_cli -x global getvar external_sip_ip如果值和实际节点 IP 不一致检查vars.xml和 SIP profile 里的param nameext-rtp-ip value$${external_media_ip}/。在 hostNetwork 下最稳妥的是用节点 IP而不是 Pod IP。SIP 401/407 一般是注册鉴权或 ACL 问题先看日志fs_cli -x sofia status fs_cli -x sofia profile external siptrace on开启 siptrace 后再让外部网关发一个 OPTIONS 或呼叫你就能在日志里看到完整的 SIP 消息流。如果看到403 Forbidden且携带invalid password查directory里的用户配置如果看到407 Proxy Authentication Required查 SIP profile 里的inbound-proxy配置是否循环。RTP 不通最隐蔽的地方是云安全组只放行了 5060 的 UDP没有放行 RTP 端口段。注意我说的是“端口段”不是单个端口。在阿里云/腾讯云安全组规则里UDP 放行20000/1024例如从 20000 到 21023 全部放行这样就不会出现偶发性断流。6.2 镜像和资源限制导致的问题FreeSWITCH 对 CPU 和内存的消耗不是线性的。内存方面每个 channel 会话都会分配内存缓存如果并发突然涨起来内存会快速上涨。我把 limits 设得过高时遇到过 Pod 一直不 OOM 但节点整体内存被打满把 limits 设得过低时又遇到 OOMKilled 导致通话掉线。经验值单 Pod 建议 requests 2C2Glimits 4C4G然后根据实际业务逐步调整不要一上来就给 16G。CPU limit 超过后可被节流throttle媒体线程如果被节流通话会出现杂音和明显的延迟。所以性能敏感场景可以给 FS 容器加SYS_NICEcapability并在启动参数里设置nice或rtprio帮助媒体线程优先调度。另外K8s 节点上的 sysctl 参数也要检查特别是 UDP 接收缓冲sysctl -w net.core.rmem_max8388608 sysctl -w net.core.rmem_default8388608 sysctl -w net.core.netdev_max_backlog5000这些参数在容器内是改不了的需要在节点上配置。如果不设高并发时 UDP 包会直接丢在内核缓冲区现象又是“偶发单通/断音”非常难查。6.3 一张表记住常见故障排查故障现象可能原因优先排查方法与命令注册不了SIP 401密码错误或 ACL 拒绝fs_cli -x acl list查directory配置呼叫 408信令超时NAT 地址错误或防火墙丢包开启 siptrace抓包确认 request 到没到接通后单通external_media_ip不对或 RTP 端口被挡fs_cli -x global getvar external_media_ip偶发爆音、断音UDP 缓冲区不足或 CPU throttle节点检查sysctl net.core.rmem_maxPod 反复重启启动参数错误或配置缺失kubectl logs pod看启动日志RTP 端口分配失败端口范围耗尽或范围冲突日志搜ERROR的rtp检查范围设置最后分享一个我用得非常顺手的排障组合在 FS Pod 旁边临时跑一个 sidecar 容器里面装tcpdump和sngrep把所有 UDP 5060 和 RTP 端口段的流量抓下来。sngrep的界面可以直接看到 SIP 消息的 From/To、SDP 的 IP 和端口单通还是不通一目了然。很多问题在裸机上靠直觉能猜出来在 K8s 里还是靠证据说话更靠谱。关于这套方案我实际做下来最大的体会是FreeSWITCH 容器化真正难的不是“把它跑起来”而是把端口、NAT、调度、存储、优雅退出这些边界条件都想清楚。如果你只是小规模测试不用套太多高可用方案一个 hostNetwork 的 StatefulSet 就够跑但如果你要接到运营商中继或者面向大量话机提供注册服务那就老老实实用固定节点 SBC 做接入把复杂留给自己能控制的层不要把不可控的网络因素暴露给外部。实际上还有一个很实用的小细节把所有 RTP 范围规划成 1024 的整数倍云上防火墙规则会好写很多将来扩容也不用返工。
阅读完成 · 觉得有帮助?
咨询建站