Synamon’s Engineer blog

Synamonはリアルとデジタルの融合を加速させるため、メタバース領域で法人向けにサービス提供を行うテックカンパニーです。現在開発を進めている「メタバース総合プラットフォーム」をはじめ、メタバース市場の発展に向けた事業展開を行っています。このブログでは、メタバース技術とその周辺の技術、開発全般に関してエンジニアがお話しします。

AWSを使ったクラウドUnityビルド環境の構築~ビルドサーバー構築編~

エンジニアの岡村です。この記事は以前掲載した『AWSを使ったクラウドUnityビルド環境の構築~ライセンスサーバー構築編~』の続編であり、シリーズの2記事目です。

synamon.hatenablog.com

前回の記事ではEC2を使ってライセンスサーバーを作成したので、そこに接続してライセンス認証をしてビルドを行うWindowsマシン(self-hosted runner)をAWS上に作っていきます。

UnityのCI事情

Unityを使ってCIを行おうとしたときにまず当たるのが、世のCIサービスのUnity対応の渋さです。サービス側がホストしてくれているマシンにUnityがインストールされている事はまずなく、エディタの処理もサイズも軽いわけではないのでデフォルトで用意されている非力なマシンに毎回ダウンロード、インストールして動かすとかなりの無駄が発生してしまいます。公式が提供しているUnity Cloud Buildが一番簡単に使えて楽なのですが、ビルドに時間が掛かってしまうのと、カスタマイズの低さに難があります。

かといって自前のマシンにインストールしたUnityを動かすのにも色々な落とし穴があったりします。セットアップされたUnityインストール済みのマシンを適当なCIツールから叩くのであればまだ楽ですが、例えばコンテナベースでやろうとしたりすると、大量の地雷を踏むこととなります(Windows Server Coreでサイレントクラッシュするなど)。最近のUnityはコマンドライン経由でのインストールや実行、前の記事で紹介したフローティングライセンスをサポートしてくれており、昔よりはだいぶ扱いやすくなったのですが、それでも叩き方にクセがあったりして一筋縄ではいきません。これはクラウドに限らず、物理PCでCIを回すときにも大変苦戦することになりました。今回はその知見もあったのでクラウド移行は割と楽に出来るかと思っていたのですが、そもそもWindows×UnityがCIしんどい環境の掛け合わせなのもあり、AWSに乗せる中でまたしても色々苦労することになりました。

EC2 スポットインスタンスの活用

前述のとおりUnity Editorは軽いアプリケーションではありません。EC2インスタンスは自由なスペックで簡単に用意できてメンテナンスフリーなのが魅力的ですが、強いインスタンスを立てると相応にお金がかかってしまいます。なので不要な時には止めておきたいのですが、その管理も出来れば自動で行いたいです。自動化するとAWSのスポットインスタンスを利用してさらに値段を抑える事が出来ます。という事で今回は必要になったタイミングでEC2のスポットインスタンスを起動し、不要になったら(しばらく利用されなかったら)シャットダウンする仕組みの構築を目指してみました。

philips-labs/terraform-aws-github-runnerの採用

github.com

今回スポットインスタンスの管理にはこちらのOSSを利用させて頂きました。他にもいくつか似た機能を実現するOSSはあったのですが、今回こちらを選んだのは主に以下のような点でした。

  • AWS, EC2を利用する前提要件を満たしている(コンテナベースもありましたが、コンテナでのビルドは以前挑戦して失敗したので……)
  • Terraformで記述されている(社内でTerraformを利用している為)
  • Windows, Linuxのインスタンスに対応している(本当はMacも欲しかったのですが、UnityのビルドはWindows上で行えるのと、Github-hosted runnerでXCode入りのMacが使えるので妥協しました)
  • コミュニティが活発である

ちなみに、この辺りのOSS選定基準は以前佐藤さんが書かれたこちらの記事も参考になります。

synamon.hatenablog.com

OSSを利用してrunner用の環境を構築する

