Однажды я готовил очередную виртуалку, чтобы установить на нее Web Call Server и несколько раз клонировать для дальнейшего развертывания тестовой CDN. И захотелось мне, что бы весь этот процесс выполнялся самостоятельно, без моего участия.

Кроме экономии времени на развёртывание, это еще и очень удобно. Вот, например, если один сервер не справляется с нагрузкой, автоматически разворачивается еще один сервер, который забирает часть нагрузки на себя. После снижения нагрузки ниже определенного уровня — дополнительный сервер отключается.

Итак, я решил запустить WCS в Docker контейнере. Причин и вариантов применения было несколько:

  1. Необходимость в развертывании большого количества WCS серверов. Например, для организации тестовой CDN. Или для не тестовой CDN, но в этом случае, контейнеры должны быть развернуты на разных удаленных друг от друга хостах.
  2. Необходимость развернуть количество WCS большее, чем доступно физических или виртуальных машин.
  3. Быстрая организация тестовых стендов.
  4. Ну и просто потому, что это стильно, модно, молодежно.

 

Решений этих задач тоже было несколько:

Для установки можно было написать скрипт, но это было бы слишком просто. Или сложно. И этот вариант не сможет учесть все зависимости и другие факторы для работы программного обеспечения. Например, обновление версии операционной системы и служебных библиотек.

Для множественного развертывания и быстрой организации тестовых стендов можно было бы использовать виртуальные машины. Один раз развернуть сервер, сделать образ и потом клонировать его в нужных количествах. В этом варианте тоже есть недостатки. Самый главный из которых — размер образа. Для виртуальной машины в образ включается все: и операционная система, и все сопутствующие программы, поэтому зачастую образ получается достаточно объемным. Для развертывания виртуальных машин из образа требуется не только время, но и достаточно большое количество ресурсов — CPU и RAM.

И, наконец, контейнеризация в Docker. Контейнеризация — это нечто среднее между запуском программного обеспечения на физическом сервере и полной виртуализацией. Главная идея Docker — запуск приложений в изолированном пространстве. Контейнеры нужны, чтобы окружение одного процесса не мешало другому. Каждый контейнер по умолчанию изолирован от других контейнеров и от машины, на которой он работает. Ещё одним важным отличием докера от обычных виртуальных машин является то, что он не эмулирует аппаратную часть. Он использует ресурсы системы, но изолирует сам процесс. Поэтому не рекомендуют запускать контейнеры из образов, которые были собраны под платформы, отличные от той, где запущен докер.

С использованием Docker для тестовых ландшафтов все понятно — это быстро и удобно. И у меня появилась идея использовать контейнеры не только для тестовых стендов CDN, но и для организации трансляции видеопотоков, например, сделать систему видеонаблюдения за подъездами нескольких домов.

Один дом => один Docker контейнер

На подъездах домов установлены IP камеры. WCS в Doсker контейнере получает RTSP видеопотоки от IP камер и конвертирует их в WebRTС потоки, которые в свою очередь воспроизводятся на сайте Управляющей Компании.

RTSP-to-WebRTC-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Плюсом реализации этой задачи с использованием контейнеров Docker будет возможность «на лету» переконфигурировать систему видеонаблюдения, не затрагивая уже работающую часть. Для добавления или отключения дома нужно будет только запустить или завершить работу контейнера, и эту работу даже можно доверить скриптам.

Дело осталось за малым — запустить WCS в Docker.

Как два байта переслать!

В Docker Hub уже загружен образ Flashphoner Web Call Server.

docker-hub-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Развертывание WCS сводится к двум командам:

1. Загрузить актуальную сборку с Docker Hub

docker pull flashponer/webcallserver

2. Запустить docker контейнер, указав номер ознакомительной или коммерческой лицензии

docker run \
-e PASSWORD=password \
-e LICENSE=license_number \
--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

где:

  • PASSWORD — пароль на доступ внутрь контейнера по SSH. Если эта переменная не определена, попасть внутрь контейнера по SSH не удастся;
  • LICENSE — номер лицензии WCS. Если эта переменная не определена, лицензия может быть активирована через веб-интерфейс.

 

Но, если бы все было настолько просто не было бы этой статьи.

Все красиво на бумаге…

На своей локальной машине с операционной системой Ubuntu Desktop 20.04 LTS я установил Docker:

sudo apt install docker.io

Создал новую внутреннюю сеть Docker с названием «testnet»:

