【Unity】ゲーム開発の現場でなぜJenkinsが利用され続けるのか

はじめに

QualiArtsのカレンダー | Advent Calendar 2022 - Qiita、19日目担当の鈴木光です
私はバックエンドエンジニアですが、クライアントのビルド周りも携わるので今回はゲーム開発現場におけるJenkinsについてのお話をさせていただきます

Jenkinsについて

皆さんご存知の通り、Jenkinsは非常に汎用的なCI/CDのプラットフォームです。Jenkins自体はJavaで実装されたOSSのソフトウェアなので基本的に自前でホストする必要がありますが、それゆえに構築してしまえばベンダーロックインされることなく多くの現場で活用することが可能です
最近はGitHub Actionsを筆頭にCircle CI, Gitlab CI, AWS Code BuildやGoogle Cloud Buildなど様々なマネージドCIサービスが業界を席巻していますが、露出の少ないゲーム系のテックカンファレンスなどを見ると未だにJenkinsはよく登場しています。おじいちゃんはまだまだ現役なのです

Jenkinsの欠点

特にWeb系の方々からすると「まだJenkinsなんて使っているの?」という感想ではないでしょうか。かくいう自分もその一人で、入社した時はほうぼうでJenkinsが利用されていて驚いた記憶があります。なぜマネージドサービスを利用しないのかと
実際、QualiArtsのバックエンドエンジニアも以前はサーバアプリケーションのCI/CD基盤としてJenkinsを採用していましたが、最近ではほとんど使っていません。もっぱらGitHub ActionsかGoogle Cloud Buildで完結しています(一部CD基盤にArgoCDを利用していたりしますが、CI基盤としてはマネージドサービスがメインです)。この理由は皆様と同じでしょう、Jenkinsにはいくつか欠点があります

  • マネージドサービスではないため自前でマシンやソフトウェアを管理する必要がある
  • Jenkins自体がプラグインにとても依存する。プラグインのバージョンについていけなかったり、アップデート作業をするとJenkinsやジョブが壊れる。そして直近のアップデートができないのでその次のアップデートもできなくなり化石になっていくという悪循環
  • ジョブや設定がコード化されていない。コード化するプラグインを使ったとしてもやはりそこだけで全ては完結しない(そのプラグインのインストールは?)。さらには馴染みのないGroovy

Jenkinsを利用していた時代もこのあたりを解決するためFabric(GitHub - fabric/fabric: Simple, Pythonic remote execution and deployment.)を使ったり、あまりJenkins側で複雑な設定をしなかったりと工夫していましたが、近年のマネージドサービスはこのあたりを解決してくれるため移行しています。またJenkinsは多岐にわたる機能を持ちますが、我々が行いたかったのは自動テストや単純なビルド・デプロイ作業であったため高度な機能が必要とされなかったというのもあるかもしれません

Unityのビルド

さて、QualiArtsでは主にUnityを用いてゲーム開発を行っており、バックエンドとは違ってクライアントでは引き続きJenkinsを利用しています。具体的にどのようなことがJenkinsによって自動化されているのかは1日目の記事(Unity開発の現場でJenkinsがしていることの紹介 | QualiArtsエンジニアブログ)を参照していただくとして、大別すると「アプリのビルド」と「アセットのビルド」の2つに分けられます。他にも細かいジョブはありますが、そちらは実験的にGitHub Actionsへ移行しています。逆に言うと、これらのジョブは移行することが出来ないわけです。なぜでしょうか?詳しく解説していきましょう

2種類の"巨大な"ビルドとキャッシュシステム

アプリのビルド

「アプリのビルド」から解説していきましょう。こちらは単純明快、クライアントのソースコードからアプリケーションをビルドし、ipaやapkといった実行ファイルを出力するジョブです。これがLinuxマシンで稼働する一般的なサーバアプリケーションであればJenkinsは不要だったでしょう。しかし、ここでビルドのターゲットになるのはiOSAndroidです。特にiOSのビルドにはmacOSを用いてしかビルドできないという制約があります。したがって、この時点でマネージドサービスであったとしてもかなりサービスを選びます。アプリケーションのビルドにはそこそこのマシンパワーも必要ですのでマネージドサービスにすると費用もかさみます。ビルドジョブには手動で行われるものの他にも1時間に1度など定期的に行われるビルドもあるため、1日のうちそこそこの時間マシンリソースが専有されているというのも従量課金制と相性が悪いです
このため、QualiArtsではMac Proを社内にホストしてJenkinsエージェントをインストール、クラウドVM上に構築されたJenkinsコントローラーと接続してジョブはMac Pro上で実行するという形式をとっています

