# LiveKit + livekit-sip AI 外呼底座 — 部署与运维文档 > 目标机器: `39.106.106.246` (阿里云 2vCPU/3.5G, Debian 13) · 交付物: `/opt/livekit-sip/` > 项目: https://github.com/livekit/livekit · 插件: https://github.com/livekit/sip > 范围: **仅外呼** (CreateSIPParticipant 工作流), Mock SIP 运营商 + Mock LLM/ASR, 已端到端验证双向语音。 ## 1. 水平扩展结论 (先行) **结论: 共享 Redis 的无状态水平复制 —— livekit-server 与 livekit-sip 均不在本机存呼叫状态, 扩容 = 加副本, 无需协调器/迁移。** | 层 | 状态存放 | 扩容方式 | 调度机制 | |---|---|---|---| | livekit-sip 网关 | Redis (trunk/呼叫状态) + 各自持有 RTP 流 | 复制 service 块 (独立端口段) | CreateSIPParticipant 经 Redis **随机派发**到任一网关节点 | | livekit-server | Redis (rooms/room_node_map/nodes) | 加节点, 客户端连任一节点 | 房间归属节点经 Redis 自动路由, 跨节点媒体互通 | | AI Agent (生产) | 无状态 worker | `--scale`/K8s HPA | AgentDispatch 按 agent_name 派发 | **最快扩容路径**: `docker compose` 里复制一个 `sipN` service 块 (改宿主端口映射即可, 容器网内 5060/10000-10200 天然隔离); K8s 场景用 Deployment + NodePort/hostPort 固定 SIP+RTP 端口段。实测从 1 网关扩到 2 网关**零配置变更** (共用同一 `sip.yaml`), 呼叫自动分摊 (见 §7)。 瓶颈与边界: 单网关受 CPU (RTP 打包/转发) 与 RTP 端口段限制; 本文配置 200 端口/网关, 实际每呼叫占 1 个 UDP mux 端口, 单节点并发上限≈端口数。LiveKit 官方口径单节点(4C8G)数百路通话; 水平扩展后容量线性叠加, 上限在 Redis 与出口带宽。 ## 2. 架构 ``` ┌────────────────────────── docker net: lksip ──────────────────────────┐ lk CLI / 业务后端 │ ┌──────────┐ ws ┌─────────────┐ ┌─────────────┐ │ (host, :7880 API) ───────┼─►│ livekit │◄───────►│ livekit2 │ │ redis │ │ CreateSIPParticipant │ │ :7880 │ Redis │ :7883(multinode)│ │ :6379 │ │ │ trunk/呼叫状态 │ └────┬─────┘ 集群 └─────────────┘ └──────┬──────┘ │ ▼ 全部经 Redis │ │ RTC(ICE 50100-50200/udp) │ 状态存储 │ ┌─────────────┐ │ ┌────▼──────────┐ ┌──────────────┐ │ │ │ redis │◄─────────┼──┤ sip1 (网关) │ │ sip2 (网关) │──► 随机被派发 CreateSIPParticipant│ └─────────────┘ │ │ 5060/udp+tcp │ │ 5060/udp+tcp │ │ │ │ │ RTP 10000- │ │ RTP 10000- │ │ │ mock-agent (host) │ │ 10200/udp │ │ 10200/udp │ │ │ 订阅=Mock ASR │ └──────┬───────┘ └──────┬───────┘ │ │ 发布=Mock LLM/TTS ───────┼─────────┘ SIP INVITE / RTP(PCMU) │ │ │ └──────────►┌────────────────┐ │ │ │ │ mock-provider │──────┘ (SIP UAS, 440Hz 音) │ │ │ 5060/udp 40000 │ 纯 stdlib Python │ │ └────────────────┘ │ └───────────────────────────────────────────────────────────────────────┘ ``` 外呼信令流 (仅出站): ``` 业务后端 → lk sip participant create --trunk ST_xxx --call +1510... → LiveKit API → Redis 派发到某 sip 网关 → INVITE sip:+1510...@mock-provider → 100/180 → 200 OK(SDP, PCMU) → ACK → 被叫以 SIP participant 身份进房, 与房间内 agent(=AI) 双向 RTP ``` ## 3. 端口规划 | 端口 | 组件 | 说明 | |---|---|---| | 7880/tcp | livekit (node1) | HTTP/WS API, 宿主映射 7880 | | 7883/tcp→7880 | livekit2 (node2) | 同一 Redis, 双节点集群 | | 50100-50200/udp | livekit ×2 | RTC ICE (各容器独立 netns 不冲突) | | 5060/udp+tcp | sip1/sip2 | SIP 信令 (容器网内, 未映射宿主) | | 10000-10200/udp | sip1/sip2 | RTP 媒体 (同上) | | 8081/8082 | sip1/sip2 health | `/healthz` + `/` (HTTP 200) | | 9091/9092 | sip1/sip2 prometheus | `/metrics` | | 5060/udp, 40000/udp | mock-provider | Mock 运营商 SIP+RTP | 生产外网仅需放行: SIP 5060/udp+tcp (+5061/tls) 与 RTP 段; LiveKit 客户端走 7880 + RTC 段。 ## 4. 从零部署步骤 (全部实测) ```bash # ── 4.1 Docker (Debian 13, 官方源; 国内需配 mirror) ───────────────────── apt-get update && apt-get install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable" \ > /etc/apt/sources.list.d/docker.list apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # ⚠ 踩坑1(Debian 13): 安装后 docker group 缺失 → docker.socket 216/GROUP; 需 groupadd docker # ⚠ 踩坑2(最小系统): 无 iptables → bridge 驱动失败 "iptables not found"; 需 apt install iptables nftables cat > /etc/docker/daemon.json <<'EOF' { "registry-mirrors": ["https://docker.m.daocloud.io", "https://docker.1ms.run"] } EOF systemctl restart docker # ── 4.2 内核调优 (见 §8, 已应用 /etc/sysctl.d/99-livekit-sip.conf) ───── # ── 4.3 部署栈 ───────────────────────────────────────────────────────── mkdir -p /opt/livekit-sip && cd /opt/livekit-sip # = 仓库 deploy/ 目录 docker compose up -d # redis + livekit + sip1 + sip2 + mock-provider docker compose --profile multinode up -d livekit2 # (可选) LiveKit 第2节点 # ── 4.4 Mock agent 依赖 (host python) ────────────────────────────────── apt-get install -y python3-venv python3 -m venv /opt/lk-venv /opt/lk-venv/bin/pip install -i https://mirrors.aliyun.com/pypi/simple/ livekit livekit-api # ⚠ 踩坑3: SDK 1.x 拆包 — rtc 在 `livekit`, AccessToken 在 `livekit-api`; # grants 必须传 VideoGrants() 对象; frame.data 是 buffer 非 bytes。 # ── 4.5 建 trunk + 发起外呼 ──────────────────────────────────────────── ./scripts/call.sh <房间> <被叫E.164> [时长秒] # 首次自动创建 trunk 并缓存于 Redis ``` trunk 定义 (`mock/trunk.json`, 换真实运营商时改 address/auth): ```json { "trunk": { "name": "mock-provider", "address": "mock-provider", "numbers": ["+15105550123"] } } ``` ## 5. 外呼链路验证 (实测输出) `./scripts/call.sh test-room-5 +15105550123 12`: ``` == 2. Mock agent 加入房间 test-room-5 (发布TTS音, 订阅被叫音频) [agent] joined room=test-room-5 peers=[] [agent] publishing 440Hz tone (500ms on / 500ms off) == 3. 发起 SIP 外呼 -> +15105550123 [agent] subscribed: callee-30395 track=TR_AMu2wATzwBLwqP SIPCallID: SCL_LEGvhUjpz3N9 ← CreateSIPParticipant 成功, 被叫应答进房 == 4. 等待 agent 结束并输出双向统计 [agent] RX frames=1000 avg_rms=4667 RESULT room=test-room-5 rx_frames=1015 rx_avg_rms=4640 bidirectional=YES ``` mock-provider (被叫侧) 日志: ``` INCOMING_INVITE from=172.18.0.6:5060 uri="sip:+15105550123@mock-provider" ANSWERED media=172.18.0.3:40000 codec=PCMU peer=172.18.0.6:10034 ← SDP 协商 PCMU ACK confirmed, RTP bridging CALL_DONE sent_rtp=… recv_rtp=… recv_avg_amplitude=… ← 双向 RTP 计数 ``` **双向语音证明**: agent RX=被叫→ASR 方向 (1015 帧, RMS 4640 = 440Hz 检测到); provider recv = LLM/TTS→被叫方向 (RTP 计数>0)。换真实 LLM/ASR 只需替换 mock-agent 内的发布/消费循环 (livekit-agents 框架同接口)。 ## 6. 运行状态检测 `./scripts/health.sh` (实测输出): ``` == 容器状态 lk-livekit2/lk-sip1/lk-sip2/lk-livekit/lk-redis/lk-mock-provider Up == HTTP 健康 PASS livekit(200) PASS sip1(200) PASS sip2(200) # sip /healthz == API 功能 PASS lk room list PASS lk sip outbound list result: ok=5 fail=0 ``` 持续监控: `watch -n5 ./scripts/health.sh`; 指标: `curl :9091/metrics`, `curl :9092/metrics` (prometheus, 含活动呼叫/错误); 呼叫级状态: provider 容器日志 `CALL_DONE` 行; 房间级: `lk room list`。 ## 7. 水平扩展实证 (同机双网关 + 双 LiveKit 节点) **① 双 LiveKit 节点集群 (共 Redis, 自动发现)** ``` $ docker exec lk-redis redis-cli hlen nodes 2 ← node1 ND_BwShgqdiB8AQ + node2 ND_moj9RpSqq5Ap ``` 并发验证中 agent 全部连 **node2(:7883)**, CreateSIPParticipant 经 **node1(:7880)**, 同房媒体互通 → 跨节点路由成立 (RESULT 见 ③)。 **② 双 SIP 网关随机分摊 (无状态验证)** ``` $ docker logs lk-sip1 | grep -oE '"room": "burst-[0-9]"' | sort | uniq -c 15 "room": "burst-1" ← sip1 处理 burst-1 $ docker logs lk-sip2 | ... 15 "room": "burst-2" ← sip2 处理 burst-2 15 "room": "burst-3" ← 及 burst-3 $ docker logs lk-mock-provider | grep INCOMING | tail -3 from=172.18.0.5:5060 call_id=lIIip… ← 两个来源 IP = sip1/sip2 from=172.18.0.6:5060 call_id=mw4Fq… from=172.18.0.5:5060 call_id=BCVgl… ``` **③ 3 路并发外呼全部双向 (agent 连 node2)** ``` RESULT room=burst-1 rx_frames=810 rx_avg_rms=3162 bidirectional=YES RESULT room=burst-2 rx_frames=866 rx_avg_rms=3066 bidirectional=YES RESULT room=burst-3 rx_frames=778 rx_avg_rms=2228 bidirectional=YES ``` → 加副本即扩容、Redis 自动调度、跨节点房间互通, 三项均实证。资源基线 (3 并发): livekit 2×6%CPU/36M, sip 2×20%CPU/30M, redis 1%/8M。 ## 8. 系统调优 **已应用** (`/etc/sysctl.d/99-livekit-sip.conf`, `sysctl --system` 生效): ```conf net.core.rmem_max = 16777216 # UDP 收缓冲上限 (pion/WebRTC 推荐 16M; 默认 208K 高并发必丢包) net.core.wmem_max = 16777216 net.core.rmem_default = 1048576 net.core.wmem_default = 1048576 net.core.netdev_max_backlog = 4096 # 网卡积压队列 (默认 1000) net.ipv4.ip_local_port_range = 10000 65000 # 出向 SIP/RTP 源端口 (含 RTP 段) net.ipv4.udp_mem = 8388608 12582912 16777216 # 全局 UDP 页缓存 (3.5G 内存安全值) net.ipv4.udp_rmem_min/wmem_min = 16384 ``` 调优前基线: `UdpRcvbufErrors=0` (3 并发, 默认缓冲已够); 上述参数为高并发 (>100 路) 预置。验证命令: `nstat -az | grep -i udp`。 **组件级**: - sip 网关: `rtp_port: 10000-10200` (每节点 200 并发余量); `max_cpu_utilization: 0.9` 默认即可, 超 90% 自动拒绝新呼叫 (背压); `log_level: info` 生产勿用 debug。 - livekit: RTC 段 50100-50200/节点; 房间自动关闭 (empty timeout) 回收端口; `logging.json: true` 便于采集。 - redis: 纯缓存用法 (`--save ""`), trunk/房间状态可丢 (重建即恢复); 若把 Redis 当持久层需开 AOF。 - 容器: `restart: unless-stopped` 已配; 单机 3.5G 下建议给 livekit/sip 设 mem limit (如 512M) 防互挤。 - 监控: 9091/9092 /metrics 接 Prometheus; 告警项: sip 活动呼叫数骤降、livekit 5xx、UdpRcvbufErrors>0、容器重启。 ## 9. 生产化清单 (Mock → 真实运营商) 1. trunk: `address` 换运营商 SIP 域名/IP, 加 `auth_username/auth_password`, 号码用 E.164; 我方 5060/udp+tcp、RTP 段需公网放行 (阿里云安全组)。 2. NAT: 阿里云 ECS 为 1:1 NAT, sip.yaml 改 `use_external_ip: true` (STUN 自动探测公网 IP 写入 SDP), livekit.yaml 同理; 或显式 `nat_1_to_1_ip`。 3. 安全: SIP over TLS (`tls.port: 5061`) + SRTP (`media_enc`), 或 IP 白名单 ACL; LiveKit API key 轮换。 4. AI 侧: mock-agent 替换为 livekit-agents 框架 (同房间/同 track 接口), STT/LLM/TTS 三段插拔。 5. 高可用: Redis 单点 → 云 Redis 主从; 网关 ≥2 跨宿主机; LiveKit 节点 ≥2 (本文已验证集群模式)。