Amazon Web Services ブログ

Unityモバイルアプリのビルドパイプラインを実装する

インタラクティブコンテンツの生成にゲームエンジンが使われる事は昨今では珍しくなくなりました。ゲームエンジンの一つである Unity はスマートフォンで動くモバイルアプリケーションはもちろん、コンソールや PC ゲーム、またリアリティショウ都市のシミュレーションなどメタバース領域にも利用されています。

利用用途が多様化するに伴い各プラットフォームに応じた Unity アプリケーションをビルドする必要が生じます。この様に複数の Unity アプリケーションを効率的にビルドする為、各社で独自のビルドパイプラインを構築する事で自動化とビルド時間の短縮を実現しています。

Unity ビルドを AWS 上で行うメリット

ビルドパイプラインは無停止で動き続け、また開発のフェーズに合わせてキャパシティを柔軟に増減させられる事が求められます。AWS はこれらの要求に応えられる柔軟性・拡張性を持っており、最適なキャパシティをお客様で指定する事が可能です。それに加えAWSでは多くのセキュリティサービスが利用でき、これらを活用する事でシステムの信頼性を高め、お客様の制作物を保護します。

また AWS for Games では Unity のビルドパイプラインに関するリファレンスアーキテクチャを公開しており、Workshop に則る事で Amazon Elastic Compute Cloud (EC2) スポットインスタンスMac インスタンスを活用し、Jenkins により自動化された iOS アプリケーションのビルドパイプラインを構築可能です。

今回私たちは Workshop に含まれた人手を介する作業を可能な限り減らし、自動化を進めた AWS CDK のサンプルを作成しました。ここには Unity のキャッシュサーバなど、より実運用に即したサービスをデプロイ出来る機能も追加されています。

アーキテクチャ

GitHub に本アーキテクチャを実装したサンプルを公開しています。Unity Build Pipeline with Jenkins and EC2 Mac

このサンプルには以下が含まれます:

  • ECS Fargate 上の Jenkins マスター
  • EC2 Linux spot インスタンスと EC2 Mac インスタンス上の Jenkins エージェント
    • Linux 上では Docker エージェントも利用可能
  • EC2 Linux の Unity Accelerator (on Docker)
  • ビルドキャッシュを継続的に暖気するための仕組み (後述)
  • AWS CDK によるほぼ自動化されたデプロイ

ご利用の AWS アカウントにデプロイしてすぐに試せるようになっていますので、ぜひご確認ください。

アーキテクチャ図

また、実用的には Unity のライセンスを管理する仕組みも合わせて必要かもしれません。このため、Unity のフローティングライセンス管理サーバーを AWS 上に構築する実装および構築手順も合わせて公開しています。こちらもぜひご確認ください: Unity Build Server with AWS CDK

設計・実装上の考慮点

本アーキテクチャを実現する上で、いくつかの考慮点があります。以下にまとめます。

EC2 インスタンスのコストを抑える方法

EC2 Mac インスタンスは、最小課金時間が24時間 (2023年2月現在) であり、静的な負荷のワークロードにより適したソリューションです。負荷が動的に変動する場合、EC2 Linux インスタンスを組み合わせて活用することで、さらなるコスト最適化が可能です。以下に本ソリューションで実装したアイデアを示します。

EC2 Linux インスタンスは、最小課金時間が1分であることやスポットインスタンスが使えることから、比較的柔軟かつ安価に利用可能です。Unity のビルドジョブに関する処理の多くは Linux 上でも実行できるため、それらを Mac インスタンスからオフロードすることで、トータルのコストを抑えることができます。

インスタンスの使い分け

 

例えば iOS 向けクライアントのビルドにおいては、Xcode を用いたビルドは Mac 上で実行する必要がありますが、それ以外のアセットインポートや Xcode プロジェクトの出力処理は Linux 上の Unity で実行可能です。このような処理を Linux インスタンスにオフロードし、動的なスケールイン・アウトによりキャパシティを最適化することで、トータルのコストを抑えつつビルド時間も高速に保つことができます。

