
Unity 2020→2021 LTSアップデートにあわせたクラウド実行用Unity Linux環境のメンテ事項
英会話学習メタバースfondiでエンジニアをしているmuoです
fondiは
ひとつの空間に最大17人のユーザーが3Dアバターで入って英会話する
海外ユーザー比率が94%で特に東南アジアのAndroidユーザーが多い(GalaxyのA0系やVivoのY系もよく利用されている)
というアプリの性質から、端末実機での負荷検証重要度が高いです
アバターが空間に複数存在する際のGPU負荷はダミーのアバターをスポーンさせるデバッグ機能を実装すれば一定計測できますが、マルチユーザー同期ミドルウェアのPhoton経由で反映されるアニメーショントリガによって引き起こされるアバターのメッシュ変形や空間内での多様なユーザーアクションに基づくコライダーなどの影響までをデバッグ機能として作り込むのは結構大変です(主に少人数チームでメンテできる気がしません)
実端末を大量に用意して新機能リリース前にテスト(QA)するのはもちろんですが、日常的な開発のなかで大量の端末操作をするのは開発体験が著しく悪いです
このためfondiの開発チームでは2021年からLinux環境を利用した実アプリに近いテストクライアントによるマルチユーザーテスト補助に取り組んでいます
イメージ的には↓のようなことをしてきました

