WebRTC 全景实战 (14):TURN 集群部署与多区域扩展
"TURN 是 WebRTC 基础设施中带宽成本最高的组件——relay 流量约占 10–30%。" — 生产运维共识
Ch5 ICE/STUN/TURN 讲了 TURN 原理。本章落地生产级 coturn 集群——从单机 Docker 到多区域 GeoDNS 部署,覆盖凭证安全、带宽成本建模与容量规划。
Serge Lachapelle 在 Curious 历史访谈 中回忆:Marratech 时代企业网内 ICE 几乎总是成功,但扩展到公网后 TURN relay 成为必需品——Today Meet/LiveKit 全球部署中,TURN 集群的运维复杂度不亚于 SFU 本身。
配套 Lab:examples/webrtc-lab/docker/coturn/docker-compose.yml
本篇术语表
| 术语 | 英文 | 解释 |
|---|---|---|
| TURN | Traversal Using Relays around NAT | NAT 穿透失败时的中继服务器 |
| STUN | Session Traversal Utilities for NAT | 获取公网 IP 的轻量服务 |
| coturn | — | 最流行的开源 TURN/STUN 服务器 |
| Allocate | 分配 | TURN 客户端请求 relay 地址的操作 |
| Relay Address | 中继地址 | TURN 分配给客户端的流量转发地址 |
| TURN REST API | — | RFC 8656 定义的短期凭证机制 |
| lt-cred-mech | Long-term Credential | coturn 长期凭证模式(开发用) |
| use-auth-secret | — | coturn 短期 HMAC 凭证模式(生产用) |
| GeoDNS | 地理 DNS | 根据客户端位置解析到最近 TURN 节点 |
| Anycast | 任播 | 同一 IP 多区域广播(高级部署) |
| Relay Ratio | 中继占比 | 走 TURN relay 的连接占总连接的比例 |
| ICE-lite | — | 简化 ICE 实现,服务端不主动连通性检查 |
| ChannelBind | 通道绑定 | TURN 优化模式,减少协议开销 |
| Permission | 权限 | TURN 允许 relay 的目标 IP 白名单 |
一、生产拓扑
| 组件 | 部署策略 | 说明 |
|---|---|---|
| SFU | 区域亲和 + Redis Mesh | 同 Room 参与者尽量同区域 |
| TURN | 每区域独立集群 | 客户端连接最近 TURN |
| STUN | 可与 TURN 共用 coturn | 轻量,可全局单点 |
| DNS | GeoDNS 或 Anycast | 客户端无感知选最近节点 |
Marratech 在瑞典企业网时代几乎不需要 TURN;Google 收购后在 Meet 全球部署中,TURN 成为带宽成本最高的基础设施组件之一——relay 流量是 SFU 转发的 2 倍(入站 + 出站各计一次)。
二、coturn 生产配置要点
2.1 开发环境(Lab 用)
examples/webrtc-lab/docker/coturn/docker-compose.yml:
services:
coturn:
image: coturn/coturn:latest
ports:
- "3478:3478/udp"
- "3478:3478/tcp"
- "49152-49200:49152-49200/udp"
command: >
-n --log-file=stdout
--lt-cred-mech
--user=test:test123
--realm=webrtc.lab
--min-port=49152
--max-port=49200
2.2 生产环境配置
# /etc/turnserver.conf 核心配置
listening-port=3478
tls-listening-port=443
listening-ip=0.0.0.0
relay-ip=<SERVER_PUBLIC_IP>
external-ip=<SERVER_PUBLIC_IP>
use-auth-secret
static-auth-secret=<YOUR_HMAC_SECRET>
realm=turn.example.com
no-multicast-peers
no-cli
stale-nonce=600
max-bps=3000000
user-quota=10
total-quota=5000
min-port=49152
max-port=65535
log-file=/var/log/turnserver.log
verbose
prometheus
| 配置项 | 开发 | 生产 | 说明 |
|---|---|---|---|
| 认证 | --lt-cred-mech --user=test:test123 | use-auth-secret | 生产禁止明文密码 |
| TLS | 可选 | 必须 443 | 穿透企业防火墙 |
--max-bps | 不限 | 3M–5M | 防止单用户占满带宽 |
| 端口范围 | 49152-49200 | 49152-65535 | 每个 relay 会话占用 1 端口 |
--no-multicast-peers | — | 必须 | WebRTC 不需要多播 |
TURN relay 为每个会话分配一个 UDP 端口。安全组/防火墙必须开放此范围,否则 Allocate 成功但 media relay 失败——这是最常见的生产部署错误。
2.3 NAT 后的 coturn 部署
云服务器部署时务必设置 --external-ip=<公网IP>/<内网IP>,否则 relay 地址可能是内网 IP,客户端无法连接。
三、TURN REST API 短期凭证
RFC 8656 定义 REST API 短期凭证机制:
3.1 凭证生成(Node.js)
// examples/webrtc-lab/signaling/ 扩展
import crypto from "crypto";
function generateTurnCredentials(secret, identity, ttlSeconds = 86400) {
const timestamp = Math.floor(Date.now() / 1000) + ttlSeconds;
const username = `${timestamp}:${identity}`;
const hmac = crypto.createHmac("sha1", secret);
hmac.update(username);
const credential = hmac.digest("base64");
return {
username,
credential,
ttl: ttlSeconds,
iceServers: [
{
urls: [
"turn:turn.example.com:443?transport=tcp",
"turn:turn.example.com:443?transport=udp",
"turns:turn.example.com:443?transport=tcp",
],
username,
credential,
},
{
urls: "stun:stun.example.com:3478",
},
],
};
}
app.post("/rooms/join", (req, res) => {
const { roomId, identity } = req.body;
const turnCreds = generateTurnCredentials(process.env.TURN_SECRET, identity);
res.json({
roomId,
identity,
iceServers: turnCreds.iceServers,
});
});
3.2 凭证时效与刷新
| 参数 | 推荐值 | 说明 |
|---|---|---|
| TTL | 24h(86400s) | 长会议足够,短于攻击窗口 |
| 刷新阈值 | 剩余 10min | 客户端主动刷新 |
| Secret 轮换 | 每 90 天 | 双 Secret 过渡期 |
四、带宽成本模型
4.1 TURN 带宽计算公式
单用户 TURN 消耗 = 媒体码率 × 2(入站 relay + 出站 relay)
1000 用户 TURN 总消耗 = 1000 × relay_ratio × 码率 × 2
示例:
1000 用户,25% relay,720p 1.5Mbps
= 1000 × 0.25 × 1.5M × 2
= 750 Mbps TURN 带宽
| 场景 | 码率 | relay 占比 | 1000 用户 TURN 带宽 |
|---|---|---|---|
| 音频 only | 50 kbps | 20% | 20 Mbps |
| 360p 视频 | 500 kbps | 25% | 250 Mbps |
| 720p 视频 | 1.5 Mbps | 25% | 750 Mbps |
| 1080p 视频 | 3 Mbps | 30% | 1.8 Gbps |
4.2 单节点容量规划
| 实例规格 | 网卡带宽 | 720p relay 会话数(估算) |
|---|---|---|
| 4 vCPU / 8GB | 1 Gbps | ~300 并发 relay |
| 8 vCPU / 16GB | 5 Gbps | ~1500 并发 relay |
| 16 vCPU / 32GB | 10 Gbps | ~3000 并发 relay |
估算假设:每路 relay 720p ≈ 1.5Mbps × 2 = 3Mbps TURN 消耗。
4.3 成本优化策略
五、多区域部署策略
5.1 GeoDNS
| 策略 | 复杂度 | 效果 | 适用 |
|---|---|---|---|
| GeoDNS | 低 | 按地理位置解析 | 大多数场景 |
| 客户端测速 | 中 | 并行 STUN 多个区域,选 RTT 最低 | 对延迟敏感 |
| Anycast | 高 | 同一 IP 多区域广播 | 大规模全球部署 |
| SFU 内置 TURN | 低 | LiveKit 自动管理 | LiveKit Cloud/自托管 |
5.2 客户端并行测速选 TURN
async function selectBestTurn(turnServers) {
const results = await Promise.all(
turnServers.map(async (server) => {
const start = performance.now();
try {
const pc = new RTCPeerConnection({ iceServers: [server] });
pc.createDataChannel("probe");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
await new Promise((resolve) => {
pc.onicecandidate = (e) => {
if (e.candidate?.type === "relay") resolve();
if (!e.candidate) resolve();
};
setTimeout(resolve, 3000);
});
pc.close();
return { server, rtt: performance.now() - start };
} catch {
return { server, rtt: Infinity };
}
})
);
return results.sort((a, b) => a.rtt - b.rtt)[0].server;
}
5.3 LiveKit 内置 TURN
LiveKit Cloud 自动管理 TURN 凭证和区域选择。自托管配置:
# livekit.yaml
turn:
enabled: true
domain: turn.example.com
tls_port: 443
udp_port: 443
external_tls: true
详见 LiveKit TURN 文档 与 LiveKit 介绍。
六、高可用与扩容
| 指标 | 告警阈值 | 扩容动作 |
|---|---|---|
| 活跃会话数 | > 单节点 80% 容量 | 加 coturn 实例 |
| 出站带宽 | > 节点网卡 70% | 加节点或升级带宽 |
| Allocate 失败率 | > 1% | 查端口范围/Secret |
| 凭证验证失败 | > 0.1% | 查 Secret 同步/时钟偏移 |
6.1 coturn Prometheus 指标
# turnserver.conf
prometheus
# 默认 :9641/metrics
# 关键指标
# turn_total_allocations
# turn_total_traffic_bytes
# turn_total_traffic_packets
6.2 coturn 集群限制
coturn 实例之间不共享 relay 会话状态。LB 应使用 session affinity(源 IP 粘性),或让每个客户端独立 Allocate。Secret 必须在所有实例间同步。
七、安全加固
| 措施 | 配置 | 说明 |
|---|---|---|
| 短期凭证 | use-auth-secret | 禁止长期密码 |
| 带宽配额 | --max-bps=3000000 | 单用户上限 |
| 会话配额 | --user-quota=10 | 防滥用 |
| TLS 必须 | --tls-listening-port=443 | 加密 + 防火墙穿透 |
| 禁用 CLI | --no-cli | 防远程管理攻击 |
| 日志审计 | --verbose + 集中采集 | 追踪异常 Allocate |
八、防火墙与企业网络
| 端口 | 协议 | 用途 | 必须 |
|---|---|---|---|
| 3478 | UDP/TCP | STUN/TURN | 是 |
| 443 | TCP/TLS | TURNS 穿透防火墙 | 生产必须 |
| 49152-65535 | UDP | TURN relay 媒体 | 是(UDP relay) |
九、常见陷阱
| # | 陷阱 | 现象 | 修复 |
|---|---|---|---|
| 1 | 未开放 UDP 49152-65535 | Allocate 成功但无 media | 安全组开放端口范围 |
| 2 | external-ip 未配置 | relay 地址是内网 IP | 设置 --external-ip |
| 3 | 长期凭证泄露 | 带宽被滥用 | 切换 REST API 短期凭证 |
| 4 | 单 TURN 节点 | 跨区 RTT 高 | 多区域 GeoDNS |
| 5 | 忽略 relay 占比 | 成本失控 | Prometheus 监控 + 告警 |
| 6 | STUN/TURN 混用同一域名 | DNS 解析混乱 | STUN 和 TURN 分开域名 |
| 7 | 时钟偏移 | 凭证验证失败 | NTP 同步所有节点 |
| 8 | LB 无 session affinity | relay 会话中断 | 源 IP 粘性或客户端重 Allocate |
| 9 | 仅 TCP TURN 无 UDP | 媒体延迟高 | 优先 UDP relay + TCP fallback |
| 10 | Secret 不一致 | 部分节点验证失败 | 配置管理同步 Secret |
十、实战 Lab
Lab 1:Docker coturn 启动与验证
cd examples/webrtc-lab/docker/coturn
docker compose up -d
turnutils_stunclient localhost
turnutils_uclient -T -u test -w test123 localhost
Lab 2:浏览器 TURN 验证
const pc = new RTCPeerConnection({
iceServers: [{
urls: "turn:localhost:3478",
username: "test",
credential: "test123",
}],
});
pc.createDataChannel("test");
pc.createOffer().then((o) => pc.setLocalDescription(o));
pc.onicecandidate = (e) => {
if (e.candidate) console.log(e.candidate.type, e.candidate.address);
};
Lab 3:REST API 凭证
- 部署带
use-auth-secret的 coturn - 用
generateTurnCredentials()生成凭证 - 浏览器连接,确认 relay 成功
Lab 4:带宽限制验证
配置 --max-bps=500000,720p 通话观察画质限制在 ~500kbps。
Lab 5:relay 占比统计
const stats = await pc.getStats();
stats.forEach((r) => {
if (r.type === "candidate-pair" && r.state === "succeeded" && r.nominated) {
console.log("Local:", r.localCandidateType, "Remote:", r.remoteCandidateType);
}
});
Lab 6:TLS 443 穿透
配置 tls-listening-port=443 + 证书,验证 turns: 连接成功。
Lab 7:跨区 TURN 延迟对比
- 部署两个区域 coturn(或模拟 DNS 解析)
- 用
selectBestTurn()测 RTT - 对比选优前后 ICE 连接时间
十一、本章小结
| 概念 | 要点 |
|---|---|
| coturn | 最流行的 TURN 服务器,生产必配 TLS 443 |
| REST API | 短期 HMAC 凭证,禁止长期密码 |
| 带宽成本 | relay = 码率 × 2 × relay 占比 |
| 多区域 | GeoDNS + 区域 TURN 集群 |
| 监控 | relay 占比 + 带宽 + Allocate 失败率 |
| 安全 | max-bps + user-quota + use-auth-secret |
| 历史 | Marratech 企业网 → 公网 TURN 必需品 |
下一篇(Ch15):Capstone 视频会议系统
系列导航
章节 主题 状态 0 架构全景与协议栈地图 ✅ 已发布 1 浏览器媒体 API 与设备管理 ✅ 已发布 2 第一个 P2P 视频通话 ✅ 已发布 3 信令服务器设计与会话状态机 ✅ 已发布 4 SDP 解剖与媒体协商 ✅ 已发布 5 ICE、STUN、TURN 与 NAT 穿透 ✅ 已发布 6 Data Channel 与 SCTP over DTLS ✅ 已发布 7 DTLS 握手与 SRTP 加密体系 ✅ 已发布 8 RTP/RTCP 媒体传输与 QoS ✅ 已发布 9 音视频编解码与 Simulcast 入门 ✅ 已发布 10 带宽估计与拥塞控制 GCC ✅ 已发布 11 Simulcast、SVC 与选择性订阅 ✅ 已发布 12 SFU/MCU/Mesh 架构与 Pion 实战 ✅ 已发布 13 调试工具链与可观测性 ✅ 已发布 14 TURN 集群部署与多区域扩展 ✅ 已发布 15 Capstone 生产级视频会议系统 ✅ 已发布