遠隔教育向けビデオ会議システム BigBlueButton on Docker(構築メモ)

Ubuntu18.04BigBlueButton Version2.3の開発版で構築中。本記事は構築メモと捉えて下さい。正常動作確認後別記事で纏めます。

Jitsiと同様、会議システムですが、遠隔教育システムとして利用されています。
Dockerによりシステム構築します。

BigBlueButon

STUNサーバリスト(参考)

Docker

Architecture

BigBlueButtonとは、以下のブロック図による各コンポーネントを一纏めにした会議システムです。各コンポーネントを統括する司令塔は、非同期で並列処理を実行するアクターモデルによるプログラミング言語ScalaとそのツールキットであるAkkaにより作成されたアプリである bbb-apps-akka( + akka-fsesl ) が担います。

docker-compose-bbb

以下bbb-confスクリプトの起動時または停止時のスクリプトからも、各コンポーネントの確認ができます。

ブロック図に示した各コンポーネント毎にコンテナを配備する方法とは別に、一つのOSコンテナ(ubuntu18.04:ver2.3)に纏めてインストールしたい場合は以下を参照願います。

手軽に構築できます。とにかく動作を確認したい、試験的に運用したい方向けです。

bbb-webrtc-sfu

bbb-webrtc-sfuは別レポジトリとなっているため、以下クローン後にイメージをビルド

$ docker build -t bbb-webrtc-sfu .

Kurento dockerイメージのビルド

kurento-media-server

$ docker build --tag kurento/kurento-media-server:6.14.0 --build-arg UBUNTU_CODENAME="bionic" --build-arg KMS_VERSION="6.14.0" .

kurento-media-serverについては、新規にUbuntu:bionic(18.04)ベースのバージョン6.14イメージをビルド。

このイメージをベースにbbb-kurentoイメージをビルド

Dockerfile

FROM kurento/kurento-media-server:6.14.0

$ cd labs/docker/kurento/
$ docker build -t bbb-kurento .

<参考>
Kurento設定ファイル、機能拡張設定ファイル、STUNサーバ設定など

  • /etc/kurento/kurento.conf.json: The main Kurento Media Server configuration file
  • /etc/kurento/modules/kurento/MediaElement.conf.ini: Generic parameters for Media Elements.
  • /etc/kurento/modules/kurento/SdpEndpoint.conf.ini: Audio/video parameters for SdpEndpoints (i.e. WebRtcEndpoint and RtpEndpoint).
  • /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini: Specific parameters for WebRtcEndpoint.
  • /etc/kurento/modules/kurento/HttpEndpoint.conf.ini: Specific parameters for HttpEndpoint.

STUNサーバによりポートの割当が適宜行われるため、ポートオプションにより予め必要なポートレンジは確保しないこと(メモリーオーバフロー対策)。

ポートの割当が多くなるとコンテナによるメモリー負荷が増えるため、ネットワークモードはホストとするか、レンジを小さく設定すること。

iptablesによりポートフォワードを利用する方法がベスト。FreeSWITCH<ポート割当ヒント> 参照のこと。

BigBlueButton on Docker に必要な新規作成したイメージファイル一覧

REPOSITORY                                    TAG                  IMAGE ID            CREATED             SIZE
bbb-coturn                                    latest               8add43c854f8        19 hours ago        92.8MB
bbb-webhooks                                  latest               8610d7753621        2 days ago          161MB
bbb-html5                                     latest               439daac50083        2 days ago          4.9GB
node                                          12-slim              544e10fd4863        2 days ago          142MB
bbb-web                                       latest               2c4bf867271b        5 days ago          2.08GB
bbb-nginx                                     latest               42a7f92d54e0        9 days ago          133MB
bbb-freeswitch                                latest               68cd11a3e2ea        13 days ago         613MB
bbb-webrtc-sfu                                latest               0c5047c1ada8        2 weeks ago         995MB
redis                                         6.0-bionic           4547a49212e6        3 weeks ago         94.3MB
mongo                                         4.2-bionic           f57898cf435c        3 weeks ago         388MB
bbb-lti                                       latest               15f6b7a57389        3 weeks ago         504MB
bbb-kurento                                   latest               5761d94dec46        3 weeks ago         792MB
kurento/kurento-media-server                  6.14.0               dcced31b05df        3 weeks ago         716MB
bigbluebutton/greenlight                      v2                   08ae3c304ddc        3 weeks ago         286MB
bbb-fsesl-akka                                latest               4659696111a4        3 weeks ago         388MB
bbb-apps-akka                                 latest               5ca10ae678e4        3 weeks ago         390MB
bbb-fsesl-client                              latest               f1dc1bc1b3cb        3 weeks ago         1.18GB
bbb-common-web                                latest               1c45577365a7        3 weeks ago         1.28GB
bbb-common-message                            latest               18cb41e0d64e        3 weeks ago         1.13GB
sbt                                           1.2.8                8716b05626d5        3 weeks ago         608MB
ubuntu                                        bionic               6526a1858e5d        4 weeks ago         64.2MB
postgres                                      9.5                  a4924338843c        5 weeks ago         196MB
nginx                                         latest               4bb46517cac3        5 weeks ago         133MB
node                                          12                   cfcf3e70099d        6 weeks ago         917MB
openjdk                                       8                    5684f3366a1f        6 weeks ago         511MB

