見出し画像

Now in REALITY Tech #88 アセット開発体制拡大を支えるCI/CD環境改善

こんにちは!
先日開催されたGREE Tech Conference 2023での登壇が無事に終わり、ホッと一息ついているIKEPです。
(REALITYのエンジニア以外にもグリーグループ各社の方々が数多くの技術的チャレンジについて発表していますので、ぜひご覧ください!発表資料と動画が公開されております!)


さて、REALITYでは最近ガチャやショップのアセット開発に関わる人が多くなり、新しいアバターアイテムをより多くのユーザのみなさまにお届けできるようになっていると思います。
また、ルーム機能がリリースされ、家具ガチャのアセット開発も行われています。
しかしこういったアセット開発の増加、拡大に伴い、エンジニアとしては技術課題も増えてくるわけであり、課題解決をしていかないと開発効率が下がってしまいます。
そこで今回は、アセット開発をより効率的に進めれるようにCI/CD環境改善を行ったので、この取り組みについてご紹介します!
(今までのREALITYのアセット開発フローはこちら↓)


1. AssetBundleのビルドジョブの並列分散化

背景

アセット開発では、Unityへの組み込みが完了後、実機確認のためにAssetBundleのビルドを行う必要があります。
REALITYのAssetBundleのビルドは、オフィスに物理的に設置しているJenkinsビルド専用マシンによって行なっています。
このJenkinsビルドが遅いという問題がありました。
色々調査した結果、ビルドマシンスペックやビルド処理自体よりも、「Jenkinsジョブの実行待ち時間が長い」ことがボトルネックとなっていました。
例えば、アセットA => アセットB => アセットCのようにビルドしようとする状況を考えると、iOS/Androidで計6つのジョブを実行する必要があります。

今まではビルドキューに乗ったこれらをただ順番に実行する設定になっており、当然、待ち時間が長くなるので、Jenkinsへのビルド依頼から完了までが遅くなります。

iOS/Androidビルドの並列化

REALITYでは、iOSとAndroidでJenkinsジョブが別であるため、ビルドマシンのローカルに2つのUnityプロジェクトがあります。
AssetBundleのビルド処理ではUnityを起動する必要がありますが、iOS/AndroidそれぞれのJenkinsジョブを同時に実行しても2つのUnityプロジェクトが起動する形になり、Unityの起動で競合することがありません。

そこで、iOSとAndroidのビルドを並列で実行できるようにしました。
並列実行は、iOSとAndroidのビルドを呼び出す親Jobで、Pipeline scriptで以下のような感じで書くことで実現できます!
(Pipeline scriptではScripted PipelineとDeclarative Pipelineがありますが、これはDeclarative Pipelineの書き方です)

pipeline {
    // nogeにしておくことで子のJobで実行するノードを制限できる
    agent none

    stages {
        stage('parallel'){
            // `parallel`で囲まれた部分が並列実行される
            parallel {
                stage('iOS'){
                    steps {
                        build job: iOSBuild, parameters: [
                            // ~ビルドパラメータ~
                        ]
                    }
                }
                stage('Android') {
                    steps {
                        build job: androidBuild, parameters: [
                            // ~ビルドパラメータ~
                        ]
                    }
                }
            }
        }
    }
}
実行するとこんな感じに2つのJobが動いているのがわかる

複数ビルドマシンでの分散ビルド

今までAssetBundleのビルドは1台のみで行なっていたところを、複数台のマシンで分散してビルドできるようにし、さらに効率化しました。
iOS/Androidそれぞれのジョブで以下の設定をしていきます。
(PluginとしてThrottle Concurrent Builds を使うと、複数ビルドマシン全体で並行実行数を制限することができたりと便利になるのでこれを利用しています)

これで複数台のマシンでジョブを分散しつつも、iOS/Androidのビルドが並列で行えるようになりました!
仮にiOS/Androidのビルドが15分ずつかかるとすると、アセットCのビルド依頼から完了までの時間が、今までは15分 * 6ビルド = 1時間半だったところが、30分で完了するようになります!!

「test1」がiOS, 「test2」がAndroidだとすると、右のような状態になっている

2.アセット開発者自身がAssetBundleビルドできる環境の整備

背景

AssetBundleのビルドは、今までは全てのビルドをエンジニアが行なっていました。
冒頭の記事でも紹介されていますが、

まとめ担当Art「◯◯ガチャシリーズの開発が完了しました」

アセット担当ENが、開発ブランチから、開発環境共通アセットブランチへのマージとAssetBundleのビルドを実施
アセット担当EN「開発環境でAssetBundleのビルドが完了しました、iOS, Androidともに実機で確認が可能になりました」

というやりとりが行われており、実機確認で問題があった際には再度このやりとりが行われます。
しかし、アセット開発の増加、拡大に伴い、非常に多くのメンバーから依頼が飛んでくるようになったので、対応コストも無視できないものになります。
また、エンジニアも別作業を行なっており、すぐに対応できないこともあるため、その間Artチームとしては作業が止まってしまうことになり、非効率な環境であると言えるでしょう…。

