JKになりたい

何か書きたいことを書きます。主にWeb方面の技術系記事が多いかも。

APIGateway+Lambda+S3で格安GitLFSサーバーを運用する【使い方の紹介と車輪の再発明_:(´ཀ`」 ∠):】

はじめに

個人ゲーム開発で困ることといえば、巨大な容量を誇るリポジトリのバージョン管理ですよね。皆さんどうしてますか?

色々なオプションがあるかと思いますが、一番格安かつデータ消失などにも強い方法として自分は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を作成しました。

・・・が、自作したあと気づいたんですけど、元ネタの記事はちゃんとアップデートが続けられていて、記事執筆時点でdotnetcore6のランタイムで動くものが公開されています。

他人のブログをひっぱってきてる記事を鵜呑みにせず、ちゃんとソースにあたりましょう。

不要な車輪の再発明をしてしまった・・_:(´ཀ`」 ∠):

元ネタ記事はこちら。

alanedwardes.com

でせっかくなので今回自作したGitLFSサーバーを使って簡単に環境構築ができるCloudFormationを用意したので紹介したいと思います。


目次

構築手順(かんたん)

AWSの環境さえあれば構築はめちゃくちゃ簡単です。

1. CloudFormationでスタックを作る

以下のリンクをクリックして、そのままスタックを作成すると必要な物は全て生成されます

https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fsimple-git-lfs-template.s3.ap-northeast-1.amazonaws.com%2Ftemplate.yml&stackName=test-lfs

GitLFSサーバーの環境構築はこれで完了!

完了すると、「出力」のタブにLFSサーバーのAPIエンドポイントが吐き出されてますので、控えておいてください。

(重要)また、リソースタブから論理IDが「RestApiKey」のものを探し、隣の物理IDのリンクをクリックしリソースに遷移します。そこで、APIキーを表示し、このキーも控えておいてください。

RestApiKeyの物理IDのリンク

APIキーを表示する画面

2. リポジトリにGitLFSの設定を追加する

GitLFSはインストールされていますか?されていなければ、しておきましょう

git lfs install

Git LFSを適用させたいリポジトリ.gitattributesファイルを設置します。.gitattributesの書き方は本記事のスコープ外なので詳しくはググってください。

以下の例はjpgとpngLFS管理する設定です。

*.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サーバーです。

github.com

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のように簡単にクロスコンパイルできればいいんですけど、なかなか難しいようです

github.com

github.com

GraalVMによるネイティブ実行ファイルの生成

自分はmacを利用していますが、Lambdaのカスタムランタイム環境はAmazon Linux2です。

故に、mac環境で作成したネイティブ実行ファイルは動作しませんし、linux用にクロスコンパイルもできません。

そこで、今回はGitHubAction上でlinux環境を用意してネイティブイメージを生成するようにしました。

github.com

また、ネイティブイメージの作成にはいくつか制約事項があります。

例えば、動的クラスローディングやリフレクションなどは場合によっては、構成ファイルを事前に作成しておかないといけないことがあります。

Native Image Compatibility and Optimization Guide

結果は?

比較してないのでわかりません٩( ᐛ )و ネイティブ実行ファイルが生成できて動いた所で満足してしまった。

また別の機会に検証記事を出せたらいいですね。