Mongo Dockerイメージファイル作成

$ git clone https://github.com/docker-library/mongo.git
$ cd mongo/4.2
$ docker build --tag mongo:4.2-bionic .

Redis Dockerイメージファイル作成

$ git clone https://github.com/docker-library/redis.git
$ cd redis/6.0

DockerfileのベースOSをUbuntu:bionic(18.04)へ変更してビルド

FROM ubuntu:bionic

$ docker build --tag redis:6.0-bionic .

.envファイルの作成

TURN_DOMAIN=<turn domain>
TURN_PORT=443
TURN_SECRET=<Please produce it by openssh rand command >
SERVER_DOMAIN=<server domain>
SHARED_SECRET=<Please produce it by openssh rand command >

# Stun Server Global IP
EXTERNAL_IP=xx.xx.xx.xx

# Nginx Reverse Proxy Local IP
SERVER_LOCAL_IP=192.168.x.xxx

SCREENSHARE_EXTENSION_KEY=akgoaoikmbmhcopjgakkcepdgdgkjfbc
SCREENSHARE_EXTENSION_LINK=https://chrome.google.com/webstore/detail/bigbluebutton-screenshare/akgoaoikmbmhcopjgakkcepdgdgkjfbc
POSTGRES_DB=<db_name>
POSTGRES_USER=<db_user>
POSTGRES_PASSWORD=<db_password>

STUNサーバドメインとサーバドメインを指定して下さい。
TURN_SECRET, SHARED_SECRETについては、以下opensslコマンド(コマンドオプションは任意)で作成します。

$ openssl rand -hex 16
911d53c0a899fd26d596c505394b2ce8

Docker-Composeファイル

${xxxxxx} 変数は .env ファイルにより指定。 コンテナ外のNginxリバースプロキシから直接ルーティング。

version: '3.5'

services:
  mongo:
    image: mongo:4.2-bionic
    restart: unless-stopped
    networks:
      - bbb-network

  redis:
    image: redis:6.0-bionic
    restart: unless-stopped
    networks:
      - bbb-network

  bbb-html5:
    image: bbb-html5:latest
    restart: unless-stopped
    depends_on:
      - mongo
      - redis
    environment:
      MONGO_URL: mongodb://mongo:27017/bbbhtml5
#      MONGO_URL: mongodb://mongo:27017/html5client
      METEOR_SETTINGS_MODIFIER: ".public.kurento.wsUrl = \"wss://${SERVER_DOMAIN}/bbb-webrtc-sfu\" | .public.kurento.enableVideo = true | .public.kurento.enableScreensharing = true | .public.kurento.chromeDefaultExtensionKey = \"${SCREENSHARE_EXTENSION_KEY}\" | .public.kurento.chromeDefaultExtensionLink = \"${SCREENSHARE_EXTENSION_LINK}\" | .public.kurento.enableVideoStats = true | .public.kurento.enableListenOnly = true"
      REDIS_HOST: redis
      ROOT_URL: http://127.0.0.1/html5client
#      ROOT_URL: https://${SERVER_DOMAIN}/html5client
    extra_hosts:
      - ${SERVER_DOMAIN}:${SERVER_LOCAL_IP}
    ports:
      - 3000:3000
    networks:
      - bbb-network

  bbb-webhooks:
    image: bbb-webhooks:latest
    restart: unless-stopped
    depends_on:
      - redis
    environment:
      REDIS_HOST: redis
      SHARED_SECRET: ${SHARED_SECRET}
      BEARER_AUTH: 1
      SERVER_DOMAIN: ${SERVER_DOMAIN}
    extra_hosts:
      - ${SERVER_DOMAIN}:${SERVER_LOCAL_IP}
    networks:
      - bbb-network

  bbb-freeswitch:
    image: bbb-freeswitch:latest
    restart: unless-stopped
    depends_on:
      - coturn
    expose: 
      - 8021
    ports:
      - 7443:7443
    extra_hosts:
      - ${SERVER_DOMAIN}:${SERVER_LOCAL_IP}
    volumes:
      - ./media-audio:/var/freeswitch/meetings
    networks:
      - bbb-network

  bbb-webrtc-sfu:
    image: bbb-webrtc-sfu:latest
    restart: unless-stopped
    depends_on:
      - redis
      - kurento
      - bbb-freeswitch
    expose:
      - 3010
    ports:
      - 3008:3008
