•
9 min read
Bedrud vs Jitsi: сравнение self-hosted сервисов видеозвонков

На моём сервере теперь работают два сервиса видеозвонков: Jitsi Meet на meet.gluek.info и Bedrud на call.gluek.info. Оба устроены одинаково, по схеме SFU (Selective Forwarding Unit, «избирательный пересыльщик»): каждый участник отправляет своё видео на сервер один раз, а сервер, ничего не перекодируя, раздаёт его остальным, причём каждому в том качестве, которое тому нужно. Разница в том, что вокруг этого медиасервера.

Из чего состоят

Jitsi Meet (docker-jitsi-meet)Bedrud + LiveKit
Контейнеры4: web (nginx), Prosody (XMPP), Jicofo, JVB2: Bedrud и LiveKit
МедиасерверJVB, JavaLiveKit, Go
СигнализацияXMPP через Prosody, Jicofo управляет конференциямиHTTP API Bedrud и WebSocket LiveKit
Размер образов≈ 3,1 ГБ (JVB 1,2 ГБ, Jicofo 1 ГБ, web 570 МБ, Prosody 312 МБ)≈ 350 МБ (LiveKit 140 МБ, Bedrud 206 МБ)
Память в простое≈ 460 МБ (JVB 300, Jicofo 100, Prosody 33, web 11)≈ 50–90 МБ (Bedrud 10–50, LiveKit 40)
Порты наружу443 через прокси + UDP 10000443 через прокси + UDP 7882 и TCP 7881
База данныхотдельной нет (Prosody хранит данные в файлах)SQLite

Память я смотрел через docker stats на одном и том же сервере в простое. У JVB по умолчанию ещё и большой лимит: я ограничил контейнер 1,5 ГБ, и Java охотно занимает то, что ей дают.

Важная оговорка: LiveKit у меня уже был для звонков в Matrix, поэтому реальная цена Bedrud для меня — это один контейнер на 50 МБ. Если ставить его с нуля, Bedrud умеет запускать встроенный LiveKit сам, и это всё равно один процесс.

Установка и настройка

Jitsi ставится по официальной инструкции docker-jitsi-meet: скачать релиз, скопировать env.example в .env, сгенерировать пароли скриптом gen-passwords.sh, указать PUBLIC_URL, открыть UDP-порт для JVB. У меня в .env в итоге 15 непустых строк, остальное — значения по умолчанию. Подробная документация, огромное сообщество, ответы на любые вопросы гуглятся.

Проблем с установкой Jitsi у меня не было. Большой плюс — готовые клиенты: в мобильном приложении Jitsi Meet достаточно поменять адрес сервера на свой, и всё работает. К Jitsi привыкли и мои собеседники, и в целом он работает неплохо.

Bedrud — один бинарник или один контейнер с config.yaml на три десятка строк. Но проект молодой (версия 0.13), документация местами расходится с реальностью: образа нет на ghcr, обязательный параметр tokenDuration выглядит необязательным, и без него вход просто не работает. Всё это я разобрал на странице проекта, а по найденным багам написал issue. Самый неприятный для повседневной работы: гости не могут повторно зайти по ссылке в комнату, где звонок уже закончился (подробности ниже). Зато интерфейс администратора, лимиты и пользователи есть из коробки, а в Jitsi для этого пришлось бы настраивать авторизацию в Prosody.

Что умеют

Jitsi MeetBedrud
Создать звонок без аккаунтада, по умолчаниюнет, гости только заходят по ссылке
Аккаунты, админка, лимитычерез Prosody/JWT, без веб-админкивстроены: пользователи, passkey, OAuth, лимиты
Сквозное шифрованиеесть (Insertable Streams)есть флаг E2E в комнате
Комната ожиданиялоббиGate
Записьчерез Jibri (ещё контейнер с Chrome)в разработке
Мобильные приложенияJitsi Meet в Google Play и App Storeобещаны, но не опубликованы: в Google Play страницы нет, в релизах только десктоп (на Mac — неподписанный бинарник в .dmg)
Зрелостьс 2013 года, огромное сообщество0.x, небольшой проект: последний коммит 26 сентября, приглашение в Discord на сайте недействительно

