はじめに
こんにちは!サーバサイドエンジニアのクロ(@kro96_xr)です。
この記事はSynamon Advent Calendar 2021の10日目です。
弊社はUnityエンジニアが多く私自身も最近はUnityを触っていたりするのですが、せっかくのアドベントカレンダーということでサーバサイドの記事を書こうと思い筆を取りました。
テーマは「HubsCloudをDocker環境で動かしてみる」です!Elixir初体験だけどなんとかなるやろ!対戦よろしくお願いします。
※正直行き当たりばったりな部分も多く、自分の理解の浅さから改善できるところは多々あると思います。ご指摘などございましたらTwitterなどにいただけると嬉しいです。
Mozilla Hubs/HubsCloudとは?
Mozilla HubsとはFirefoxで有名なMozilla社が提供しているオープンソースのVRシステムです。ブラウザ上で動作するため、PCやスマートフォンを使ってVR空間に入ることが可能であり、起動自体も公式サイトからルームを作成するだけで可能です。
HubsCloudとは上記のMozilla HubsをAWS上で動作させることができるものです。AWSのマーケットプレイスで公開されているので導入自体は比較的簡単に出来るかと思います。とはいえ常時起動だとそれなりにコストもかかるんですよね。
それならローカルで起動してみればよくない?
というわけで調べてみるといくつかローカルで起動している記事は見つかりましたが、Dockerで動かしている記事は見つけられませんでした。それなら自分で手を動かしてDockerの環境構築を試してみましょう!
なお、公式discordではDockerに関してプライベートチャンネルでやりとりされているようでした。後述しますがローカル環境構築のサポートはしていない旨の記載がありましたので開発者に絞っているのでしょうか。一応依頼すれば招待してもらえそうでしたが…。
注)以下全て記事公開時点での情報になります。
構成および名称について
まず初めにHubsのシステム構成と記事内で出てくる名称についてざっくり説明します。
詳細についてはこちらをご覧いただくと良いかと思います。
クライアント
- Hubs
クライアントはReact、Three.js、A-Frameを組み合わせて作られています。
今回こちらもローカルで立ち上げますが、Dockerにはのせないこととします。
リポジトリはこちら
- Hubs
クライアントはReact、Three.js、A-Frameを組み合わせて作られています。
サーバーサイド
データベース
DBについてはPostgres DBが使われており、推奨バージョンは11.xです。
Docker環境構築
それでは早速環境構築に移りましょう。
Reticulumのリポジトリを見ると、「チーム規模が小さいからローカル環境構築のサポートはしてないよ。でも自分で設定することは可能だよ。やる場合はHubsとDialogもローカル実行が必要だよ。」(意訳)と書いてあります。
Due to our small team size, we don't support setting up Reticulum locally due to restrictions on developer credentials. Although relatively difficult and new territory, you're welcome to set up this up yourself. In addition to running Reticulum locally, you'll need to also run Hubs and Dialog locally because the developer Dialog server is locked down and your local Reticulum will not connect properly
というわけでdocker-composeを使ってReticulum, Dialog, DBを立ち上げていくことにします。
余談ですがreticulumリポジトリにあるdocker-compose.ymlの更新日が3年前でした。つらい。
ディレクトリ構成
ディレクトリ構成はざっくりこのような形にしました。dialog
ディレクトリとreticulum
ディレクトリの構成自体はクローンしたものとほぼ同じなので省略しています。 また、クライアント(hubs/)は任意の場所で構いません。
ReticulumTest/ ┠dialog/ ┃ ┠リポジトリからcloneされたファイル ┃ ┗certs/ (後述) ┃ ┠server.key ┃ ┠server.pem ┃ ┗pub.key ┠reticulum/ ┃ ┠リポジトリからcloneされたファイル ┃ ┗storage/ ┃ ┗dev/ ┠tmp/ ┃ ┗db/ ┠docker-compose.yml ┗Dockerfile
Dockerの設定
Dialog用のDockerfileはリポジトリのものをそのまま使っているので割愛します。
- reticulum/Dockerfile
# 指定バージョンのelixir/Erlangが入ったベースイメージを使用 # https://hub.docker.com/layers/hexpm/elixir/1.8.2-erlang-22.3.4.23-ubuntu-focal-20210325/images/sha256-825e3361145e2394690e2ef94d6cc4587a7e91519ad002526d98156466d63643?context=explore FROM hexpm/elixir:1.8.2-erlang-22.3.4.23-ubuntu-focal-20210325 # ディレクトリの設定 ARG ROOT_DIR=/ret RUN mkdir ${ROOT_DIR} WORKDIR ${ROOT_DIR} # ディレクトリのコピー COPY ./reticulum ${ROOT_DIR} # 依存するライブラリのインストール RUN apt-get update && apt-get install -y git inotify-tools RUN mix local.hex --force && mix local.rebar --force && mix deps.get # キャッシュファイル用ディレクトリ作成 RUN mkdir -p /storage/dev && chmod 777 /storage/dev EXPOSE 4000
- reticulm/docker-compoes.yml
version: '3' services: db: image: postgres:11 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres volumes: - ./tmp/db:/var/lib/postgresql/data ports: - "5432:5432" ret: build: context: ./ dockerfile: Dockerfile environment: - "MIX_ENV=dev" - "DB_HOST=db" volumes: - ./reticulum:/ret - ./storage/dev:/storage/dev tty: true ports: - "4000:4000" depends_on: - db dialog: build: context: ./dialog dockerfile: Dockerfile environment: - "HTTPS_CERT_FULLCHAIN=/app/certs/server.pem" - "HTTPS_CERT_PRIVKEY=/app/certs/server.key" - "AUTH_KEY=/app/certs/pub.key" tty: true volumes: - ./certs:/app/certs ports: - "4443:4443"
各種環境変数について補足
MIX_ENV
コンパイル時に使用される設定ファイルを指定しています。この場合reticulum/config/dev.exs
が読み込まれます。DB_HOST, POSTGRES_USER, POSTGRES_PASSWORD
dev.exs
にDB設定があるのですが、接続に使用するユーザー名とパスワードがdev環境ではpostgresで指定されています。本来であれば当然変えるべきなのですが、今回は環境構築がメインということでそのまま指定しています。また、DB_HOST
を指定しないとlocahost
で接続しにいくのですが、コンテナ間通信では使えないのでコンテナ名であるdb
を指定する必要があります。HTTPS_CERT_FULLCHAIN, HTTPS_CERT_PRIVKEY, AUTH_KEY
後述しますが、Dialogサーバの証明書ファイル
、秘密鍵
、Reticulumサーバの公開鍵
を設定します。
env_db_host = "#{System.get_env("DB_HOST")}" # Configure your database config :ret, Ret.Repo, username: "postgres", password: "postgres", database: "ret_dev", hostname: if(env_db_host == "", do: "localhost", else: env_db_host), template: "template0", pool_size: 10
初回起動
ビルドとコンテナの起動 特に言うことはありませんね。
docker-compose build
docker-compoes up -dDB生成
reticulumのコンテナに入ります。
DB生成を行います。そこそこ時間がかかるので気長に待ちましょう。docker-compose exec ret bash
root@~:/ret# mix ecto.create
サーバ起動
iex -S mix phx.server
ここで終わったと思ってhttps://localhost:4000/
としても残念ながら動きません。
ソースコードを追うとわかるのですが、クライアント(Hubs)側でwebpackでサーバを立ち上げており、そのhtmlを取得しています。 そのためクライアント側の設定を進めていく必要があります。
なお、接続先自体はdev.exs
に設定があります。こちらも後で修正します。
config :ret, Ret.PageOriginWarmer, hubs_page_origin: "https://#{host}:8080", admin_page_origin: "https://#{host}:8989", spoke_page_origin: "https://#{host}:9090", insecure_ssl: true
また、それ以外にも多数躓いたポイントがあったのでひとつひとつ進めていきます!
動かすまでにやること
HOSTSファイルの修正
hubs.local
、hubs-proxy.local
と127.0.0.1
を紐づけてください。(方法は割愛)
クライアント(Hubs)の起動
hubsリポジトリのREADMEに記載されている手順に沿ってクライアントの立ち上げを行いましょう。
まず、hubsのhubs-cloudリポジトリから任意の場所にクローンして依存関係のインストールを行います。
cd hubs
npm ci
インストールが完了したらWebpack Dev Serverを立ち上げます。
npm run local
クライアント-管理画面(Hubs/admin)の起動
こちらもREADMEに従って依存関係のインストールとWebpack Dev Serverの起動を行います。
cd hubs/admin
npm install
npm run local
Dialogの設定
Dialogコンテナではdocker-compose.yml
で指定した通り証明書ファイル
、秘密鍵
、Reticulumサーバの公開鍵
が必要になります。
また、Reticulumリポジトリにあったサーバ証明書は有効期限が切れているので新たにオレオレ証明書を生成して設定してください。(方法は各自でお願いしますmm)
ポイントとしては、SAN(Subject Alternative Name)のDNS Nameの値がhubs.local
になっていないとChromeでエラーになります。
Chromeがコモンネームの設定を非推奨化、そのエラー対策としての自己署名証明書のCSRの作り方
ちなみに各サーバの証明書ファイルの配置は下記の通りです。リネームして置き換えるか、設定ファイルをいじって参照先を変えるかご自由にどうぞ。
- Reticulumでは
reticulum/priv/dev
- Hubsでは
hubs/certs
- Dialogでは
sialog/certs
置き換えた後、ブラウザにルートCAの証明書をインポートする必要がありますが、こちらも各自お願いします。
Google Chromeへ証明書ファイルをインポートするには | GMOグローバルサイン サポート
最後に、Reticulumサーバの環境変数に秘密鍵の内容を設定しておきます。
docker-compose exec ret bash
export PERMS_KEY={生成した秘密鍵の内容}
これがないとReticulumとDialog間の通信ができずルーム入室できません。
永続化できてないけど一旦このままで…
各ソースコードの修正
これで動くようになった…かと思いきやできません。
Dockerを使っていなければReticulum⇔Webpack Dev Server間の通信が出来るはずなのですが、 今回はコンテナ⇔ホスト間の通信のため、一工夫必要です。
誰だDocker使おうなんて言い出したの。
Reticulumの修正
dev.exs
を開き、host_front = "host.docker.internal"
を定義してhubs_page_origin
らを修正します。
host = "hubs.local" host_front = "host.docker.internal" 略 config :ret, Ret.PageOriginWarmer, # hubs_page_origin: "https://#{host}:8080", # admin_page_origin: "https://#{host}:8989", # spoke_page_origin: "https://#{host}:9090", hubs_page_origin: "https://#{host_front}:8080", admin_page_origin: "https://#{host_front}:8989", spoke_page_origin: "https://#{host_front}:9090", insecure_ssl: true
これでやっと通信できる!…と思いきやもう少し修正が必要です。
こちらはクライアント側のhubs/webpack.config.js
ですが、allowedHosts
でhubs.local
が指定されています。
devServer: { https: createHTTPSConfig(), host: "0.0.0.0", public: `${host}:8080`, useLocalIp: true, allowedHosts: [host, "hubs.local"], headers: { "Access-Control-Allow-Origin": "*" },
ですので、reticulum/lib/ret/http_util.ex
を修正してリクエストヘッダでHOSTを指定してあげましょう。
defp retry_until_success(verb, url, body, options) do default_options = [ # headers: [], headers: [{"Host", "hubs.local"}], cap_ms: 5_000, expiry_ms: 10_000, append_browser_user_agent: false ] 略
続いてローカル環境のDialogとの通信のための修正です。
dev.exs
を以下のように修正します。
# dev_janus_host = "dev-janus.reticulum.io" dev_janus_host = "hubs.local" # config :ret, Ret.JanusLoadStatus, default_janus_host: dev_janus_host, janus_port: 443 config :ret, Ret.JanusLoadStatus, default_janus_host: dev_janus_host, janus_port: 4443
続いてadd_csp.ex
を修正します。
# default_janus_csp_rule = # if default_janus_host != nil && String.length(String.trim(default_janus_host)) > 0, # do: "wss://#{default_janus_host}:#{janus_port} https://#{default_janus_host}:#{janus_port}", # else: "" default_janus_csp_rule = if default_janus_host, do: "wss://#{default_janus_host}:#{janus_port} https://#{default_janus_host}:#{janus_port} https://#{default_janus_host}:#{janus_port}/meta", else: ""
最後にiex -S mix phx.server
でサーバを立ち上げなおしましょう。
長い道のりでしたがこれでhttps://hubs.local:4000?skipadmin
にアクセスすればトップページが表示されるはずです。
※開発者モードでコンソールを見るとわかるのですが、ロケールの問題で翻訳できない部分([@formatjs/intl Error MISSING_TRANSLATION]
)が出てきています。
これは本家mozilla hubsを見ても一部だけ日本語訳されているので仕方ないと思ってそのままにします。
ユーザー登録を試してみる
右上のサインインをクリックするとサインイン画面に遷移します。ここでメールアドレスを入力してNextを押すと…
Reticulumを立ち上げているコンソールにリンクが表示されるのでURLをクリックして認証を完了します。
DBの確認
DBコンテナに入ってユーザーを確認します。
docker-compose exec db bash
root@~:/# psql -U postgres -d ret_dev
ret_dev=# ret_dev=# SELECT * FROM accounts;
ちゃんと登録されていますね。
管理ポータルを試してみる
DBにアクセスしてis_admin
をtrue
に変更します。
その後https://hubs.local:4000/admin
にアクセスすると管理画面が表示されるはずです。
ルームの作成を試してみる
Dialogコンテナに入りWebRTCサーバを起動します。この際、コンテナ内のIPアドレスが必要になります。
Start dialog with MEDIASOUP_LISTEN_IP=XXX.XXX.XXX.XXX MEDIASOUP_ANNOUNCED_IP=XXX.XXX.XXX.XXX npm start where XXX.XXX.XXX.XXX is the local IP address of the machine running the server. (In the case of a VM, this should be the internal IP address of the VM).
というわけでコンテナに入って下記のように立ち上げます。
本当はdocker-network使ってやればいいんでしょうが…それだとアドベントカレンダーに間に合わない
docker-compose exec dialog bash
root@~:/app# hostname -i
{ipアドレス}
root@~:/app# MEDIASOUP_LISTEN_IP={ipアドレス} MEDIASOUP_ANNOUNCED_IP={ipアドレス} npm start
トップページの部屋を作成する
ボタンから入室します。
真っ暗な空間ではありますが、無事に入室することができました!
リアクションなどもこの通り!不気味!
まだまだ実用には程遠いですがひとまず動くところまでいったので今回はこれでおしまいです。
終わりに
Dockerで環境構築出来たら遊び倒せるし、環境の共有も楽だなーと軽い気持ちで始めたら思った以上に大変で泣きそうでした。
しかし、苦しんだ分HubsCloudへの理解を少し深めることができたのではないかなと思います。
普段であればおそらくここまで記録に残すこともないでしょうし、あらためてアドベントカレンダーに参加して良かったです。
参考リンク
以下参考リンクです。ありがとうございました。
Home · gree/hubs-docs-jp Wiki · GitHub
Mozilla HubsのバックエンドサーバーReticulumを改造する方法
Reticulumをローカルで動かしてデプロイする - Qiita
告知
本テックブログやnote記事のお知らせは、Synamon公式Twitterで発信しています。
弊社の取り組みに興味を持っていただけましたらぜひフォローお願いします!
カジュアル面談も実施中ですので「詳しく話を聞いてみたい!」という方はチェックいただけると嬉しいです。
▼カジュアル面談はMeetyから meety.net
▼エントリーはこちら herp.careers
Synamonアドベントカレンダーはまだまだ続きますので、今後もご覧いただけると嬉しいです!