sudo docker network create \
--subnet 192.168.1.0/24 \
--gateway=192.168.1.1 \
--driver=bridge \
--opt com.docker.network.bridge.name=br-testnet testnet

Cкачал актуальную сборку WCS с Docker Hub

sudo docker pull flashphoner/webcallserver

Запустил контейнер WCS

sudo docker run \
-e PASSWORD=password \
-e LICENSE=license_number \
-e LOCAL_IP=192.168.1.10 \
--net testnet --ip 192.168.1.10 \
--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Переменные здесь:

  • PASSWORD — пароль на доступ внутрь контейнера по SSH. Если эта переменная не определена, попасть внутрь контейнера по SSH не удастся;
  • LICENSE — номер лицензии WCS. Если эта переменная не определена, лицензия может быть активирована через веб-интерфейс;
  • LOCAL_IP — IP адрес контейнера в сети докера, который будет записан в параметр ip_local в файле настроек flashphoner.properties;
  • в ключе —net указывается сеть, в которой будет работать запускаемый контейнер. Запускаем контейнер в сети testnet.

 

Проверил доступность контейнера пингом

ping 192.168.1.10

ping-to-docker-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Открыл Web интерфейс WCS в локальном браузере по ссылке https://192.168.1.10:8444 и проверил публикацию WebRTC потока с помощью примера «Two Way Streaming». Все работает.

publish-play-ubuntu-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Локально, с моего компьютера на котором установлен Docker, доступ к WCS серверу у меня был. Теперь нужно было дать доступ коллегам.

И вот здесь меня поджидали первые грабли.

Грабли №1

Внутренняя сеть Docker является изолированной, т.е. из сети докера доступ «в мир» есть, а «из мира» сеть докера не доступна.

Получается, чтобы предоставить коллегам доступ к тестовому стенду в Docker на моей машине, я должен предоставить консольный доступ к своей машине. Для тестирования внутри группы разработчиков такой вариант с натяжкой допустим. Но мне то хотелось это все запустить в продакшен. Неужели, миллиарды контейнеров во всем мире работают только локально?

Конечно же нет. Путем курения мануалов был найден ответ. Нужно пробросить порты. Причем проброс портов нужен не на сетевом маршрутизаторе, а в самом Dockere.

Отлично! Список портов известен. Пробрасываем:

docker run \
-e PASSWORD=password \
-e LICENSE=license_number \
-e LOCAL_IP=192.168.1.10 \
-e EXTERNAL_IP=192.168.23.6 \
-d -p8444:8444 -p8443:8443 -p1935:1935 -p30000-33000:30000-33000 \
--net testnet --ip 192.168.1.10 \
--name wcs-docker-test --rm flashphoner/webcallserver:latest

В этой команде используем следующие переменные:

  • PASSWORD, LICENSE и LOCAL_IP — мы рассмотрели выше;
  • EXTERNAL_IP — IP адрес внешнего сетевого интерфейса. Записывается в параметр ip в файле настроек flashphoner.properties;
  • Так же в команде появляются ключи -p — это и есть проброс портов. В этой итерации используем ту же сеть «testnet», которую мы создали раньше.

 

В браузере на другом компьютере открываю https://192.168.23.6:8444 (IP адрес моей машины с Docker) и запускаю пример «Two Way Streaming»

play-with-port-forwarding-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Web интерфейс WCS работает и даже WebRTC трафик ходит.

И все было бы прекрасно, если бы не вторые и третьи грабли, которые на этот раз были в паре.

Грабли №2 и №3

Контейнер с включенным пробросом портов запускался у меня около 10 минут. За это время я бы успел вручную поставить пару копий WCS. Такая задержка происходит из-за того, что Docker формирует привязку для каждого порта из диапазона.

При попытке запустить второй контейнер с этим же списком портов, я ожидаемо получил ошибку, что диапазон портов уже занят.

Получается, что вариант с пробросом портов мне не подходит — из-за медленного старта контейнера и необходимости менять порты для запуска второго и последующих контейнеров.

Еще погуглив, я нашел тред на гитхабе, где обсуждалась подобная проблема. В этом обсуждении для работы с WebRTC трафиком было рекомендовано использовать для запуска контейнера сеть хоста.

Запускаем контейнер в сети хоста (на это указывает ключ —net host)

docker run \
-e PASSWORD=password \
-e LICENSE=license_number \
-e LOCAL_IP=192.168.23.6 \
-e EXTERNAL_IP=192.168.23.6 \
--net host \
--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Отлично! Контейнер запустился быстро. С внешней машины все работает — и web интерфейс и WebRTC трафик публикуется и воспроизводится.