Нагрузка на 5, 10 и 20 человек

Как мерил

Участников изображали headless-браузеры Chromium через Playwright в Docker на моём MacBook. Каждый бот заходит в комнату через обычный веб-интерфейс, как живой человек, поэтому сравниваются не только серверы, но и то, как их веб-клиенты просят видео.

Сценарий как на обычной встрече, где камеры у всех одновременно не включены:

  • 1 «презентующий»: камера показывает медленно меняющийся слайд 720p (вместо демонстрации экрана);
  • 2 «говорящих»: камера 720p с подвижной тестовой картинкой и звук;
  • остальные слушают: камера и микрофон выключены.

Боты заходят по одному, после этого 3 минуты идёт звонок, и в это время на сервере каждые 3–5 секунд пишутся docker stats контейнеров и счётчики сетевого интерфейса. В таблице средние значения за эти 3 минуты. CPU указан в процентах одного ядра: на сервере их 5. Трафик — это прирост относительно фона до начала теста. В Jitsi я учитываю все четыре контейнера, в Bedrud — Bedrud и LiveKit.

Результаты

Jitsi, 5Jitsi, 10Jitsi, 20Bedrud, 5Bedrud, 10Bedrud, 20
CPU сервера, % ядра, среднее182645172644
CPU сервера, % ядра, пик474264203083
Память, МБ491550586134175248
Отдаёт, Мбит/с1,33,47,314,933,060,5
Принимает, Мбит/с1,00,60,56,46,57,0

Почти вся нагрузка Jitsi приходится на JVB (18 → 45% ядра, 350 → 440 МБ), а Jicofo, Prosody и web на звонок почти не реагируют. У Bedrud так же: работает LiveKit (17 → 43% ядра, 80 → 140 МБ), а сам Bedrud тратит 0,1–0,2% CPU.

Что из этого следует

  • Процессор одинаковый. На одной и той же встрече JVB и LiveKit берут почти одинаковую долю ядра: около 45% на 20 человек. На пятиядерном сервере это не проблема ни для одного из них.
  • Память у Bedrud в 2,5–3,5 раза меньше: 134–248 МБ против 490–590 МБ. Разница почти целиком в JVM: JVB с Jicofo занимают 450–540 МБ ещё до того, как начинают что-то пересылать.
  • Трафик отличается в 8 раз, но это разница не серверов, а клиентов. Веб-клиент Jitsi в режиме плиток просит у отправителей только нижний слой качества: три камеры вместе присылают на сервер меньше 1 Мбит/с, то есть по 200–300 кбит/с на камеру. Клиент Bedrud (LiveKit) запрашивает видео под размер плитки, а плитки при трёх камерах большие, и каждый отправитель шлёт около 2 Мбит/с. Картинка в Bedrud заметно лучше, но и каналу сервера это обходится дороже: 60 Мбит/с на 20 человек против 7 у Jitsi. Если пересчитать на мегабит пересылаемого видео, LiveKit тратит процессора в разы меньше.
  • Оговорка про 20 человек в Bedrud: все 20 ботов жили на одном Mac за DSL на 65 Мбит/с, а сервер отдавал им 60 Мбит/с. Канал был почти забит, а Docker на Mac занимал 4,5 ядра на декодировании видео. Возможно, без этого ограничения трафик Bedrud был бы ещё выше.
  • Своп на сервере во время теста не рос (около 2,8 ГБ, как и до теста), так что ни один из серверов не вытеснил другие сервисы.

Чего этот тест не показывает

