SyntaxHighlighter

2020年8月3日月曜日

nginx と Flask を使った Git LFS サーバー

nginx と Flask を使った Git LFS サーバー

Git LFS サーバーを自前で用意する必要が生じたのですが、手頃なものが見つからなかったので、手作りで作ってみることにしました。
Git LFS サーバーの仕様自体はシンプルで、とりあえず動作させるぐらいであればすぐに作れたので、今後のために記録を残しておこうと思います。

また、今さらですが Docker にも慣れておきたかったので、 Docker のイメージにしてみました。本来は docker-compose を使うようなケースのような気もしますが、とりあえず単一の Docker コンテナにしてあります。

なお、作成したサーバーのソースコードなどは、 gitlfs_with_nginx として GitHub 上に置いてあります。

Git LFS サーバー

Git LFS は、 Git で大きめのバイナリファイルを管理するために GitHub が中心となって策定した Git の拡張機能です。

概要としては、以下のようになるかと思います。

  • あらかじめ指定したファイルを、 Git リポジトリではなく、別に指定した Git LFS サーバーに保存する。
    ファイルは、ファイル固有の oid (現状ではファイルの SHA256 値) と紐付けて、 Git LFS サーバー内で Key-Value ストアのように保存される。
  • 元の Git リポジトリには、実体となるファイルの oid などが書かれたポインターファイルのみを保存しておき、 checkout などの際に、適宜 Git LFS サーバーからファイルの実体をダウンロードしてくる。
  • これらのファイルの保存やダウンロードの動作は、 Git のフックなどで実現されており、ユーザーはほとんど意識することなく、通常の Git リポジトリで作業する感覚で作業することができる。

ちなみに、私は Git LFS を最初に使ったとき、「Git LFS サーバーで管理されているファイルだけ、 Subversion のようなタイミングでダウンロードされるんだな」と感じました。

それはともかくとして、 Git LFS サーバーは比較的シンプルな仕様の HTTP サーバーです。
Batch APIBasic transfer をサポートするだけであれば、(どこまで異常系を考えるかによりますが) それほど悩まずに実装することができると思います。

Flask と nginx による Git LFS サーバー

そこで、 Git LFS サーバーの理解を深めるのを兼ねて、 Python の Flask フレームワークを使って実装してみました。
また、 nginx をリバースプロキシとして使うことで、パフォーマンスを向上させています。
以下に概要のみ、記します。 これらの処理は lfs_server.py に書かれています。

batch エンドポイント (batch メソッド)

Batch API の仕様に従って、 download, uploadoperation 要求に対し、指定された oid のファイルの実体をダウンロードもしくはアップロードするための URL を返します。

このとき、ファイルの実体をダウンロード/アップロードする際の GET/PUT リクエスト時に送信してもらうヘッダを同時に返します。
このヘッダには認証用のデータなどを含めることができます。
今回の実装でも、リポジトリ名や認証期限などを blake2b でハッシュをとったものをヘッダに追加しています。

ダウンロード用エンドポイント (download_file メソッド)

ダウンロードは、基本的に nginx に処理を任せます。
Flask 側では認証処理をしたあと、 X-Accel-Redirect ヘッダを指定し、 nginx 側で静的なファイルを配信するように構成することで、 Flask 側でファイル配信しなくてすむようにしています。

具体的には、 X-Accel-Redirect ヘッダに /repos/repository_name/oid のようなパスを指定することで、 nginx から静的なファイルを配信できるようにしています。

アップロード用エンドポイント (upload_file メソッド)

アップロードも、基本的に nginx に処理を任せます。
nginx 側で client_body_temp_path, client_body_in_file_only などを使うことで、クライアント側から PUT されてきたファイルを、 nginx がファイルに保存するようにします。
そして、そのファイル名を Flask 側に渡すことで、 Flask 側では渡されたファイルを oid で一意に決まる所定の場所に移動させます。
Flask 側はファイルの移動を行うだけなので、処理の負荷が少なくてすみます。

Docker での実装

上記のような仕組みの Flask サーバーの実装や nginx の設定を行い、 Docker イメージとして構築してみました。

env.list としていくつかのパラメータは変更できるようになっており、社内利用ぐらいであれば、 Basic 認証を追加するぐらいで使えなくはないのではないかと思います。

0 件のコメント:

コメントを投稿