下図では、全て Mac でビルドする場合 (Case1) と、Linux+Mac を組み合わせてビルドする場合 (Case2) の所要時間を図示しています。同じ Mac インスタンスの台数であれば、Case2 の方が Linux インスタンスで一部処理を並列化できる分、速くビルドができます。同じ速度を Case1 で達成するためには Mac インスタンスの台数を増やして並列化する必要がありますが、これはコストの増大に繋がるため理想的ではないでしょう。一方で Linux インスタンスはより効率的かつ安価にキャパシティを増減できるため、ビルド速度とコストの最適化に繋がります。

インスタンスを使い分けるほうが時間もコストも得

大容量のビルドキャッシュを保持する

EC2 をオートスケールさせる場合、すべてのインスタンスはステートレスです。つまり、ビルドジョブ間でデータは共有されないことがあります。インスタンスの中には一時的にビルド後のキャッシュが保持されていますが、スケールインやスポット中断によりインスタンスが終了されれば、キャッシュは消えてしまいます。このためステートレスな EC2 上で単純にビルドした場合、従来のオンプレミス上の基盤よりもキャッシュの恩恵を得づらく、ビルド速度が低下してしまう可能性があります。

例えば Unity ビルドにおいては、キャッシュすべきファイルの代表例として以下の3つが挙げられます:

  1. リポジトリ: バージョン管理システム管理下の、ソースコードやアセットの生ファイルが含まれます
  2. Library ディレクトリ: Unity がインポートしたアセットのキャッシュが含まれます
  3. ビルドの出力先ディレクトリ: ビルドの生成物が含まれます

これらはすべてローカルのファイルシステム上に保存され、リポジトリの pull や Unity ビルドを実行する際に参照されます。いずれも差分を処理する形になるため、前回のビルドで利用したファイルがあればビルド速度は著しく向上します。これこそ、キャッシュが重要な理由です。

キャッシュがステートレスなインスタンスで揮発する問題は、AWS の機能を活用することで解決可能です。以下に2種類の方法を紹介します。

1. AMI でキャッシュを共有・更新する

一つ目の方法は、Amazon Machine Image を活用するものです。

Amazon Machine Image (AMI) とは EC2 インスタンスを新しく起動する際に指定する情報で、ストレージである Amazon EBS のスナップショットなどが含まれます 。AMIは、すでに起動しているインスタンスを元にして作成できます。そのインスタンスの EBS をスナップショットしてコピーし、AMI として保存するのです。この AMI をキャッシュとして継続的に更新し各ビルドサーバーで使い回すことで、ステートレスなインスタンス群に対してビルドの高速化に必要なキャッシュを保持させるというアイデアです。

ami概要

今回はすべての Unity ビルドが Linux エージェント上で実行されることを想定しています。このため、キャッシュの管理も Linux エージェントについてのみ考えます。もし何らかの事情で Mac インスタンスでもキャッシュを考慮する必要がある場合も、基本的には同じく AMI を用いた方法が流用可能です。

今回のシステムでは、Linux エージェントの EC2 インスタンスは Auto Scaling Group (ASG) で管理されています。ASG は Desired capacity に合わせて自動的にインスタンスを増減させますが、新しくインスタンスを起動する際は ASG の LaunchTemplate で指定された AMI を用いて起動します。つまり Linux エージェントの使う AMI を変更するには、ASG および Launch Template の設定を更新すれば良いです。

ASGがAMIを参照するイメージ

ここまでをまとめると、新しく起動する Linux エージェントがローカル上のキャッシュが保持された状態で起動するには、次の処理を実行すれば良いです:

  1. ファイルシステム内にキャッシュが存在する状態のインスタンスから、新しく AMI を作成する
  2. 作成された AMI を使うように Launch Template を更新する
  3. 更新された Launch Template を使うように Auto Scaling Group を更新する

ami snapshot

更に、一連の処理を定期的に実行することで、キャッシュを新しく保ち続けることもできます。

上記の方針を具体的に実装する例を紹介します。実用上は、以下の点を考慮する必要があるでしょう。

  1. AMI を作成する際は、作成中に対象の EC2 インスタンスを再起動する必要がある。再起動せずに AMI を作ることもできますが、スナップショットの整合性が保証されないため非推奨です。
  2. ASG に属さないインスタンスで AMI を作成する必要がある。上記の理由でインスタンスが再起動される際、ASG に属するインスタンスはインスタンスが ASG により途中で終了されてしまう可能性があるため。
  3. AMI を作成中のインスタンスでは、作成中にビルドジョブが実行されてはならない。Unity のビルドジョブが実行中に AMI を作成すると、キャッシュに不整合が発生する可能性があるため 。
  4. AMI 作成中にSpot インスタンスの中断が発生し、作成が失敗する可能性がある。このため、リトライの仕組みは必須。