Это один прогон на каждую точку, с синтетическим видео и с одной машины-клиента. Он хорошо сравнивает два сервера между собой на одной и той же встрече, но не говорит, сколько людей выдержит сервер. Для этого нужно гонять ботов с нескольких машин и с хорошим каналом.

Заодно тест выловил ещё один баг Bedrud: «постоянная» комната выключается после окончания звонка, и гости по старой ссылке в неё больше не попадут, пока хозяин её не пересоздаст (issue #212). Сначала я принял за баг и зависание ботов на «connecting», но его вызвал я сам, когда между прогонами включал комнату прямо в базе, а в LiveKit её к тому моменту уже не было.

Блокировки: какой проще заблокировать

Оба сервиса для браузера выглядят почти одинаково: страница и сигнализация идут по HTTPS на 443-й порт, а звук и видео — по WebRTC, то есть DTLS и SRTP поверх UDP. Сам WebRTC по трафику опознаётся легко: у STUN-пакетов фиксированный «magic cookie», у DTLS характерное рукопожатие. Поэтому если цензор решит резать WebRTC целиком, пострадают оба, и любой другой сервис звонков в браузере тоже. Содержимое при этом зашифровано, а адрес сервера (meet.gluek.info или call.gluek.info) виден только в SNI при подключении к сайту.

Различаются они тем, что будет, если заблокировать конкретный порт или UDP вообще:

  • Jitsi шлёт медиа через один порт UDP 10000. Это порт JVB по умолчанию, и правило «резать UDP 10000» пишется в одну строку. Поэтому после публикации поста я подключил Jitsi к TURN-серверу coturn, который уже стоял на сервере для другого сервиса. Если UDP не проходит, клиент идёт через TURN по TCP 3478, а если закрыт и он, то через TURN по TLS на 443-м порту (turn.gluek.info).
  • LiveKit принимает медиа на UDP 7882, а если UDP не проходит, клиент сам переключается на TCP 7881 (ICE-TCP). Качество по TCP хуже, но звонок состоится. Порты тоже стандартные для LiveKit, так что опознать его по ним так же легко.
  • Сигнализация у Jitsi идёт через XMPP по WebSocket, у Bedrud — через WebSocket LiveKit. Обе спрятаны внутри TLS на 443-м порту и снаружи от обычного HTTPS не отличаются.
  • IP-адрес у обоих один и тот же — адрес моего VPS. Если заблокируют его, перестанет работать всё сразу, включая сайт.

Итого: теперь у обоих есть путь, который переживает блокировку всего, кроме 443-го порта. TURN по TLS на 443-м порту снаружи выглядит как обычное HTTPS-соединение с turn.gluek.info, и отличить его можно разве что по характеру трафика. Порт 443 на сервере занят Caddy, поэтому я поставил сборку Caddy с модулем layer4: она смотрит на SNI в начале TLS-соединения и отдаёт соединения с turn.gluek.info в coturn, а всё остальное — сайтам. Тот же TURN я отдал и LiveKit: если не проходят ни UDP 7882, ни TCP 7881, клиенты Bedrud и Element Call тоже уходят на turn.gluek.info:443. Путь через TLS я проверил тестовыми соединениями, а настоящий звонок из сети, где открыт только 443-й порт, ещё не пробовал.

Итог

Если LiveKit на сервере уже есть, Bedrud почти бесплатен: два контейнера, в 2,5–3,5 раза меньше памяти, чем у Jitsi, при том же процессоре и с более качественной картинкой. Но проект сырой: за один вечер я нашёл четыре бага (ссылка на комнату, например, живёт ровно один звонок), а мобильных клиентов, которые обещает сайт, пока нет.

Я оставляю оба. Никогда не знаешь, какой из них в нужный момент сработает лучше: однажды Jitsi не запустился на холодную и подвёл меня, а второй сервис на том же сервере стоит почти ничего.

You can reply from any Fediverse (Mastodon, GoToSocial, etc.) website or app by pasting this URL into the search field of your client: