HN 日本語サマリー

← 一覧へ戻る
インフラ・DevOps

オブジェクトストレージバケットにGitを「話す」方法を教えた

I taught a bucket to speak Git (tigrisdata.com)

19 pointsby xena1 コメント

要約

この記事は、筆者がオブジェクトストレージバケットをGitリポジリのバックエンドとして機能させる実験について詳述しています。`go-git`ライブラリと`billy`ファイルシステム抽象化レイヤーを組み合わせることで、従来のファイルシステムに依存しないGitサーバーを構築することに成功しました。これにより、Gitのオブジェクトストアとしての性質を活かし、クラウドネイティブ環境でのGitホスティングの課題を解決する可能性を探っています。

全文翻訳

What happens if I just point a git server at an object storage bucket?以前、エージェントサンドボックスをGoに移植していたとき、Go用のファイルシステム抽象化であるbillyの上にすべてを構築しました。このプロジェクトの秘訣は、Tigrisバケットに、シェルインタープリターとそのツールが区別できないほどファイルシステムのように振る舞うことを教えることでした。Billyは、この全体的な見せかけを機能させるための重要な層でした。うまくいった後、私はbillyを通常のユースケースから大きく逸脱して使用していることを知りました。これは元々、Gitのプロトコルとデータ形式の純粋なGo実装であるgo-gitのために作られました。/usr/bin/gitバイナリの存在には全く依存していません。billyのファイルシステムインターフェース上のすべてのメソッドは、go-gitがそれを必要とするためにのみ存在します。これが私に恐ろしいアイデアを与えました:私はすでにファイルシステムのように鳴るバケットを持っており、go-gitのネイティブ言語は「ファイルシステム」です。これはJust Work™するのだろうか?調べてみましょう。 Gitは常にオブジェクトストアだった 表面的な部分を取り除けば、Gitリポジトリは4つの基本的なものです: オブジェクト、または圧縮されたデータのブロブ。個々のリポジトリのほとんどのオブジェクトはファイルです。 ツリー、または他のオブジェクトにマップするオブジェクト。TL;DR: ツリーはフォルダーです。 コミット、または1つのツリーとその親コミットを指すオブジェクト。これにより、どのファイルが1つの論理的な変更セットに属するかを特定できます。 リファレンス、ブランチ、タグ、これらはオブジェクトの山の中の小さな可変ポインタです。 注:この記事に取り掛かるまで、Gitは空のフォルダーに対するパッチのみを保存し、それによってリポジトリの履歴を再構築していると私は思っていました。それは違います。実際にはファイル全体を追跡しており、これが大きなバイナリブロブがツールを非常に混乱させる理由を説明しています。diffのメンタルモデルは日常的にGitを使用する上では問題ありませんが、この投稿が扱うストレージ層では間違っています。 例えば、新しいGitリポジトリを作成し、README.mdをコミットしたとしましょう。.gitフォルダーのツリーはこんな感じになります: $ tree .git.git ├── COMMIT_EDITMSG ├── config ├── HEAD ├── index ├── objects │ ├── 5e │ │ └── b8151eb669aa4467b6dea2c4bce19183cd0b41 │ ├── 6a │ │ └── 6a8ecfcae2632152486aca3d9150ef83dedd66 │ ├── f4 │ │ └── d2487a1c6d742c8037c0296ddf80625190bd80 │ ├── info │ └── pack └── refs ├── heads │ └── main └── tags ご覧のとおり、3つのオブジェクトがあります。そのうちの1つはコミット5eb8151eb669aa4467b6dea2c4bce19183cd0b41、次はツリー、最後はREADMEファイルです。mainブランチもそのコミットを指しています: $ cat .git/refs/heads/main 5eb8151eb669aa4467b6dea2c4bce19183cd0b41 クールな点は、この半分がコンテンツアドレス指定されていることです。コンテンツアドレス指定された部分は、一度コミットされると変更されることはありません。Gitオブジェクトは、Tigrisが構築されている基本的なモデルと同様に、追記専用ストレージであるため、Tigrisの内部モデルに非常によく適合します。頻繁に変更されるのはrefsであり、これらは最新のコミットを指すように更新されます。しかし、これらは小さなファイルなので、Tigrisは手間なく処理できます。 しかし、Gitリポジトリをサーバーでホストすると、単一障害点が生じることになります。私たちのGitリポジトリは、故障する可能性のある単一のマシンでホストされています。Gitオブジェクトがファイルシステムオブジェクトと1対1で相関しているという実装全体は、全員(GitHubでさえも)Gitバイナリにシェルアウトして実際にファイルを保存しているためです。Gitリポジトリのホスティングは、ステートレスなクラウドネイティブ環境において最もステートフルなサービスの一つとなっています。確かにGitは理論上は分散型ですが、私たちのほとんどは、疑わしい稼働時間慣行を持つ一つの大きなストア(GitHub)にGitリポジトリを置くためにそれを利用してきました。GitHubのハブユーザーに対して公平に言えば、GitHubは私たちには想像もできない規模で運用しています。彼らは設立以来、負荷を処理するためにEngine Yardにもっと大きなサーバーを構築してもらう必要があったなど、限界を押し広げてきました。Gitのツールは他の選択肢を与えないため、彼らはすべてを大きなマウントされたファイルシステムで行う必要があります。 人間の理解を超える恐怖の茶番 さて、この奇妙さがあなたを動かすほど気になるとしましょう。すべてのものをローカルファイルシステムに保存せずにGitサーバーを構築するには、何らかの方法でGitを話す必要がありますが、従来の選択肢はそれほど優れていません: Gitバイナリにシェルアウトすると、「ライブラリ」はGitプロセスのargvになり、エラー処理は画面スクレイピング出力になります。Gitは内部的に、すべての機能をライブラリとして公開するのではなく、数え切れないほどのサブコマンドで実装しています。コードベースは、プロセスを強制終了するdie()への依存によってかろうじて維持されています。 libgitを使ってGitの内部にリンクすると、「問題が発生したらdie()」という動作を継承し、アプリが突然ランダムにクラッシュし始めます。これは稼働時間にとって良くありません。 libgit2(ライブラリとして再書き込みされたもの)を使用しようとすると、GPLに悩まされている(リンク例外付きで、これを弁護士に説明してみてください)、Gitで何かをするたびにCへのジャンプをしなければならない(非常に頻繁に)、開発が停滞している、Goバインディングがアーカイブされている、そして保証に反してローカルファイルシステムを依然として仮定している、という事実に直面する必要があります。絶望的に聞こえるかもしれませんね?WebAssemblyなどを使ってこの混乱を抑え込むことができるかもしれませんが(fork()/exec()やposix_spawn()などを実装する良い方法があるとして)、これらすべてを処理できる純粋なGoライブラリがあったとしたらどうでしょうか? go-gitの登場です。これは、Gitプロトコルと内部をゼロから実装した純粋なGoライブラリです。これはcgoや/usr/bin/gitに依存せず、リポジトリがローカルファイルシステムに保存されていることを前提としません。そのストレージインターフェースは、私がすでにTigrisを話すように教えた正確なインターフェースであるbillyに対して記述されています。私はバケット内にあるだけのGitサーバーを望んでおり、その部品はそこにあり、私を呼んでいました。 ああ、うまくいった そこで私はobjgitをハックしました。これはオブジェクトストレージをバックエンドとするGitサーバーです。起動するために追加しなければならなかった唯一のファイルシステム呼び出しはMkdirAllでした。私はトランスポートパッケージをソケットに接続してプレーンテキストのGitプロトコルを実装し、バケットに接続し、現在作業中のリポジトリをプッシュしました。私の絶対的な驚きは、それが機能したことです。Gitはプッシュ、プル、ログ、ブレーム、タグ、すべてができました。私はGitを自分で実装する必要はありませんでした。ただ、四角い杭を丸い穴に押し込むという途方もない量の作業を、杭が入るまで続けただけでした。 今にして思えば、これは腹立たしいほど理にかなっています。ベアリポジトリはファイルシステム上のこれら4種類のものです。ファイルシステムをオブジェクトストレージに置き換えれば、その他すべてがJust Working™するのは完全に論理的です。Gitのディスク上のフォーマットは、そのデータベーススキーマであり、open/stat/renameを十分に説得力を持って偽装すれば、APIは私たちが夜中に眠れるように自分自身に語る嘘なので、全体的な見せかけは機能し続けます。 多くのハッキングの後、私はこのような機能リストを手に入れました: HTTP、従来のgit://、SSHの3つのトランスポートでのプッシュとプル 最初のリポジトリプッシュ時にアップサートされる これは実験であり、認証は面倒で複雑なので、認証には全く手間をかけていない ファイルシステム層を最適化するためのPrometheusメトリクス すべてがローカル状態のない一つのGoバイナリから出力され、生成されたSSHキーさえもバケットに保存される スマートなGitクローンを実行する際に一時ファイルが必要となる楽観的キャッシュとしてのみ可変ストレージが必要なKubernetesクラスターでこれを実行できる この投稿の残りの部分は、「ああ、うまくいった」から実用に近いものにするために何が必要だったかです。 免責事項(人生の最高のもののようです):これは実験です。徹底的なテストや正確性の検証はされていません。もし半分に壊れても、両方の破片はあなたのものです。あなたの会社のモノレポをこれに移行して、火災が発生したときに私にメールを送らないでください。 生き残ったPOSIXのイディオムの一つ Gitは永続性について非常に神経質であり、その全体的な戦略は、あなたが何度も目にする一つのUnixイディオムです。