play-with-port-forwarding-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Потом я запустил еще пару контейнеров. Благо на моем компьютере несколько сетевых карт.

На этом можно было бы поставить точку. Но меня смутил тот факт, что количество контейнеров на хосте будет упираться в количество сетевых интерфейсов.

Способ, который я использую в продакшене

Начиная с версии 1.12 Docker предоставляет два сетевых драйвера: Macvlan и IPvlan. Они позволяют назначать статические IP из сети LAN.

  • Macvlan — позволяет одному физическому сетевому интерфейсу (машине-хосту) иметь произвольное количество контейнеров, каждый из которых имеет свой собственный MAC-адрес.
  • Требуется ядро Linux v3.9–3.19 или 4.0+.
  • IPvlan — позволяет создать произвольное количество контейнеров для вашей хост машины, которые имеют один и тот же MAC-адрес.
  • Требуется ядро Linux v4.2 + (поддержка более ранних ядер существует, но глючит).

 

Я использовал в своей инсталляции драйвер IPvlan. Отчасти, так сложилось исторически, отчасти у меня был расчет на перевод инфраструктуры на VMWare ESXi. Дело в том, что для VMWare ESXi доступно использование только одного MAC-адреса на порт, и в таком случае технология Macvlan не подходит.

Итак. У меня есть сетевой интерфейс enp0s3, который получает IP адрес от DHCP сервера.

network_adapter-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

т.к. в моей сети адреса выдает DHCP сервер, а Docker выбирает и присваивает адреса самостоятельно, это может привести к конфликтам, если Docker выберет адрес, который уже был назначен другому хосту в сети.

Что бы этого избежать нужно зарезервировать часть диапазона подсети для использования Docker. Это решение состоит из двух частей:

  1. Нужно настроить службу DHCP в сети таким образом, чтобы она не назначала адреса в некотором определенном диапазоне.
  2. Нужно сообщить Docker об этом зарезервированном диапазоне адресов.

 

В этой статье я не буду рассказывать, как настраивать DHCP сервер. Думаю, каждый айтишник в своей практике сталкивался с этим не единожды, в крайнем случае, в сети полно мануалов.

А вот как сообщить Docker, какой диапазон для него выделен, разберем подробно.

Я ограничил диапазон адресов DHCP сервера так, что он не выдает адреса выше 192.168.23. 99. Отдадим для Docker 32 адреса начиная с 192.168.23.100.

Создаем новую Docker сеть с названием «new-testnet»:

docker network create -d ipvlan -o parent=enp0s3 \
--subnet 192.168.23.0/24 \
--gateway 192.168.23.1 \
--ip-range 192.168.23.100/27 \
new-testnet

где:

  • ipvlan — тип сетевого драйвера;
  • parent=enp0s3 — физический сетевой интерфейс (enp0s3), через который будет идти трафик контейнеров;
  • —subnet — подсеть;
  • —gateway — шлюз по умолчанию для подсети;
  • —ip-range — диапазон адресов в подсети, которые Docker может присваивать контейнерам.

 

и запускаем в этой сети контейнер с WCS

docker run \
-e PASSWORD=password \
-e LICENSE=license_number \
-e LOCAL_IP=192.168.23.101 \
-e EXTERNAL_IP=192.168.23.101 \
--net new-testnet --ip 192.168.23.101 \
--name wcs-docker-test --rm -d flashphoner/webcallserver:latest

Проверяем работу web интерфейса и публикацию/воспроизведение WebRTC трафика с помощью примера «Two-way Streaming»:

Есть один маленький минус такого подхода. При использовании технологий Ipvlan или Macvlan Docker изолирует контейнер от хоста. Если, например, попробовать пропинговать контейнер с хоста, то все пакеты будут потеряны.

isolation-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Но для моей текущей задачи — запуска WCS в контейнере — это не критично. Всегда можно запустить пинг или подключиться по ssh с другой машины.

ssh-to-docker-WCS_Docker_network_WebRTC_browser_CDN_streaming_publish

Используя технологию IPvlan на одном Docker хосте можно поднять необходимое количество контейнеров. Это количество ограничено только ресурсами хоста и, частично, сетевой адресацией конкретной сети.

Заключение

Запуск контейнеров в Dockere может быть сложным только для новичков. Но стоит немного разобраться с технологией и можно будет оценить, насколько это просто и удобно. Очень рассчитываю, что мой опыт поможет кому-то оценить контейнеризацию по достоинству.

Ссылки

WCS в Docker