VMMac ProにあるJenkins

アセットのビルド

Unityには画像や動画といったアセットを外部へ切り出すためにアセットバンドルというシステムがあります。Unityがアセットを読み込むためにプラットフォームごとの変換処理やアセットの圧縮などを事前に行い、アセットをメタデータと一緒に外へ切り出すことで、ランタイムで動くアプリケーションに対して動的にコンテンツを配布できるという仕組みです。Unityで開発された運用中のサービスにおいて、新しく追加されるガチャやDLCコンテンツなどがアプリ更新なしに行われているなら十中八九アセットバンドルを使っていることでしょう
このビルド処理にはかなりのマシンリソースが必要で、おろし金と呼ばれる弊社のMac Proを用いても数十分はかかる処理になっています。ただ実はこの処理、ビルドターゲットがiOSでもビルドマシンがmacOSである必要はないのでAWSのスポットインスタンスのようなLinuxVMを用いてコスト最適化しつつクラウド化することは可能です(過去にそのようなプロジェクトは実在しました)

JenkinsのメリットとUnityビルドにおけるキャッシュ

今までの話だけだと「Macが使えるマネージドサービスならコストを一定かければJenkinsじゃなくて良いの?」ということになるかもしれません。アセットのビルドなんて「macOSじゃくても良いならJenkinsである必要ある?」となることでしょう。しかし、なかなかそういうわけにもいかないのです
先程、Jenkinsのデメリットについてお話しました。逆に我々がJenkins使い続ける究極のメリットを紹介しましょう。それが"ジョブごとにワークスペースが維持される"というものです
一般的に、CIは毎回クリーンな環境で行われる方が良いとされています。そうしないと冪等性や再現性が担保されないためです。変に以前の状態が維持されている環境でCIを実行してしまったがために、想定していたプログラミング言語のバージョンやライブラリの依存関係がおかしくなって挙動が変わるということは容易に想像できます。ただ、これは毎回クリーンな環境で実行したCIが現実的な時間内で終了することが前提です。1テストするのに10時間かかっていたら開発も進みません。しかし、上記2つのビルドではそれが起こるのです
ここでアプリのビルドについて触れてみます。Unityには Library というディレクトリがあり、色々なキャッシュが詰まっています。これがあることでエンジニアは快適な開発体験を享受できます。 Library ディレクトリは数GBあり、Unityは Library ディレクトリ自体やその中のキャッシュデータが不足しているとそれを起動時などに生成しますが、数GBのキャッシュデータを生成するのですから当然時間がかかります。さらにUnityのプロジェクトは大きくなりがちです。VCSからチェックアウトするだけでもかなりの時間がかかります。さらにさらに、アプリのビルドに必要な埋め込みのアセットをSVN, Git LFS, Plastic SCM, Perforceといったアセット用VCSからチェックアウトすることも必要です。これらの処理を毎回するのでしょうか。帯域の問題もありますが、何より時間がかかります。開発中のアプリのビルドは速ければ速いほど検証やQAといったイテレーションを回せるのでビルド時間というのはとても重要な項目です
また、多くのマネージドCIサービスではジョブが並列に走ることが前提です。サービス上にキャッシュの仕組みが用意されているとはいえ、バージョン管理されたコードを並列にビルドしつつ、それを適切にキャッシングするのはなかなか骨が折れるのではないでしょうか。Jenkinsはエージェントごとに並列数も絞れますし、そもそもジョブはキューイングされるので直列のビルドです。以前のジョブで使われていたワークスペースはそのまま維持され、次のジョブでも利用されるため Library ディレクトリの生成やデータのチェックアウト処理は短時間で完了します
これがそのままアセットのビルドにも関わります。アセット用VCSからチェックアウトする必要があるといったことを先程お伝えしましたが、アプリ埋め込みのアセット量などたかが知れています。本当にヤバいのはアセットバンドルとして外部へ切り出されるリソースたちです。タイトルの規模や対応プラットフォームにもよりますが、例えばQualiArtsのIDOLY PRIDEではこれらは全てのプラットフォーム合わせてデータ量が数十GBあります。アセットバンドルは圧縮されているので生のアセットデータは100GB近くあったりします。この容量になるとマネージドサービスのキャッシュシステムの上限を超えたり、そもそもキャッシュへストア・リストアする作業自体にも時間がかかります。例えばGitHub Actionsでは actions/cache というワークフローを用いてデータのキャッシュを実現できますが、キャッシュのデータサイズには10GBの上限がありますし、ログを見る感じ内部的には恐らく分散ストレージに対してキャッシュデータをダウンロード・解凍・圧縮・アップロードといった処理をしています。"ジョブごとにワークスペースが維持される"というのはこの問題に対して非常に効果を発揮します。アセットバンドルのフルビルドは本来丸一日レベルのジョブですが、Jenkinsが持つこの特性のおかげで数十分で済んでいます。これがゲーム開発の現場で良くJenkinsが使われている理由です
(ちなみにGitHub Actionsのセルフホステッドランナーはワークスペースを維持しますが、ワークスペースがジョブごとではなくエージェントごとに維持されます。共有ディレクトリをマウントするなどすればジョブごとにすることも可能かもしれませんが、その工夫の維持管理にもコストがかかりますし、どちらにしろ後述する問題があることにより移行は厳しいかなという所感です)