#   defined bbb/webrtc-sfu/config/default.example.yml
    environment:
#      KURENTO_NAME: kurento
      KURENTO_URL: ws://kurento:8888/kurento
      REDIS_HOST: redis
      FREESWITCH_CONN_IP: bbb-freeswitch
      LOG_LEVEL: debug
    extra_hosts:
      - ${SERVER_DOMAIN}:${SERVER_LOCAL_IP}
    networks:
      - bbb-network

  coturn:
    image: bbb-coturn:latest
    restart: unless-stopped
    environment:
      TURN_DOMAIN: ${TURN_DOMAIN}
#     TURN_PORT: 443
      TURN_PORT: ${TURN_PORT}
      SECRET: ${TURN_SECRET}
      EXTERNAL_IP: ${EXTERNAL_IP}
      ENABLE_REST_API: 1
      PORT: 3478
    networks:
      - bbb-network
#        aliases: 
#          - ${TURN_DOMAIN}

  kurento:
    image: bbb-kurento:latest
    restart: unless-stopped
    volumes:
      - ./media-video:/var/kurento/recordings
      - ./media-screenshare:/var/kurento/screenshare
    environment:
      STUN_IP: ${EXTERNAL_IP}
      STUN_PORT: ${TURN_PORT}
    networks:
      - bbb-network

  bbb-apps-akka:
    image: bbb-apps-akka:latest
    restart: unless-stopped
    depends_on:
      - redis
    environment:
      JAVA_OPTS: -Dredis.host=redis
    networks:
      - bbb-network

  bbb-fsesl-akka:
    image: bbb-fsesl-akka:latest
    restart: unless-stopped
    depends_on:
      - bbb-freeswitch
      - redis
#    command: ["wait-for-it.sh", "bbb-freeswitch:8021", "--timeout=60", "--strict", "--", "/usr/share/bbb-fsesl-akka/bin/bbb-fsesl-akka"]
    command: bash -c "cd /usr/share/bbb-fsesl-akka/ && wait-for-it.sh bbb-freeswitch:8021 --timeout=60 --strict -- /usr/share/bbb-fsesl-akka/bin/bbb-fsesl-akka"
    environment:
      JAVA_OPTS: -Dredis.host=redis -Dfreeswitch.esl.host=bbb-freeswitch
    networks:
      - bbb-network

  bbb-web:
    image: bbb-web:latest
    restart: unless-stopped
    depends_on:
      - redis
    volumes:
      - ./bigbluebutton:/var/bigbluebutton
    environment:
      SERVER_DOMAIN: ${SERVER_DOMAIN}
      SHARED_SECRET: ${SHARED_SECRET}
      TURN_DOMAIN: ${TURN_DOMAIN}
      TURN_PORT: ${TURN_PORT}
      TURN_SECRET: ${TURN_SECRET}
    ports: 
      - 8090:8090
    networks:
      - bbb-network

  bbb-greenlight:
    image: bigbluebutton/greenlight:v2
    restart: unless-stopped
    depends_on:
      - gl-db
    volumes:
      - ./greenlight_logs:/usr/src/app/log
      - ./greenlight_db:/usr/src/app/storage
    env_file:
      - .env
      - greenlight.env
    extra_hosts:
      - ${SERVER_DOMAIN}:${SERVER_LOCAL_IP}
    ports:
      - 80:80
    networks:
      - bbb-network

  gl-db:
    image: postgres:9.5
    restart: unless-stopped
    expose:
      - 5432
    volumes:
      - ./db/production:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    networks:
      - bbb-network

networks:
  bbb-network:

Greenlight Dockerイメージファイルの作成

docke-composeコマンドによる各コンテナ起動前に、BigBlueButtonのウェブインターフェイスであるGreenlightのイメージファイルも事前に作成しておきましょう。

Github

DockerHub

$ git clone -b v2 --single-branch https://github.com/bigbluebutton/greenlight.git
$ cd greenlight
$ docker build -t bigbluebutton/greenlight:v2 .

コンテナ内でドメインの名前解決が出来ない問題が発生した場合

名前解決出来ているかどうか、以下コマンドまたは curl コマンドでチェックします。

# bundle exec rake conf:check
Checking environment: Passed
Checking Connection: Failed
Error connecting to BigBlueButton server - Failed to open TCP connection to www.your-domain.com:443 (Connection refused - connect(2) for "www.your-domain.com" port 443)

エラーが発生した場合、コンテナ内で /etc/hosts にドメインに対応したサーバのローカルIPを追加します。

$ docker exec -ti  docker_bbb-greenlight_1 bash

