LiveKit + Egress を Windows の Docker Desktop でローカル開発したときの詰まり所
ブラウザから複数人ライブ配信(LiveKit + Egress + メディアサーバ)を Windows の Docker Desktop でローカルに立てたら何ヶ所か詰まった。症状→原因→対処でまとめたメモ。一番効いたのは --node-ip。
ブラウザから複数人がライブ配信するやつを作るのに、LiveKit(WebRTC の SFU)と LiveKit Egress(部屋を1枚に合成して配信するやつ)をローカルの Docker で動かそうとしたら、Windows の Docker Desktop 特有のところで何ヶ所か詰まった。同じ構成でハマる人向けに、症状 → 原因 → 対処でメモしておく。
結論(先に)
- 一番効いたのは LiveKit に
--node-ip=ホストのLAN IPを広告させること。これが無いとホストのブラウザから WebRTC がつながらない。 - LiveKit と Egress は 同じ Redis を共有しないとジョブが渡らない。
- 設定ファイルを直したら
docker compose restart(up -dだとマウント中身の変更を拾わない)。 - Egress の RTMP 出力先は
rtmp://host/{app}/{stream_key}(2セグメント) 必須。 - Egress の合成は 部屋にトラックが無いと始まらない → 検証は Ingress + ffmpeg で。
作ろうとした構成
ブラウザ(配信者/ゲスト) ──WebRTC──▶ LiveKit(SFU) └─ Egress が部屋を1枚に合成 └─RTMP──▶ メディアサーバ(MediaMTX) └─HLS──▶ 視聴者(大量)ステージに乗る少人数だけ WebRTC で LiveKit に入り、合成した1本を HLS にして視聴者は普通の HLS プレイヤーで見る(視聴者は SFU に入れない)。これを全部 Docker(LiveKit / Egress / メディアサーバ / Redis)でローカルに立てたかった。
詰まり① ホストのブラウザから WebRTC がつながらない(最重要)
症状:ブラウザから部屋に入ろうとすると could not establish pc connection。signaling(WebSocket)は通るのに、映像回線(WebRTC の PeerConnection)が張れない。
原因:LiveKit はデフォルトで、自分の到達先(ICE 候補)として Docker コンテナ内部の IP(192.168.x.x みたいなの) を広告する。コンテナ同士なら届くが、ホストで動いているブラウザからはこの内部 IP に到達できない。
対処:LiveKit に ホストの LAN IP を広告させる。起動オプションに --node-ip を付ける。
# docker-compose.yml(LiveKit サービス)command: --config /etc/livekit.yaml --node-ip ${LIVEKIT_NODE_IP}# .env(このマシンの LAN IP。DHCP なら変わるので注意)# LIVEKIT_NODE_IP=192.168.x.xこうすると ホストのブラウザも、他のコンテナ(Egress 等)も同じアドレスで LiveKit に届く。
※本番(Linux サーバ)ではサーバの公開 IP、もしくは use_external_ip: true(STUN で自動検出)を使う。
一般化した教訓:ローカル Docker で WebRTC 系が「ホストのブラウザからつながらない」ときは、まず「広告している IP がホストから届くか」を疑う。signaling が通って映像だけダメなのは、ほぼ ICE 候補(IP)の問題。
詰まり② Egress がジョブを受け取らずハングする
症状:Egress を開始する API を叩くと応答が返らずタイムアウト。Egress のログは起動直後で止まったまま。
原因:LiveKit と Egress は Redis 経由でジョブを受け渡す。LiveKit 側に Redis 設定が無いと、Egress にジョブを配れずに待ち続ける(egress not connected (redis required))。
対処:LiveKit と Egress の両方に同じ Redis を向ける。
# LiveKit の configredis: address: redis:6379# Egress の config も同じ Redis詰まり③ 設定ファイルを直しても反映されない
症状:マウントした config(livekit.yaml など)を書き換えて docker compose up -d しても、古い設定のまま動く。
原因:docker compose up -d はサービス定義(compose ファイル)の差分でしか作り直さない。マウント先のファイル中身が変わっても検知しないので、コンテナは起動時に読んだ古い設定のまま。
対処:docker compose restart <service>(or up -d --force-recreate)で作り直して再読込させる。地味だが数十分溶かした。
詰まり④ Egress の RTMP 出力先 URL は「2セグメント」必須
症状:Egress の合成出力を RTMP でメディアサーバに送ろうとすると rtmp urls must be of format rtmp(s)://{host}(/{path})/{app}/{stream_key} で弾かれる。
原因:Egress は RTMP 出力先に rtmp://host/{app}/{stream_key}(ホストの後にパスが2つ) を要求する。rtmp://host/streamkey(1つ)だとダメ。
対処:メディアサーバ側のパスを2セグメントに合わせる。例えば MediaMTX なら「アプリ名 live + ストリームキー」で rtmp://mediamtx:1935/live/{id} の形にする。ついでに配信ソフト(OBS 等)からの入力(サーバ=rtmp://host/live, キー={id})と出力を同じパス=同じ HLS に寄せられて都合がいい。
詰まり⑤ 空の部屋だと Egress の合成が始まらない
症状:Egress を開始しても EGRESS_STARTING のまま。合成用の Chrome(ヘッドレス)は起動しているのに、いつまでも配信が始まらない(エラーも出ない)。
原因:部屋に映像トラックが1つも無い(誰も publish していない)と、合成する中身が無くて先に進まない。カメラの無いヘッドレス環境で検証しようとすると、この状態になる。
対処(カメラ無しで検証する方法):LiveKit Ingress を立て、ffmpeg のテスト映像を RTMP で流し込んで部屋に「参加者トラック」として publish する。これで合成 → HLS までヘッドレスで全ループを確認できる。
ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=15 \ -f lavfi -i sine=frequency=800 \ -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p \ -c:a aac -f flv rtmp://localhost:<ingress_rtmp_port>/x/<stream_key>- Ingress の RTMP ポートは、メディアサーバの RTMP(1935)と衝突しないよう別ポートにマップする(例: ホスト側 1936)。
- 実トラックが入ると Egress は
EGRESS_ACTIVEになり、合成された HLS セグメントが出てくる。実際、テスト映像を流したら 10 秒ほどで HLS の.tsセグメント(数MB の実データ)が取れた。
まとめ
| 詰まり | 原因 | 対処 |
|---|---|---|
| ① ブラウザから WebRTC 不通 | LiveKit がコンテナ内 IP を広告 | --node-ip=ホストLAN IP を広告 |
| ② Egress がハング | LiveKit–Egress 間の Redis 未共有 | 両方に同じ Redis |
| ③ 設定が反映されない | up -d はマウント中身の変更を見ない | restart で再読込 |
| ④ RTMP 出力が弾かれる | 2セグメント(/app/key)必須 | メディアサーバのパスを合わせる |
| ⑤ 合成が始まらない | 部屋にトラックが無い | Ingress + ffmpeg で実トラック投入 |
一番効いたのは ①(--node-ip)。ローカル Docker で WebRTC が絡むときは「広告している IP がホストから届くか」を最初に確認するのが近道だった。②〜⑤も合わせると、Windows の Docker Desktop でもブラウザ実機なしで多人数ライブ配信の全ループを検証できるようになる。
(本番は Linux サーバ上での運用が前提。上記はローカル開発環境を成立させるための工夫。)