公開したサンプル実装には、上記を考慮した実装例を含めています。ぜひ実際の実装もご確認ください。

ただし、この方法の問題として、AMI からインスタンスが起動した場合は EBS Snapshot のファイルが遅延読み込みされるため、起動直後のビルド時に I/O レイテンシーの増加が観測されることがあります。この問題は Fast Snapshot Restore (FSR) の機能を使うことで解決可能で、起動直後からレイテンシーの低下なしにファイルアクセスが可能になります。詳細はこちらのブログもご覧ください。

2. EBS ボリュームのプールを利用する

上記の AMI を使った方法では、インスタンス起動直後の I/O パフォーマンス低下が無視できません。FSR を使うことで解決はできますが、コストの増加FSR Credit の管理など新たな関心事が生じます。

また別の方法として、EBS ボリュームのプールを作成する方法も利用できます。

まず、Amazon Elastic Block Store (EBS) ボリュームを複数個作成します。そして ASG で管理された Jenkins エージェントの EC2 インスタンスが起動する毎に、このボリュームを1つアタッチし、またインスタンスが終了するときはデタッチします (ボリュームを削除しないことに注意してください)。このボリュームの中には、Jenkins の workspace を配置します。Jenkins の workspace には Git リポジトリや Unity の Library ディレクトリ、ビルドディレクトリが含まれます。

上記により、EBS ボリュームの中のデータにはキャッシュさせたいデータが全て含まれ、新たに起動する EC2 インスタンスはキャッシュが手元にある状態で起動することができます。また EBS Snapshot とは異なり、データは S3 ではなく常に EBS ボリューム上に存在するため、起動直後に I/O レイテンシーも増加することもありません。FSR の管理も不要です。

EBSボリュームプールの概要図

プール内の EBS ボリューム数は、どう決めれば良いでしょうか?まず、ボリューム数は Jenkins Agent のフリート (ASG)の最大キャパシティに合わせると良いでしょう (それよりも少ない・多い場合にどうなるか考えてみてください)。そして ASG の最大キャパシティは、ビルドのキューが十分な速さで捌ける程度に確保する必要があります。最大キャパシティを増やすとキューが詰まるリスクを減らせますが、EBS ボリュームを確保する分静的なコストが増大するというトレードオフがあります。このトレードオフを考慮して、ユースケースに応じた最適なキャパシティを設定してください。

EBSプールを作成する方法についてもサンプルの実装に含めていますので、ぜひご確認ください。また、上の図ではインスタンスがボリュームを上から順番に使うように書かれていますが、これでは特定ボリューム内のキャッシュが偏って古くなる可能性があります。これを避けるには、インスタンスがアタッチするボリュームをランダムに決める、乱択処理を実装すると良いでしょう。

上記2つの方法のいずれかを使えば、ビルドキャッシュをステートレスなインスタンス間で保持・共有することができます。またこれ以外にも S3 や EFS に適宜キャッシュをアップロード・ダウンロードする方法なども考えられるでしょう。それぞれの方法にメリット・デメリットがあるため、ユースケースに合わせて比較検討してください。

まとめ

この記事では Unity のビルドパイプラインを AWS で構築する例をご紹介しました。この CDK サンプルを用いる事で、AWS を用いた Unity のビルド環境をより迅速に構築することができるようになります。

iOS / Mac ビルドが必要な場合も、Linux の Spot インスタンスを活用することでコストパフォーマンスを向上できます。また、これまで Spot インスタンスをビルドに利用する際、ローカルキャッシュが消される事でアセットビルドに時間を要する状態になるという課題がありましたが、今回のサンプルではその課題を解決する仕組みも提案しています。

この様に Unity の持つ機能と AWS の機能を組み合わせる事で、より可用性の高いサービスを構築出来る事も AWS の強みです。ぜひ公開されたサンプルをお試しいただき、フィードバックをお寄せいただければ幸いです。

Special thanks: この記事の執筆には Game Solutions Architect の長田・藤原に協力いただきました。