# vi /etc/hosts
192.168.xx.xx www.your-domain.com

再度上記チェックコマンドで確認して下さい。

上記は、docker-compose ファイルで、以下の extra_hosts セクションを追加することと同義です。

Greenlight専用の環境変数ファイルgreenright.envを、下記サンプルファイルを雛形に新規作成してdocker-composeファイルで指定します。

FreeSWITCH

Github

Dockerfileに以下PostgreSQL関連ライブラリのインストールを追加すること。

mod_spandsp

$ docker exec -ti docker_bbb-freeswitch_1 bash
# apt update
# apt install libtiff5-dev

PostgreSQL in the core

$ docker exec -ti docker_bbb-freeswitch_1 bash
# apt-get install libpq-dev

<ポート割当ヒント>

Running the Container
CID=$(sudo docker run --name freeswitch -p 5060:5060/tcp -p 5060:5060/udp -p 5080:5080/tcp -p 5080:5080/udp -p 8021:8021/tcp -p 7443:7443/tcp -p 60535-65535:60535-65535/udp -v /home/ubuntu/freeswitch/conf:/usr/local/freeswitch/conf bettervoice/freeswitch-container:1.6.6)

Keep in mind that freeswitch has to be able to read the mounted volume.

Large port range issue
Because of an issue in docker, mapping a large port range like in -p 60535-65535:60535-65535/udp can eat a lot of memory. Starting docker with --userland-proxy=false solves this partially, but startup will still be slow. As a workaround you can remove this from the docker commandline and manually add the iptables rules instead:

CIP=$(sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $CID)

$ sudo iptables -A DOCKER -t nat -p udp -m udp ! -i docker0 --dport 60535:65535 -j DNAT --to-destination $CIP:60535-65535
$ sudo iptables -A DOCKER -p udp -m udp -d $CIP/32 ! -i docker0 -o docker0 --dport 60535:65535 -j ACCEPT
$ sudo iptables -A POSTROUTING -t nat -p udp -m udp -s $CIP/32 -d $CIP/32 --dport 60535:65535 -j MASQUERADE

Etherpad

https://etherpad.org/

Shared Secret Keyの変更

In bbb-web container

/usr/share/bbb-web/WEB-INF/classes/bigbluebutton.properties

beans.dynamicConferenceService.securitySalt=<value_of_salt>

以下サイトでUUID(Universal Unique ID)を作成

DockerコンテナNginxリバースプロキシーによるSSL接続エラーのヒント

アドミンユーザホーム画面

Screenshot from 2020-09-16 00-06-47

エラー:ブルースクリーン

Screenshot from 2020-09-16 00-08-00

参照:ブルースクリーンエラー

bbb-html5 v2.3-α2


設定ファイル(コンテナ内)
# /app/bundle/programs/server/assets/app/config/settings.yml

Screenshot from 2020-09-18 22-42-22

Coturn: Stun/Turn Server

Session Traversal Utilities for NAT (STUN)
音声・ビデオ通話する際、LAN内に配備されたユーザ双方のグローバルIPを特定するために使用されます。ユーザ双方のIP特定後、データは直接遣り取りされます。

Traversal Using Relay around NAT (TURN)
上記データの直接の遣り取りが許可されない場合、TURNサーバを仲介役にデータを遣り取りします。

Network address translation ( NAT )

STUNサーバ確認用クライアントstun-clientによる動作確認

$ sudo apt-get install stun-client
$ stun 172.217.212.127:19302
STUN client version 0.97
Primary: Open
Return value is 0x000001

STUN/TURNサーバ動作確認サイト

/etc/turnserver.conf

listening-port={{ .Env.PORT }}
tls-listening-port={{ .Env.TURN_PORT }}

# Fingerprints in TURN messages are required for WebRTC
fingerprint

# The long-term credential mechanism is required for WebRTC
lt-cred-mech

use-auth-secret
static-auth-secret={{ .Env.TURN_SECRET }}

# If the realm value is unspecified, it defaults to the TURN server hostname.
# You probably want to configure it to a domain name that you control to
# improve log output. There is no functional impact.
realm={{ .Env.TURN_DOMAIN }}

# Configure TLS support.
# Adjust these paths to match the locations of your certificate files

#cert=/etc/letsencrypt/live/ssl/fullchain.pem
#pkey=/etc/letsencrypt/live/ssl/privkey.pem

# Limit the allowed ciphers to improve security
# Based on https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
cipher-list="ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS:!AESCCM;"

# Enable longer DH TLS key to improve security
dh2066

# All WebRTC-compatible web browsers support TLS 1.2 or later, so disable
# older protocols
no-tlsv1
no-tlsv1_1

# Log to a single filename (rather than new log files each startup). You'll
# want to install a logrotate configuration (see below)
log-file=/var/log/coturn.log