简介这份资源面向运维工程师与VoIP平台开发者聚焦Freeswitch高可用架构的落地实践通过Keepalived实现主备节点自动故障切换解决通信服务单点故障导致的中断问题。压缩包共4个文件约38KB包含2个Shell脚本、1份安装手册文本和1张Visio逻辑图分别承担健康检查、故障后SSH辅助连接、部署步骤说明与主备切换流程可视化等用途。资源围绕主备节点配置、虚拟IP绑定、自定义健康检查脚本、故障转移触发等关键环节展开逻辑图直观呈现从检测异常到VIP漂移、备节点接管的完整链路安装手册则给出Keepalived的安装命令与keepalived.conf核心参数示例。已有669人学习下载适合需要快速搭建Freeswitch HA环境、理解切换机制或排查切换异常的读者参考可据此缩短方案验证周期并沉淀可复用的运维脚本。1. 从一次凌晨三点的告警说起FreeSWITCH 高可用到底在保什么凌晨三点监控群里弹出一条告警主用 FreeSWITCH 节点进程还在但 SIP 端口已经不通所有呼入全部失败。值班同事手动切到备用节点花了将近十分钟业务侧投诉已经堆了一屏。这件事之后我们把 HA 方案重新梳理了一遍核心问题其实就一个——FreeSWITCH 的进程存活和业务可用是两回事keepalived 默认只检查进程进程没死但服务已经废了的情况它根本发现不了。这份资源围绕的就是这个场景用 keepalived 给 FreeSWITCH 做双机热备配合 VIP 漂移、健康检查脚本和状态同步让主节点真正不可用时备用节点能在秒级接管。它适合已经在跑 FreeSWITCH、被单点故障坑过、想自己动手搭一套可控 HA 的运维和通信开发同学。整套方案不依赖云厂商的负载均衡纯靠 keepalived 的 VRRP 加自定义检查脚本落地边界清晰、可调试出问题能自己查。2. keepalived 与 FreeSWITCH 的配合逻辑VRRP 状态机怎么驱动 VIP 漂移2.1 为什么选 keepalived 而不是自己写心跳脚本自己写心跳脚本不是不行但你要处理脑裂、优先级抢占、VIP 绑定与释放的原子性、对端失联后的仲裁这些坑一个个填下来工作量远超预期。keepalived 基于 VRRP 协议天然解决了多播心跳、优先级选举和 VIP 管理三件事配置量小出问题看日志就能定位。常见做法是主备两台机器各跑一个 keepalived 实例通过 VRRP 多播互相通告状态优先级高的持有 VIP优先级低的处于 BACKUP 状态待命。选型上要注意一点keepalived 本身不关心 FreeSWITCH它只关心自己配置的检查脚本返回什么。所以整套方案的核心不在 keepalived 配置本身而在你写给它的健康检查脚本到底能不能真实反映 FreeSWITCH 的可用状态。这是后面避坑章节要重点展开的地方。2.2 VRRP 状态切换与 FreeSWITCH 的联动时序理解时序很关键。正常状态下 MASTER 持有 VIPFreeSWITCH 监听在 VIP 上SIP 信令和 RTP 媒体都走这个地址。当检查脚本连续失败达到weight或fall阈值后keepalived 把本机优先级降低BACKUP 检测到更高优先级消失后经过master_down_interval接管 VIP同时触发notify_master脚本启动或激活本机的 FreeSWITCH 服务。这里有个容易忽略的点VIP 漂移是秒级的但 FreeSWITCH 从启动到能正常处理呼叫需要几秒到十几秒不等取决于加载的模块数量和 dialplan 复杂度。所以notify_master脚本里不能只写个systemctl start freeswitch就完事得等端口真正可用再返回否则会出现 VIP 已经在本机但服务还没起来的空窗期。#!/bin/bash # notify_master.sh - VIP 漂移到本机后执行 # 参数说明 # $1 状态组名, $2 实例名, $3 新状态 VIP192.168.1.100 FS_PORT5060 MAX_WAIT30 # 先确保 FreeSWITCH 进程在跑 systemctl start freeswitch # 轮询等待 SIP 端口真正可用避免 VIP 已漂移但服务未就绪 for i in $(seq 1 $MAX_WAIT); do if ss -lun | grep -q :$FS_PORT; then logger FreeSWITCH SIP port $FS_PORT is ready after ${i}s exit 0 fi sleep 1 done # 超时未就绪记录日志并返回失败让 keepalived 感知异常 logger FreeSWITCH failed to bind port $FS_PORT within ${MAX_WAIT}s exit 1这段脚本的逻辑是先拉起进程再用ss检查 UDP 5060 是否真的在监听。MAX_WAIT设 30 秒是保守值实际环境如果模块多可以调到 60。返回非零会让 keepalived 认为切换失败配合notify_fault可以进一步做告警。注意ss -lun里的u是 UDPFreeSWITCH 的 SIP 默认走 UDP如果你用的是 TCP 传输要改成-ltn。2.3 健康检查脚本进程存活不等于业务可用这是整套方案里最值得花时间打磨的部分。keepalived 自带的MISC_CHECK或VRRP_SCRIPT只是执行你给的脚本脚本返回 0 表示健康非 0 表示异常。很多人图省事直接写pgrep freeswitch这就是开头那次事故的根因——进程在但 SIP 栈已经卡死检查照样通过。我一般会分三层检查第一层看进程是否存在第二层看 SIP 端口是否监听第三层实际发一个OPTIONS请求看有没有响应。第三层最真实但也最重频率不能太高一般 5 到 10 秒一次比较合适。#!/bin/bash # check_fs.sh - FreeSWITCH 三层健康检查 # 返回 0 健康, 1 异常 VIP192.168.1.100 FS_PORT5060 # 第一层进程检查 if ! pgrep -x freeswitch /dev/null; then echo FAIL: freeswitch process not found exit 1 fi # 第二层端口监听检查 if ! ss -lun | grep -q :$FS_PORT; then echo FAIL: SIP port $FS_PORT not listening exit 1 fi # 第三层实际 SIP OPTIONS 探测 # 用 sipsak 或 fs_cli 发探测这里用 fs_cli 示例 RESULT$(fs_cli -x sofia status 2/dev/null | grep -c RUNNING) if [ $RESULT -lt 1 ]; then echo FAIL: sofia profile not running exit 1 fi echo OK: all checks passed exit 0三层检查的代价是脚本执行时间变长fs_cli调用本身有开销。如果节点呼叫量大建议把第三层换成更轻的sipsak发 OPTIONS或者降低检查频率到 10 秒。interval和fall两个参数要配合调interval 5 fall 3意味着连续 3 次、每次间隔 5 秒失败才切换总共 15 秒的容忍窗口能过滤掉偶发抖动。3. 双机热备落地keepalived.conf 配置与状态同步实操3.1 主备节点 keepalived.conf 的差异化配置主备两台机器的 keepalived 配置大部分相同差异在state、priority和nopreempt三个地方。主节点state MASTER、priority 100备节点state BACKUP、priority 90。nopreempt建议在备节点上开启防止主节点恢复后立刻抢回 VIP 造成二次中断——等维护窗口再手动切回更稳妥。# /etc/keepalived/keepalived.conf - 主节点配置 global_defs { router_id FS_NODE_01 # 节点标识主备要不同 enable_script_security # 要求脚本有执行权限且属主为 root } vrrp_script chk_freeswitch { script /etc/keepalived/check_fs.sh interval 5 # 每 5 秒检查一次 fall 3 # 连续 3 次失败判定为异常 rise 2 # 连续 2 次成功判定为恢复 timeout 4 # 脚本超时时间超过按失败算 weight -30 # 失败时优先级降低 30 } vrrp_instance FS_HA { state MASTER interface eth0 # 心跳网卡主备要一致 virtual_router_id 51 # 主备必须相同 priority 100 # 主节点高于备节点 advert_int 1 # VRRP 通告间隔 1 秒 authentication { auth_type PASS auth_pass FsHa2024 # 主备密码必须一致 } virtual_ipaddress { 192.168.1.100/24 dev eth0 } track_script { chk_freeswitch } notify_master /etc/keepalived/notify_master.sh notify_backup /etc/keepalived/notify_backup.sh notify_fault /etc/keepalived/notify_fault.sh }备节点配置只需改三处router_id换成FS_NODE_02state改成BACKUPpriority改成 90再加一行nopreempt。virtual_router_id、auth_pass、virtual_ipaddress必须和主节点完全一致否则 VRRP 协商不起来。weight -30配合主节点 100 的优先级失败后降到 70低于备节点的 90触发切换。3.2 FreeSWITCH 侧的配置调整与状态同步VIP 漂移解决了信令入口的问题但 FreeSWITCH 本身还有两处要改。第一处是sofiaprofile 的sip-ip和rtp-ip要绑定到 VIP 而不是物理网卡地址否则 VIP 漂过来后 FreeSWITCH 还在监听旧地址。第二处是ext-sip-ip和ext-rtp-ip如果走 NAT 要设成 VIP 对应的公网映射地址。!-- sofia profile 中的关键配置 -- param namesip-ip value192.168.1.100/ param namertp-ip value192.168.1.100/ param nameext-sip-ip valueautonat:192.168.1.100/ param nameext-rtp-ip valueautonat:192.168.1.100/状态同步是双机热备里最容易被低估的部分。注册用户信息、正在进行的通话、语音留言这些状态默认只存在主节点内存里切换后备节点是不知道的。常见做法有两种一是注册信息落共享存储比如 RedisFreeSWITCH 通过mod_xml_curl或mod_odbc_cdr把注册和 CDR 写到外部库二是接受切换时正在通话中断但注册用户重新注册后能恢复。前者体验好但架构复杂后者简单但切换瞬间会有掉线。我一般建议先做第二种把切换窗口压到最短等业务稳定了再上共享存储。3.3 切换验证怎么确认 VIP 真的漂过去了配置写完不验证等于没写。验证分三步先看 keepalived 状态再确认 VIP 位置最后实际发一个呼叫测试。# 第一步查看 keepalived 运行状态和 VRRP 实例状态 systemctl status keepalived journalctl -u keepalived -n 50 --no-pager # 第二步确认 VIP 当前在哪台机器上 ip addr show eth0 | grep 192.168.1.100 # 第三步在主节点上手动停掉 FreeSWITCH观察切换 systemctl stop freeswitch # 等待 15-20 秒后在备节点上再次执行 ip addr 检查 VIP 是否漂移 # 同时用 fs_cli 确认备节点 sofia profile 已 RUNNING fs_cli -x sofia status第三步的等待时间要覆盖interval * fall master_down_interval按上面的配置大约是 5×3318 秒。如果超过 30 秒还没切换先看journalctl里 VRRP 有没有收到对端通告再检查check_fs.sh的返回值是不是真的非零。手动bash -x check_fs.sh跑一遍能看到每一步的输出比猜快得多。4. 避坑与排查那些让切换失效的细节4.1 坑一检查脚本返回码被忽略现象停掉 FreeSWITCH 后 keepalived 毫无反应VIP 一直不漂移。原因脚本里最后一条命令是echo或grep返回码恒为 0keepalived 认为检查永远成功。或者脚本没有执行权限keepalived 静默跳过。解决脚本末尾显式exit 0或exit 1不要依赖最后一条命令的隐式返回码。chmod x /etc/keepalived/check_fs.sh属主设为 root。enable_script_security开启后keepalived 会拒绝执行属主非 root 的脚本日志里会有明确提示。4.2 坑二脑裂导致双主同时持有 VIP现象两台机器都认为自己该持有 VIP客户端注册时随机落到不同节点通话时断时续。原因心跳网卡不通或 VRRP 多播被防火墙拦截主备互相收不到通告各自认为对方挂了。解决确认interface指定的网卡是对的iptables -I INPUT -p vrrp -j ACCEPT放行 VRRP 协议协议号 112。生产环境建议心跳走独立网线或独立 VLAN不要和业务流量混在一起。有条件的话加一个仲裁节点或使用unicast_peer单播模式替代多播减少对网络环境的依赖。4.3 坑三VIP 漂移了但 FreeSWITCH 没重新绑定现象ip addr显示 VIP 已经在备节点但 SIP 注册全部失败sofia status显示 profile 还是 DOWN。原因FreeSWITCH 启动时绑定的是物理网卡地址VIP 漂过来后没有重新监听。或者notify_master脚本里systemctl start执行了但服务启动失败脚本没检查返回值就返回了 0。解决确认sip-ip和rtp-ip配的是 VIP。notify_master.sh里加端口轮询等待如 2.2 节的脚本所示。另外检查 FreeSWITCH 日志fs_cli -x console loglevel 7看绑定失败的具体原因常见的是端口被占用或权限不足。4.4 坑四切换后注册用户全部掉线且不恢复现象VIP 切换成功新呼叫能进来但原来注册的分机全部显示未注册要手动重启话机才恢复。原因SIP 注册状态存在主节点内存里备节点没有这份数据。话机的注册有效期expires还没到不会主动重新注册。解决短期方案是把expires调短比如从 3600 秒降到 120 秒切换后最多两分钟自动恢复。长期方案是注册信息落 RedisFreeSWITCH 用mod_xml_curl从共享存储读注册数据切换后备节点直接接管已有注册。这个改造工作量不小建议先评估业务对掉线时长的容忍度再决定。4.5 坑五nopreempt 配置位置写错现象主节点恢复后立刻抢回 VIP导致刚稳定下来的通话又断一次。原因nopreempt写在了vrrp_instance块外面或者写在了主节点上。这个参数只在 BACKUP 节点生效且必须在vrrp_instance块内部。解决确认nopreempt写在备节点的vrrp_instance FS_HA { }里面。主节点不要加这个参数。加了之后主节点恢复会停留在 BACKUP 状态需要手动systemctl restart keepalived或等维护窗口再切回。5. 进阶技巧用 notify 脚本做切换审计与自动恢复切换这件事最怕的是「切了但没人知道」。keepalived 的notify_master、notify_backup、notify_fault三个钩子除了用来启停服务还能做审计和自动恢复。我现在的习惯是在每个钩子里写一条结构化日志记录时间、节点、切换原因然后推到一个集中的日志系统。这样事后复盘时能清楚看到每次切换的触发点和耗时。#!/bin/bash # notify_backup.sh - 本机降为 BACKUP 时执行 # 记录切换事件并做基础清理 STATE$3 NODE$(hostname) TIMESTAMP$(date %Y-%m-%d %H:%M:%S) # 结构化日志方便后续采集分析 echo {\time\:\$TIMESTAMP\,\node\:\$NODE\,\state\:\$STATE\,\event\:\vip_released\} \ /var/log/fs_ha_switch.log # 释放本机可能残留的 SIP 会话避免切换后旧会话干扰 fs_cli -x sofia profile external killgw all 2/dev/null logger FS HA: node $NODE transitioned to $STATE exit 0这段脚本的关键在$3参数keepalived 调用 notify 脚本时会传三个参数状态组名、实例名、新状态。$3就是MASTER、BACKUP或FAULT。日志用 JSON 格式是为了方便被 Filebeat 或 Fluentd 采集不用再写正则解析。fs_cli那行做的是清理网关状态切换后旧网关连接可能还挂着清掉能让新节点从干净状态开始。验证方法上我一般会做一次「计划内切换演练」在业务低峰期手动systemctl stop keepalived观察备节点接管是否在预期时间内完成检查日志里有没有异常确认注册用户恢复情况。演练频率不用高每季度一次就够但每次都要记录切换耗时和失败点。有个小技巧是把advert_int临时调到 0.5 秒做快速切换测试验证完再改回 1 秒这样能在几分钟内跑完多轮切换而不影响生产。从那以后我每次配 keepalived 都强制走一遍「停服务 → 看日志 → 验 VIP → 测呼叫」四步不省任何一步。HA 这东西平时看不出价值真出事的时候差一秒都是事故。希望帮到你。本文还有配套的精品资源点击获取