なぜエンジニアがビルドする必要があるのか?

REALITYのガチャアイテムの開発ブランチは以下のようになっており、開発環境のAssetBundleのビルド前に、このブランチツリーに沿って共有ブランチassetbundle-labまでマージしていく必要があります。
また、マージ最中にコンフリクトが起こる可能性もあるため、コンフリクト解消の対応も必要です。

Artチーム用のビルドツールの開発

逆に言えば、ブランチツリーに沿ったマージとコンフリクトの解消ができれば、エンジニアがビルドする必要はないということです。
そこで、これらを自動化しつつ、AssetBundleのビルドをslackから行えるツールを開発しました。

Slack botに対してビルドしたいアセットが含まれたブランチ名を指定したコマンドを送信するだけ

slack botで実行を検知し、ブランチツリーに沿ったマージ処理を行うJenkinsジョブを実行後に、AssetBundleビルドのジョブを実行しています。
マージ処理はshell scriptでgitコマンドを順次実行させる形になります。
このマージ処理の中にコンフリクト検知とコンフリクト自動解消機能を持たせています。

コンフリクト検知は、一旦マージを実行し、ログを確認するという泥臭い方法になっていますw

#!/usr/bin/env bash
# 現在のブランチに指定したブランチをマージすることでコンフリクトが発生するファイル一覧を調べる

CONFLICT_MESSAGE="Merge conflict in "

# 一旦マージを実行して結果を確認
MERGE_RESULT=`git merge --no-commit $1`

# コンフリクトしたファイル一覧を抽出
CONFLICT_FILE_NAMES=`echo "$MERGE_RESULT" | grep "${CONFLICT_MESSAGE}" | sed "s/^.*${CONFLICT_MESSAGE}//g"`

echo "$CONFLICT_FILE_NAMES"

# マージ検出のみをしたいのでマージ処理そのものはなかったことにする
RESET_RESULT=`git reset --hard`

コンフリクトの自動解消については、マスタデータなどのよくコンフリクトが発生するものに限定しています。
Unityをバッチモードで起動し、C#で実装しておいたマスタデータの再書き出し処理を実行しています。

for conflictFile in $CONFLICT_FILE_NAMES; do
    # コンフリクト自動解消に対応したファイルなら、ログだけ出しておいて後で処理させる
    if printf '%s\n' "${AUTO_CONFLICT_RESOLVE_TARGET_FILES[@]}" | grep -qx $conflictFile; then
        echo "${conflictFile} is allowed conflict"
    # コンフリクト自動解消できないファイルがコンフリクトしている場合はエラーにして終了
    else
        exit 1
    fi
done

# それぞれのファイルのコンフリクトを解消を行なっていく
for conflictFile in $CONFLICT_FILE_NAMES; do
    UNITY_CLI="/Applications/Unity/Hub/Editor/2022.2.5f1/Unity.app/Contents/MacOS/Unity"

    # コンフリクト自動解消の対象ファイルがコンフリクトしていたら、
    if [ $conflictFile = $MASTER_FILE_A_PATH ]; then
        # Unityを起動して、マスタの再書き出しをすることでコンフリクト解消
        "${UNITY_CLI} \
        	-executeMethod ${EXECUTE_METHOD} \
        	-projectPath ${PROJ_PATH} \
            -nographics \
            -batchmode \
            -logfile \
            -quit"
    elif [ $conflictFile = $MASTER_FILE_B_PATH ]; then
        # その他コンフリクト解消の手段を用意して実行してあげる
    fi

    # コンフリクト解消したファイルをadd
    git add $conflictFile
done

git commit -m "Jenkinsによるコンフリクト自動解消"

こうすることで、エンジニアはブランチツリーをどう構成し、どの順番でマージするかだけを管理すればよく、Artはアセットの開発サイクルを早く回せるし、エンジニアは自分の作業に集中することができます!

こんな感じでJenkinsの環境変数設定に直書きしちゃってるので、もう少しいい感じにしたいが…

実際このツールの開発前で、自分の作業が立て込んでいる時は1日~3日も待たせてしまっていた時もありましたが、今では(並列分散ビルドも相まってですが)最短10分前後で実機確認が可能になっています!

まとめ

  • REALITYのアセット開発が拡大していく中でこういった改善活動は「縁の下の力持ち」状態ですが、こういった課題をこまめに解決していかないと、将来の自分たちの苦労が増えるだけなので、頑張っていきたいです!

  • slackからのビルドとかは実は、ビルド依頼を頼まれていた僕自身がラクになるようにと思って作り始めたものですが、他のエンジニアやArtチームなど多くの人にも「便利だ!」と使ってもらえたので、なかなか嬉しかったです…!