terraform-aws-github-runnerを使うと、以下のような構造が出来上がります。 (画像はGitHubより引用)

LambdaのコードなどはOSS側で用意してもらえますが、GitHubと繋ぐための設定や、実際に動かすマシンイメージ(後述)と設定は自分で用意する必要があります。基本的な手順はOSSのドキュメントに記載されている為割愛します。

設定項目 設定した値(参考)
runner_extra_labels "self-hosted,unity-2021.2.11f1"
instance_types "t3.xlarge"
volume_size 50

※ここのラベルは今後GitHub Actionsを作成するときにrunnerを指定する為に利用します。

Terraformの知識があれば構築にそう苦労することはないかと思います。(自分は知識がなかったので社内の得意なメンバーにお願いしました。この場を借りて感謝します。)

ただし、ドキュメント中に「GitHubのAppKeyをBase64エンコードしろ」という指示がありますが、これはBase64でエンコードされているpemキーをさらにBase64でエンコードしろという意味なので、ここだけ気をつけてください。

runner用のVPCとライセンスサーバーのVPCをVPC Peeringで接続する

プライベートIPでライセンスサーバーとのやり取りを行う場合には、VPC Peeringを利用してVPC同士を接続すると便利です。

※IPアドレスは一例です

ピアリング接続を作成した後、それぞれのルートテーブルにルートを追加し、それぞれのセキュリティグループで送信元SGを許可するように設定してください。ライセンスサーバーが動いていれば、ランナー用に用意したサブネット内に適当なインスタンスを立ててhttp://***.***.***.***:8080/v1/status(プライベートIPアドレス)にアクセスすれば疎通確認ができます。

Runner用のAMIを構築する

Unityをインストールしたマシン(を作る為のマシンイメージ)を用意していきます。今回はAWSのサービスの一つであるEC2 Image Builderを利用して作成しました。

aws.amazon.com

手動でEC2インスタンスを立て、Unityをインストールした後イメージ化してもいいのですが、もし手動で構築する場合は、Unityインストール後にSysprepを実行する必要があります。

EC2 Image Builderで利用するための、Unityをインストールするコンポーネントを作成します。サンプルのyamlファイルを紹介するので、http://***.***.***.***:8080の部分を前回の記事でセットアップしたライセンスサーバーのアドレスに置き換えてください。また、 ここでインストールするエディタのバージョンも指定しているので、利用するバージョンにあわせて書き換えてください。

name: UnityInstallDocument
description: 
schemaVersion: 1.0

phases:
  - name: build
    steps:
      - name: PowerShellVersion
        action: ExecutePowerShell
        inputs:
          commands:
            - $PSVersionTable
      - name: DownloadHub
        action: ExecutePowerShell
        inputs:
          commands:
            - Invoke-WebRequest https://public-cdn.cloud.unity3d.com/hub/prod/UnityHubSetup.exe -OutFile UnityHubSetup.exe
      - name: InstallHub
        action: ExecuteBinary
        inputs:
          path: .\UnityHubSetup.exe
          arguments:
            - /S
      - name: InstallEditor
        action: ExecutePowerShell
        inputs:
          commands:
            - 'Start-Process -FilePath "C:\Program Files\Unity Hub\Unity Hub.exe" -Wait -ArgumentList "-- --headless install --version 2021.3.0f1 --changeset 6eacc8284459 -m windows-il2cpp -m android -m android-sdk-ndk-tools -m android-open-jdk -m linux-il2cpp -m ios --childModules"; exit 0'
      - name: DownloadVisualStudio
        action: ExecutePowerShell
        inputs:
          commands:
            - Invoke-WebRequest https://aka.ms/vs/17/release/vs_buildtools.exe -OutFile vs_buildtools.exe
      - name: InstallVisualStudio
        action: ExecuteBinary
        inputs:
          path: .\vs_buildtools.exe
          arguments:
            - --quiet
            - --wait
            - --add
            - Microsoft.VisualStudio.Component.VC.Tools.x86.x64
            - --add
            - Microsoft.VisualStudio.Component.Windows10SDK.20348
      - name: RegisterEnvironmentVariableForVisualStudio
        action: ExecutePowerShell
        inputs:
          commands:
            - '[Environment]::SetEnvironmentVariable("VS170COMNTOOLS", "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools", "Machine")'
      - name: CreateLicenseFile
        action: ExecutePowerShell
        inputs:
          commands:
            - 'New-Item "$env:PROGRAMDATA\Unity\config" -ItemType Directory -Force'
            - '@{licensingServiceBaseUrl = "http://***.***.***.***:8080"; enableEntitlementLicensing = $true; clientConnectTimeoutSec = 10; clientHandshakeTimeoutSec = 10} | ConvertTo-Json | Out-File "$env:PROGRAMDATA\Unity\config\services-config.json" -Encoding ascii;'

