こんにちは、エンジニアの岡村です。
このブログで何度か紹介してきましたように、弊社ではGitHub ActionsとAWSでUnityのCIを回しています。
Unityもコマンドで動かす事が出来るのでCIへの組み込み自体は可能なのですが、Unityは一般的なプログラムと異なり多くのグラフィックアセットを扱うのでリポジトリサイズや処理負荷がどうしても高く、CIの時間も伸びがちです。その為、UnityのCIではキャッシュが重要です。
一般的なUnityを使った開発において、Unityをローカルマシン上で動かしてプロジェクトフォルダを使いまわしている場合はLibraryフォルダ内に様々なキャッシュが入っている為に高速で起動・ビルドが可能です。しかし、弊社のCI環境ではUnityをEC2のスポットインスタンス上で動かしている為、プロジェクトフォルダは毎回削除されてしまいます。これは環境依存要素を排除する観点では利点なのですが、代償としてビルド時間が延びる問題、GitHub LFS帯域の問題が発生していました。
ビルド時間に関してはマシンスペックを上げることである程度解決しています。GitHub ActionsにCI環境を移行してからはUnity Acceleratorも併用しており、30GBを超えるプロジェクトではありますが、一番長いAndroidビルドでも1時間以内には終わる様になっています。並列化も併用している為現時点ではビルドが詰まる状態にはなっていません。
Unityプロジェクトでは大量の画像や音声、3Dモデル等のデータを扱っている為、それらはLFSで管理しているのですが、GitHubではLFSデータのダウンロードの際、転送するファイルサイズに応じて課金されます。金額自体はそれほど高いわけではないのですが、CIが活発化すると実行の度にチェックアウトが走るために一気に利用量が増え、制限に引っ掛かってしまい、その度にデータパック(GitHub LFSの課金単位)を購入する必要が出てしまいます。繁盛期にはキャッシュを使わない状態だと2~3日で追加購入が必要になるという事態になっていました。
- ビルド時間の問題(軽微)
- GitHubのLFSストレージ帯域がもりもり消費される問題
この帯域の問題の解決の為に、まずはGitHubがデフォルトで持っているキャッシュの仕組みを利用する事にしました。
GitHub公式のキャッシュ機能
GitHubにはキャッシュの仕組みがあり、各リポジトリに10GB(2022年11月17日時点)のキャッシュ専用ストレージが付与されています。このストレージからの読み出しには課金されず、用量上限に達したり一定期間利用されなかったキャッシュは自動で削除されます。
弊社環境では本体リポジトリのLFSに加えて、upmパッケージ化した外部アセットや汎用アセットでもLFSを活用していたため、LFSフォルダ及びupmパッケージのローカルキャッシュをGitHub上でキャッシュする様に設定しました。
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: lfs: false - name: Get LFS file catalog run: "git lfs ls-files -l | cut -d' ' -f1 | sort | uniq > .lfs-files" shell: bash - name: Cache LFS files uses: actions/cache@v3 with: path: .git/lfs key: lfs-files-${{ hashFiles('.lfs-files') }} restore-keys: | lfs-files- timeout-minutes: 10 continue-on-error: true - name: LFS pull run: git lfs pull - name: Cache packages uses: actions/cache@v3 with: path: Library/PackageCache/ key: packages-${{ hashFiles('Packages/packages-lock.json') }} restore-keys: | packages- timeout-minutes: 10 continue-on-error: true # 以降にUnityのビルド処理を書く
GitHub公式のキャッシュ機能の問題点
まず、10GBのキャッシュストレージの容量制限が全然足りませんでした。前述の例ではキャッシュ内容をLFS及びパッケージに限定していましたが、それでも弊社プロジェクトにおいてはそれぞれ1~3GB(圧縮済)の大きさになっていました。そこに他のブランチでのキャッシュ等を含めると容易に制限を超えてしまい、すぐにキャッシュミスが発生するようになってしまいました。このキャッシュストレージはお金を払って拡張することが出来ません。
また、大容量のキャッシュをダウンロードする際に、ダウンロードが途中停止し、そのままタイムアウトになってしまう現象も発生していました。
- キャッシュストレージが1リポジトリ当たり10GBしかなく、拡張もできない問題
- 大容量のキャッシュをダウンロードする際、途中でダウンロード速度が0になり、そのままタイムアウトする問題
このような問題が発生していた為、キャッシュを別の方式に変更することにしました。
tespkg/actions-cacheの採用
兎にも角にもキャッシュストレージが足りないので、Amazon AWS S3ストレージにキャッシュを保存することにしました。S3にキャッシュを保存するActionはMarketplaceにいくつかあるのですが、その中でも新が割と頻繁に行われていて、動作が安定しており、使い方がGitHub公式のアクションとほぼ同じであるこちらのアクションを採用しました。
- name: Cache packages uses: tespkg/actions-cache@v1 with: region: ap-northeast-1 bucket: cicd-caches accessKey: ${{ secrets.AWS_CACHE_ID }} secretKey: ${{ secrets.AWS_CACHE_KEY }} path: Library/PackageCache/ key: packages-${{ hashFiles('Packages/packages-lock.json') }} restore-keys: | packages- timeout-minutes: 10 continue-on-error: true
tespkg/actions-cache
を動かすには、キャッシュの保存先となるバケット(例ではcicd-caches
)と、保存及び取り出しの権限を持ったIAMユーザーを作成し、以下のようなポリシーをアタッチしてやる必要があります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucketMultipartUploads", "s3:ListBucket", "s3:DeleteObject", "s3:GetBucketLocation", "s3:ListMultipartUploadParts" ], "Resource": [ "arn:aws:s3:::cicd-caches/*", "arn:aws:s3:::cicd-caches" ] } ] }
また、キャッシュはライフサイクルルールを設定して一定期間で削除するようにすると、ストレージの容量が際限なく消費されることを防げます。
結果
今回紹介したActionに乗り換えた結果、キャッシュ容量が足りずにLFSからダウンロードし直しになる事がなくなりました。また、同じリージョン内のEC2を使ってビルドしているおかげか、キャッシュのダウンロード/アップロード速度も上がった印象です。おまけに公式のActionでは発生していた、ダウンロードが途中で止まってタイムアウトする現象も発生しなくなったので、動作を安定させることが出来ました。
- キャッシュストレージ上限が無くなった
- ダウンロード/アップロードが150%くらい早くなった
- キャッシュダウンロード中に速度が0になってそのままタイムアウトする現象が無くなった
諸々の心配事が解決したので、今後はキャッシュで保存する範囲を広げる事を視野に入れてもいいかもしれません。ビルド時間は今の所それ程問題にはなっていませんが、それでも短くなるに越したことはないです。最初に話した環境依存要素という観点もあるので少しずつ試す形にはなりますが。
という所で今回の記事は終わりです。読んでいただきありがとうございました!