[PonsLink] WebRTC 시그널링과 TURN 인프라 구축 회고
실시간 제품을 만들면 처음엔 영상이 붙는 순간에 환호한다. 그다음 주부터는 NAT 뒤의 사용자, 회사 방화벽, 모바일 전환, 재연결 폭풍을 상대한다. PonsLink의 시그널링·TURN·SFU 구성은 그 실패를 제품 상태로 다루기 위한 결과다.
시그널링 경로
클라이언트는 nginx의 /socket.io/ 로케이션을 통해 API 컨테이너에 WebSocket 업그레이드로 붙는다. nginx는 Upgrade/Connection 헤더를 넘기고, proxy_read_timeout을 3600초로 둔다. 버퍼링은 끈다.
API 쪽 모듈:
signaling/broker.ts— 룸/세션 이벤트 라우팅signaling/redisAdapter.ts— 인스턴스 간 pub/subsignaling/replayStore.ts— 재연결 시 누락 이벤트 재생signaling/waitingAlert.ts— 호스트 수락 전 대기 상태 전파
Redis가 빠지면 수평 확장이 아니라 상태 단절이 된다. 어댑터를 시그널링의 일부로 취급해야 한다.
TURN 자격증명
turn/turnCredentials.ts가 HMAC 기반 단기 자격증명을 발급한다. 실측 env:
TURN_SERVER_URLS=turn:turn.ponslink.com:3478?transport=udp, ...tcp, turn:...:5349?transport=tcpTURN_CREDENTIAL_TTL_SECONDS=600TURN_REALM=ponslink.com
장기 공유 비밀번호를 클라에 심지 않는다. 600초면 세션 시작에는 충분하고, 유출 시 피해 창도 짧다. coturn은 docker 컨테이너(docker-coturn-1)로 돌린다.
SFU와 P2P의 경계
기본 미디어 런타임은 MEDIA_RUNTIME_PROVIDER=pion-p2p다. 1:1·소규모는 메시 P2P가 싸고 단순하다. 다자간·대역 비대칭이 커지면 SFU(wss://ponslink.com/sfu/ws, RTC 40000–40100, announced IP)로 올린다. P2P_FORCE_TURN=false — TURN은 폴백이지 기본 경로가 아니다. TURN 중계 비용과 지연을 기본값으로 지불하고 싶지 않았다.
룸 안 도구들도 같은 파이프를 탄다
화이트보드 이벤트, 폰캐스트 플레이리스트, 파일 전송 시그널, 자막 동기화는 각자 REST + 실시간 채널을 섞어 쓴다. 공통 규칙은 하나다.
- 권한 확인은 API에서 한다 (session-tokens, session-access).
- 바이트 이동은 가능한 한 피어/오브젝트 스토리지로 보낸다.
- 상태 복구는 replay로 가능하게 남긴다.
폰캐스트는 로컬 파일을 클라우드에 올리지 않고 미리보기·공유하는 면이다, 수신 오류는 사용자가 복구할 수 있는 메시지로 끝나야 한다. "영원히 로딩"은 버그가 아니라 설계 실패다.
운영 지표
TURN 면에는 /turn/health, /turn/metrics, /turn/analytics/usage, /turn/security/events가 있다. 시그널링 성공률, ICE 상태, TURN 사용 비율을 제품 지표로 보지 않으면 장애를 "네트워크 탓"으로만 넘기게 된다.
실시간 인프라는 마법이 아니다. 짧은 자격증명, 재연결 재생, 폴백 경로, 관측 가능한 상태 네 가지를 빼먹으면 데모 다음 주에 무너진다.