※上記コード中でUnity HubをExecuteBinaryではなくExecutePowerShellで実行しているのは、HubがEditorインストールを正常完了してもリターンコードに1を返す謎仕様でパイプラインが失敗扱いになるのを回避する為です。

※2022年4月15日追記 上記コード中にPowerShellでjsonファイルを作成している箇所がありますが、-Encoding utf8を指定していたのを-Encoding asciiに修正しました。PowerShellのOut-Fileの引数であるutf8はBOM付きutf-8を指しています。BOM付きで保存されたservices-config.jsonを使用した場合、Unityを動かしたときにライセンス認証に失敗してしまうため、BOM無しで保存するように修正する必要がありました。BOM無しutf-8で保存する方法は幾つかあるのですが、今回はjson内でascii範囲外の文字を使っていないのでasciiで保存する事で代用しています。(無いとは思いますが)日本語ドメイン等を使っていたらutf-8で保存しなければならないので頑張ってください。PowerShell 6以上であればutf8NoBOMが使えるのですが、自分が試したときのバージョンは5.1.20348.558でした。

※2022年5月2日追記 Unityのモジュールのインストールが上手く行っていなかったため修正、またWindowsのIL2CPPビルドを行う際にVisualStuidoのSDKが必要だったため、そのインストールプロセスを追加しました。Unityのインストール時にvisualstudioをインストールするオプションもありますが、そちらはパイプライン上ではうまく動作しませんでした。

コンポーネントができたら、それを基にイメージレシピを作成します。

設定項目 設定した値(参考)
ベースイメージ Windows Server 2022 English Full ECS Optimised x86の最新バージョン
作業ディレクトリ C:/
コンポーネント 直前に作成したビルドコンポーネントをアタッチ
ストレージ gp2 50GiB(終了時に削除)

インフラ設定のVPC等は一旦ネットに繋がれば十分ですが、きちんとテストしたい場合は実際に動かす環境を利用するといいかもしれません。ビルド時に利用するIAMロールには、AmazonSSMManagedInstanceCoreEC2InstanceProfileForImageBuilderポリシーのアタッチが最低限必要です。(必要に応じて追加してください。)

イメージレシピ、インフラ設定ができたらそれを基にパイプラインを作成しましょう。スケジュールは今の所AMI作成まで自動化する予定はないので今回は手動に設定しました。ディストリビューションはデフォルトで問題ありません。

パイプラインが完成したら、アクション>パイプラインを実行する でamiをビルドする事が出来るようになります。もしコンポーネントの設定ミスでビルドに失敗しても、パイプラインの詳細からCloudWatchに記録されたログを確認することが可能です。

AMIが出来上がったら、前述したterraformの設定ファイルにAMI IDを設定して構築を行いましょう。 OSSの説明書通りにAWS側のインフラ構築、及びGitHub Appを作成してください。

次回予告

これでUnityを実行するための環境が整いました。次回(またしても日付未定)は、今回作ったランナーと前回作ったライセンスサーバーを接続して、GitHub Actionsのコードを書いてビルドを行っていきます。