ワークスペースが維持されることでキャッシュになる

ビルド時間以外のメリット

実はビルド時間以外にもJenkinsにはメリットがあります。それは従量課金でないことと、UIがわかりやすいというものです
我々の現場ではJenkinsのジョブを実行するユーザーがエンジニアであるとは限りません。クリエイターやプランナーなど多様な職種のユーザーがJenkinsのボタンをポチっと押します。仮に素晴らしいキャッシュシステムを構築し、Jenkinsから脱却できるぞと意気込みながらGitHub Actionsに完全移行した場合は、次にこの問題が立ちはだかることでしょう。そう、Jenkinsはユーザー数によってお金がかかるということはありませんが、GitHubはユーザー数による従量課金制なのです。QualiArtsが所属するCyberAgentグループは全社的にGitHub Enterprise Cloudを導入していますが、料金表を見るとユーザーあたり21ドルかかります。しかもこれが日割りだとは思えません。最低でも月ごと、あるいは年ごとになるでしょう。ゲーム開発の現場は人員の流動性も激しく、開発人数も多いです。ソーシャルゲームの開発であっても100人を超える現場は珍しくありません。当然全員がJenkinsを扱うわけではありませんが、必要な人に絞ったとしても利用するなら2週間のインターン生や3ヶ月のヘルプで来ていた人のためにGitHubのアカウントを1ヶ月分・1年分契約したりする必要があるということになります。仮にJenkinsの頃と比べ20人追加でGitHubアカウントが必要になり、それが年契約だとすると5040ドルです。追加でかかるにはちょっと看過できない金額ですね
加えて、Jenkinsは(あまり役に立ちませんが)ジョブの残り時間、進捗やキュー状況などが直感的なUIで表示されます。GitHub Actionsを利用したことがある方ならわかると思いますが、エンジニアにはわかりやすくても非エンジニアにはちょっとわかりにくい作りをしているのではないでしょうか?定期実行されたジョブの確認方法や手動実行するためにはどのボタンを押せば良いかなどの操作方法のドキュメントを書いたり、問い合わせの対応をしたりして工数が発生するならなんのためにGitHub Actionsへ移行したのかわかりません

JenkinsにおけるダッシュボードのUI
GitHub Actionsにおける手動トリガーのUI

まとめ

今回はゲーム開発の現場でJenkinsが利用され続ける理由について解説しました。近年では色々な記事やカンファレンスでマネージドCIサービスのことが話され、Jenkinsから脱却したというテーマを聞くことも珍しくありません。ゲーム系でもなんとかしてJenkinsを脱却したという他社さんの取り組みの記事を見ることが増えてきましたが、やはりマネージドMacマシンのスペック不足やキャッシュのための巨大なディスクをネットワークマウントした際に起きる実行速度の問題など課題が残っているという話もされています。バックエンドの人間としてはあまりJenkinsをホストしたくないものですが、現状のゲーム開発現場においてJenkinsは非常に素晴らしいソリューションです。とはいえGitHub Actionsが最適な部分もあるので今後もJenkinsと併用していきたいと思います。もしこのあたりの課題がまるっと解決された、いい感じのCIソリューションがあれば教えていただけると幸いです