はじめに
個人ゲーム開発で困ることといえば、巨大な容量を誇るリポジトリのバージョン管理ですよね。皆さんどうしてますか?
色々なオプションがあるかと思いますが、一番格安かつデータ消失などにも強い方法として自分はAPIGateway+Lambda+S3の組み合わせでGitLFSサーバーをサーバーレス運用しています。
この方法だと、S3の代金だけでほぼ運用できるので実質無料みたいなものです。
因みにGitHubのGitLFSは50GB単位で月5ドル、Unity Plastic SCMは5~25GBで5ドル、以後25GBごとに5ドルかかります。
アクティブなリポジトリ一本ならいいですが、過去の作品を放置しておくにはランニングコストが気になってきますね。
APIGateway+Lambda+S3を使ったGitLFSサーバーの構築は、このあたりの記事でやり方が紹介されています。自分も数年前まで、このあたりで紹介されているCloudFormation Templateを使って環境を構築していました。
が、しかし記事内で使われているLambdaのランタイムがdotnetcore2と既にサポートされておらず、提供されているLambda関数もdllファイルなので自分でアップデートすることは困難で現在は使えません。
Amazon S3 に Git LFS サーバを超簡単に立てる - Qiita
Git LFS サーバーを Amazon S3 に立ててみた - デニッキ!
そのため、今回GitLFSサーバーを実装して、誰でも環境構築できるようCloudFormation Templateを作成しました。
他人のブログをひっぱってきてる記事を鵜呑みにせず、ちゃんとソースにあたりましょう。
不要な車輪の再発明をしてしまった・・_:(´ཀ`」 ∠):
元ネタ記事はこちら。
でせっかくなので今回自作したGitLFSサーバーを使って簡単に環境構築ができるCloudFormationを用意したので紹介したいと思います。
目次
構築手順(かんたん)
AWSの環境さえあれば構築はめちゃくちゃ簡単です。
1. CloudFormationでスタックを作る
以下のリンクをクリックして、そのままスタックを作成すると必要な物は全て生成されます
GitLFSサーバーの環境構築はこれで完了!
完了すると、「出力」のタブにLFSサーバーのAPIエンドポイントが吐き出されてますので、控えておいてください。
(重要)また、リソースタブから論理IDが「RestApiKey」のものを探し、隣の物理IDのリンクをクリックしリソースに遷移します。そこで、APIキーを表示し、このキーも控えておいてください。
2. リポジトリにGitLFSの設定を追加する
GitLFSはインストールされていますか?されていなければ、しておきましょう
git lfs install
Git LFSを適用させたいリポジトリに.gitattributes
ファイルを設置します。.gitattributes
の書き方は本記事のスコープ外なので詳しくはググってください。
*.png filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text
ここから本題。
まず、Git LFSサーバーのエンドポイントを以下のコマンドで先ほどCloudFormationによりデプロイされたOutputのURLに書き換えます。
git config -f .lfsconfig lfs.url アウトプットされたURL
次に、上記LFSサーバーにリクエストをする際、APIキーをヘッダーに付与するよう設定を追加します。
git config http.アウトプットされたURL.extraHeader "x-api-key: (1)で控えたAPIキー"
これで全ての設定が完了しました。
3. 動作確認をする
最後に動作確認をします。リポジトリに適当なpngやjpgファイルをコミットし、プッシュしてみてください。
もしプッシュ時にlock云々で怒られたら以下のコマンドを実行してください。本サーバーは現時点ではlock機構は実装されていません。
git config 'lfs.アウトプットされたURL.locksverify' false
プッシュが完了すると、デプロイされた際に作られたS3バケットに当該ファイルが何かしら上がっているはずです。ContentTypeがoctet-streamになっているので、一見画像には見えませんが。
細かい話。
GitLFSサーバーの実装
上記のCloudFormation Templateでデプロイされるのは、今回自分が実装した簡易的なGitLFSサーバーです。
GitLFSサーバーの実装とてもシンプルで、最低限動かすためにはBatchAPIとBasic TransferAPIを処理できれば問題ありません。
Batch API
BatchAPIはLFSでアップロード、ダウンロードしたいオブジェクトのメタ情報がまとめてリクエストされるので、それぞれのオブジェクトに対応するアップロード用URL、ダウンロード用URLをレスポンスとして返すエンドポイントになります。
仕様はこちら git-lfs/batch.md at main · git-lfs/git-lfs · GitHub
Basic Transfer API (Upload)
デフォルトでBasic Transferが利用されます。
ファイルのアップロードでは、上記で返したアップロード用URLにPUTリクエストでバイナリデータ(Content-Type: application/octet-stream)がつらつらとストリームで流れてきます。
これを任意のストレージにアップロードすればOKです。
今回の実装はとてもシンプルで、S3のアップロード用PresignedURLを発行してそれを返すだけでOKです
https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html
仕様はこちら
git-lfs/basic-transfers.md at main · git-lfs/git-lfs · GitHub
Basic Transfer API (Download)
こちらも同様、BatchAPIで返したダウンロードURLに対してGETリクエストが飛んでくるので、アップロードされたバイナリデータをストリームで返せばOKです。
こちらもS3のダウンロード用PresignedURLを発行してそれを返すだけでOKです
仕様はこちら
git-lfs/basic-transfers.md at main · git-lfs/git-lfs · GitHub
GraalVMによるネイティブイメージのビルド
JVM言語 + API Gateway + Lambda構成の課題
今回、サーバーはScalaで書いています。
が、JVMは起動が遅いため、LambdaのようにJVMを立ち上げた状態を維持できない環境では多数のリクエストを捌かなければならない場合、この点が問題となります。いわゆるJVMのコールドスタート問題です。
王道のアプローチ?はAWSの公式に記載があります。
AWS Lambda のコールドスタートパフォーマンスの最適化 - AWS SDK for Java
個人利用しているGitLFSのAPIなんて頻繁に叩かれるものでもないので今回は別にどうでもいいんですけど、せっかくなのでこの問題にアプローチしてみようと思いました。
GraalVMというアプローチ
この問題に対する1つのアプローチとして、GraalVMのnative-imageコマンドを利用したネイティブ実行ファイルを生成する、という方法があります。
JVMではJITコンパイラにより、実行時に実行環境に応じてコンパイルが行われます
一方、GraalコンパイラはJITコンパイルすることもできますし、それに加え、事前に全てのコンパイルをおこなっておくAOTコンパイルを行うこともできます。後者の場合、ネイティブ実行ファイルが生成されるため実行時のVMは当然不要で、初期実行速度も高速になるわけです。
その代償として、当然環境に応じて事前にコンパイルし、ネイティブイメージを作成しておかないといけません。
Goのように簡単にクロスコンパイルできればいいんですけど、なかなか難しいようです
GraalVMによるネイティブ実行ファイルの生成
自分はmacを利用していますが、Lambdaのカスタムランタイム環境はAmazon Linux2です。
故に、mac環境で作成したネイティブ実行ファイルは動作しませんし、linux用にクロスコンパイルもできません。
そこで、今回はGitHubAction上でlinux環境を用意してネイティブイメージを生成するようにしました。
また、ネイティブイメージの作成にはいくつか制約事項があります。
例えば、動的クラスローディングやリフレクションなどは場合によっては、構成ファイルを事前に作成しておかないといけないことがあります。
Native Image Compatibility and Optimization Guide
結果は?
比較してないのでわかりません٩( ᐛ )و ネイティブ実行ファイルが生成できて動いた所で満足してしまった。
また別の機会に検証記事を出せたらいいですね。