実際のクライアントと同様にPhoton系のイベントを発行するダミーユーザーを大量にテスト空間へ投入し、負荷影響を検証ターゲット端末実機で確認するというものです
スクリーンショットはGUI版のものですが、GUIを取り除いたHeadless版(Unity 2021以降はDedicated Serverという名前に変わりました)のほうが非ゲーミングPCでも10-15クライアント程度問題なく並行稼働できて便利なので、最近はHeadless版を積極的にメンテしています
ベース環境はWindowsやmacOSのUnity Standaloneビルドでもよいのですが、OSライセンス的に使いやすくDockerによるコンテナ化(そしてチーム内向けプライベートレジストリによるバイナリ配布)を介した環境整備・放棄のしやすさという面から、Linux環境をターゲットとして必要なときに必要なだけクラウドリソースを割り当ててテストするほうが最終的に開発者の日常負荷が低いであろうという読みです
※このエントリはfondiアドベントカレンダー2022(存在しない記憶)の23日目のエントリです(ほんとうはGPUコンテナ上でREALITYアバターを動かしてみた REALITY Advent Calendar #21 というエントリに共感して書いてみました)
Unity 2021.3へのアップデートモチベーション
fondiチームではUnityの年次バージョンがLTSリリースされたら徐々に検証を進め、一定のタイミングにて本番環境適用するポリシーで年次アップデートを実施しています
今年は2022年11月にリリースしたアプリアップデートでUnityを2021.3系へ更新しました
今回のアップデートでは機能面での強化はもちろん、Unity 2020時点で壊れていた OnDemandRendering.renderFrameInterval によるオンデマンドレンダリングが正常動作する期待、などに加えて
チームのフルタイムメンバー・副業/業務委託で関わって頂いている方々の手元機材にApple SiliconベースのMacが増えてきたことからApple Silicon環境のUnity Editorネイティブサポートも大きなモチベーションでした
Unity Editor起動時やimport発生時の速度向上など、プロダクトに関わるなかでパフォーマンスを発揮してもらいやすい環境を整える側面でも期待が大きなバージョンアップです
複雑なprefabを編集する際にUnity Editorがクラッシュしやすいという事象もUnity 2020で頻発しており、これが解消する期待もありました(結果として残念ながら依然クラッシュ率は高いので2022へ期待)
私はUnity 2021へのアップデート作業の大半が完了したタイミングで触り始め、Linux関連でいくつかハマるだろうなと思っていたらしっかりハマったので問題と解決策を共有します
issue:Linux環境でのUnity Editor動作
floating windowの表示で99%クラッシュする問題 https://issuetracker.unity3d.com/issues/linux-editor-crashes-at-burst-signal-handler-when-opening-floating-windows
これはUnity Editorを利用する場合の話で、クラウド上でコンテナ動作させる分にはあまり関係ない話なのですが、Unity Editorを起動してBuild Configに代表される各種ポップアップ画面を開くだけでEditorが99%クラッシュします

Unityのissueを見てのとおり、結構長く解決未定のまま置かれていたのですが執筆時点の最新版であるUnity 2021.3.16f1でついに解決したようです🎉(未検証)
ここではワークアラウンドについて記述します
Linux用Standalone/Dedicatedバイナリを生成する際には手元環境がLinuxのほうが便利ということもあり、ワークフロー整備中には頻繁にLinuxビルドをするので結構困ります
どうもUnity Hubから起動することで内部設定されるX/Waylandの描画コンフィグが絶妙に壊れているようで、フォーラムではUnity Hubの旧バージョンインストールといったワークアラウンド策が提示されていますが、どうにも不毛なのでUnity Hub外からUnity Editorを起動することで回避します
Unity Editorをそのまま直接起動するとUnity Hub経由でのライセンスバリデーションを利用できないので、.ulf(マニュアルライセンスファイル)を利用するかUnity Hub側に持っているライセンスキャッシュファイルを移動するなどの手間がかかります
Unity 2021.3.16f1へアップデートするか、ご自身の環境にあわせた解決をしましょう
Unity本体設定:ProjectSettings.assetの書き換え
前述のように、従来Standaloneビルドのビルドオプションとして用意されていたHeadlessビルドがUnity 2021ではDedicated Serverという別プラットフォーム扱いに変わりました

Standaloneとの行き来時のSwitch Platform自体は一瞬で済むので安心ですが、C#コードコンパイル時のsymbol定義が別プラットフォームとして切り分けられたため再設定が必要です
Player Settingsから手動でぽちぽちやるのは不毛なので、ProjectSettings/ProjectSettings.asset を直接テキストエディタで開き
scriptingDefineSymbolsにぶら下がっているStandaloneの値をServerというキーへコピーするのが手っ取り早いです
サードパーティーライブラリにまつわるもの
fondiではサードパーティーのライブラリを多く利用しており、さまざまな理由でそれらのバージョンアップがUnityアップデート時にまとめてやってくる年次行事という感があります
Agora
Bugsnag
Firebase
Photon(PUN2)
Pusher
SRDebugger
などいろいろ利用していますが、案の定いくつかLinuxサポートに関してハマりました
サードパーティーライブラリ:Agora
fondiではバーチャル空間での音声通話ミドルウェアとしてAgoraを利用しています
従来はAgora 3.5系を利用していましたが、Unity 2021化に際してAgora 4.0系をテストしつつ、結果的に3.7.2へアップデートしました
さて、Agoraは残念ながらLinux環境をランタイムとしてサポートしていません(3系でも4系でも)
ないので最低限体裁を整える必要があり、このためにはライブラリ構造に関する一定の理解が必要です
UnityからAgoraを扱う公式のAgora Unity SDKは
Agora Unity SDK
Agora Native Plugin
という2層構造になっており、何も考えずにアプリを起動するとC#側のAgoraクラスローダーからAgora Native Pluginの初期化コードが呼ばれるタイミングで内部例外が発生、その後もAgora系のメソッドが呼ばれるたびに内部でエラーを返します
特に厄介なのは各種sceneにおいてZenjectによりDIしていく部分で、DIシーケンスの途中で例外を吐くとシーケンスが途中で停止してscene実行に必要な依存関係解決に至らない場合が多くあります
AgoraはWindowsやmacOSなどをネイティブサポートしており、Linuxのためだけにライブラリ全体をラッピングしたりDIシーケンスをつぶさに観察してif分岐を埋め込むなどは避けたかった(もともと私の手元Linux環境でUnity Editorを最低限動作させて開発進行することを目的にして対応スタートしたため)という事情があります
ともあれC#的にみるとagoraSdkCWrapper.soという共有ライブラリへアクセスしDllImportしてメソッドを呼び出せればよいので、同名のfondi独自モックライブラリ(中身は全部 return 0; するだけ)のコードをUnity用リポジトリ内に置いておき、Linux環境で開発するメンバーや共有用Dockerイメージのビルド者が適宜手元でmakeして実行環境に配置するという使い方をしてきました
さて、今回アップデートしたAgora 3.7系ではAgora自体の内部構造がIrisベースへと大きく変わっており、SDKの呼び出し口もAgoraRtcWrapper.soへと変わり、Native側の呼び出し手順も変わっています
他社様のライブラリなので詳細は避けますが、内部のPlayerやRecorder類の初期化が完了したあとはCallIrisApiという呼び出しを通じて内部コマンドを発行し、コマンド側では64KBのメモリをアサインしてJSONを返却するというそれなりにリッチな連携方法です
自前のモックではネイティブ側でのバッファを適当なアドレスへ固定してもよいのですが、ちゃんとC#側で解釈できるデータを用意するのが若干面倒だったのでここはエラーを返しておくことにしました
このため、一部の内部API呼び出しは常にfailすることになります(Agora関連機能の完全動作はそもそも期待しておらず起動から各画面遷移まではカバーできたので一旦よしとしました)
サードパーティーライブラリ:Firebase SDK
fondi内には、iOS/Android両対応のプッシュ通知やRealtime DatabaseなどFirebase利用箇所があります
今回Unity 2021化とあわせてFirebase SDK 9.4.0へアップデートしたところ、手元Linuxデスクトップ環境では正常動作するもののDockerコンテナ内の挙動がおかしいことに気付きました
どうもタイトル画面から3Dアバターのホーム画面への遷移すら失敗しています
ログを確認すると、正常起動時には
Mono path[0] = '/home/muo-work/fondi/fondi-build/testing/fondi-server_Data/Managed'
Mono config path = '/home/muo-work/fondi/fondi-build/testing/fondi-server_Data/MonoBleedingEdge/etc'
Preloaded 'AgoraRtcWrapper.so'
Preloaded 'FirebaseCppApp-9_4_0.so'
Preloaded 'FirebaseCppAuth.so'
Preloaded 'FirebaseCppDatabase.so'
Preloaded 'FirebaseCppMessaging.so'
Preloaded 'FirebaseCppStorage.so'
Preloaded 'lib_burst_generated.so'
Preloaded 'FirebaseCppAnalytics.so'
Initialize engine version: 2021.3.11f1 (0a5ca18544bf)
[Subsystems] Discovering subsystems at path /home/muo-work/fondi/fondi-build/testing/fondi-server_Data/UnitySubsystems
となる一方、コンテナ内では
Mono path[0] = '/fondi/fondi_Data/Managed'
Mono config path = '/fondi/fondi_Data/MonoBleedingEdge/etc'
Preloaded 'AgoraRtcWrapper.so'
Preloaded 'lib_burst_generated.so'
Unable to preload the following plugins:
FirebaseCppAnalytics.so
FirebaseCppApp-9_4_0.so
FirebaseCppAuth.so
FirebaseCppDatabase.so
FirebaseCppMessaging.so
FirebaseCppStorage.so
Initialize engine version: 2021.3.11f1 (0a5ca18544bf)
となっており、Firebaseのライブラリ読み込みに失敗しています
コンテナ内でlddコマンドにより依存ライブラリを調査します
# ldd FirebaseCppApp-9_4_0.so
linux-vdso.so.1 (0x00007fff1f43c000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc75c890000)
libsecret-1.so.0 => not found
libglib-2.0.so.0 => not found
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc75c88b000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc75c65f000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc75c576000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc75c556000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc75c32e000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc75f6ee000)
libsecretへの依存が最近のFirebase SDKで追加されたようですね
少々依存パッケージ規模が大きめですが、APTからインストールします
apt install libsecret-1-0
パッケージ名の指定に若干のクセがあります
リリースされているのは0.20.5-3ですが、指定はlibsecret-1-0です(おそらくsemverやっていくぞという気持ちの表明でしょう)
この変更をDockerfileに反映すると
Mono config path = '/fondi/fondi_Data/MonoBleedingEdge/etc'
Preloaded 'AgoraRtcWrapper.so'
Preloaded 'FirebaseCppApp-9_4_0.so'
Preloaded 'FirebaseCppAuth.so'
Preloaded 'FirebaseCppDatabase.so'
Preloaded 'FirebaseCppMessaging.so'
Preloaded 'FirebaseCppStorage.so'
Preloaded 'lib_burst_generated.so'
Preloaded 'FirebaseCppAnalytics.so'
Initialize engine version: 2021.3.11f1 (0a5ca18544bf)
無事にログイン系が通りました
まとめ
Unity 2020につづき、Unity 2021でも無事にテスト用クライアントをDockerへ詰め込めたので、おおむねどこのクラウド環境でも動作するようになりました
Unity自体のアップデートによる問題・ミドルウェアのアップデートに伴うものの両方へ対応する必要がありましたが、いまのfondiのユースケースにおいては「根本的に無理!!」というパターンを踏まずに済んでいます
個人的にはArmベースのAWS Fargateが大好きなのでUnityのLinux Arm出力がサポートされることを願うばかりですが、Unity Technologies社からは「プラットフォーム上で動作するとこまで持っていけてもそれだけじゃプラットフォームサポートの5%に過ぎないんだよお」(大意)というしごくまっとうな発言がされているのでまあ諦めですね
どうしても欲しければForumに書くよりSalesに連絡しろ、というのもおっしゃるとおりです・・・