2013-03-26

KDEレポジトリ消失問題の記事の全訳:完璧過ぎるミラー

Too Perfect A Mirror - Me, my blog, and my Johnson

完璧過ぎるミラーと題して、今回のKDEのレポジトリが危うく消失しかけた障害について書いている記事があるので、参考のために全訳する。ただ、私にはgitの知識がないため、あくまで参考程度に。

(追記)

以下のテキストは公開時より書き換えられてはいないが、我々のバックアップ方法や失敗原因などの詳細に関する疑問に答えるために追記した。もし以前にこの記事を読んで、「おい、なんでバックアップ取ってねーんだ」と思ったならば、追記を読むといい。

当初公開した記事で説明し忘れたことがある。我々はレポジトリのtarballは持っている。tarballは数日おきに作成しているが、これは完璧なバックアップというわけではない。より詳しくは記事中で説明する。

これは、あやうくKDE大災害2013になりかけた今回の事件について振り返るために書いている。即座に書かれたブログ投稿、git.kde.orgが落ちたや、git.kde.orgが復帰、現状報告などで、事件の内容についてはすでに語られている。

以下が、事件の全容だ。

「なんじゃこりゃ?」

2013-03-22に、git.kde.orgの仮想マシンをホストするサーバーが、セキュリティアップデートのために落とされた。サーバー上で走る仮想マシンはどちらも問題なくシャットダウンされ、セキュリティアップデートがホストに適用され、マシンがリブートされた。

ホストが復帰してVMを起動した時、VMはすぐにファイルシステムが壊れている兆候をみせた。問題のファイルシステムはext4であった。ファイルシステムの問題が長期にわたって蓄積していたのか、ホストやVMの今回のシャットダウンやリブートに関連してのものなのかは、まだ分かっていないし、おそらくは永久に判明しないだろう、前者であることを伺わせる証拠があるものの、はっきり断言できるほどではない。

この文章を読んでいる者の多くが知っての通り、KDEには複数のanongitマシンがあり、その目的は、1500個ものgitレポジトリにかかるアクセスを分散してさばき、またメインサーバーのバックアップとしても機能している。しかし、anongitを確認したところ、すべてレポジトリが壊れており、ほとんどはレポジトリ自体がなくなっていたのだ。

何故こんなことが起こったのか。

完璧なミラー

あらゆるソフトウェアに言えるように、我々のミラーリングシステムにもバグがあった。そして、大方のバグに言えるように、問題が起こるまで存在に気がつかなかったのだ。

バグの原因は設計上の欠陥だ。git.kde.orgは常に信頼できる一次ソースであるという前提。この前提の理由は明白だ。git.kde.orgはしっかりと管理されており、プッシュされたコードはすべて妥当性を検証するよう独自のフックをかましていたのだ。常に正しいと仮定することは妥当な決定だった。

そして、ミラーリングシステムは、それほどの時間差なく、git.kde.orgと全く同じように振る舞うべく設定されていた。これはgitレポジトリのコードのみならず、管理上の多くのメタデータまでにも適用される。同期はおよそ20分ごとに発生し、ミラーリングシステムはanongitをgit.kde.orgと全く同じようにみせかけ、一次ソースが死んでいたり交換されたりしたならば、ミラーからレポジトリを同期するように設計されていた。

我々は、サーバーがディスクを紛失したり、あるいはサーバー自体が塵になったり、VMのファイルシステムが完全に消失した場合には備えていたものの、ファイルシステムの障害には備えていなかった。この障害の具合が、ミラーリングシステムに予期せぬ問題を引き起こしたのだ。

事象発生順序

  1. VM復帰。全レポジトリを格納するプロジェクトファイルが破損。
  2. anongit同期開始。同期のために、anongitは新しいプロジェクトファイルを取得したが、これは、興味深いことに、それぞれのanongitごとに違ったものとして見えていた。もちろん壊れていることには変わりないが。壊れ方というのが、プロジェクトファイルから、ほとんどのレポジトリが削除されたかのように見えていたのだ。
  3. 各anongitは、サーバーから削除されたレポジトリ(これは通常も起こりうる妥当な操作である)をローカルから取り除くため、プロジェクトファイルに入っていないローカルのレポジトリをすべて削除した。
  4. 原因はまだよくわかっていないが(おそらくは一部のanongitの同期間隔が他のものより頻繁だったのだろうが)、一部のanongitがレポジトリを再クローンし始めた。壊れたものをクローンしたのだ。これについては後ほど。

というわけで、障害は完璧にミラーされてしまった。いや、というより、結果的には、不完全にミラーされたという事だ。anongitの全データは失われてしまった。

ラッキーラッキーラッキーラッキーラッキーラッキーラッキーラッキー

ツイていた。

projects.kde.orgをホストし、anongitとも同期している(ただしユーザー向けではない)サーバーが、Hetznerデータセンターにあり、三年前から静的IPv4アドレスのブロックを専有していた。Hetznerは最近、IPv4アドレスの枯渇のため、IPv4アドレスブロックの利用には結構な額の利用料を徴収し始めていた。ハードウェアもだいぶ古くなったことだし、この機会に新しいより良いハードウェアで、しかもコストも同額のものにprojects.kde.orgを移行させようとしていた。

この事件が起こるちょうど一日前のこと、anongitのクローンシステムが、移行のための新しいサーバーを立ち上げていた。幸運はこれだけではない。この一台のサーバーは、ちょうど20分ごとに発生する同期すべき時刻にあたっていたのだが、たまたまサーバーのリブートが重なっていたのだ。その結果、最新のプロジェクトをフェッチするコマンドはタイムアウトし、渡されたスクリプトは、単にサーバーからレポジトリの最新リビジョンのフェッチを試みたが、これはサーバーが妥当なcustom packを返さなかったために失敗していたのだ。

本来ならば常時4,5個の完全なKDE Gitレポジトリのコピーが存在すべきはずなのに、git.kde.orgとその他のすべてのanongitが完全に死滅した中、この新しい一台のサーバーのみが、唯一オリジナルの1500のレポジトリのコピーを残していたのだ。

我々はすべてのレポジトリに対してgit fsckを走らせ、すべてを検証した。我々は安堵とともに、このレポジトリから、通常はanongitのメタデータ同期に使うスクリプトを使って、メタデータを含むgit.kde.orgを復旧できたのだった。

すべてが簡単に解決したわけではない。我々のgitoliteレポジトリも壊れてしまった。独自の変更を施した時代遅れのGitoliteを復旧するより、この機会に前々から必要とされていた、とっくの昔に行うべきだったアップグレードを行うつもりだ。これにより、有益な機能が追加されたナイスなGitoliteのバージョン3を提供できる。ユーザー向けのコマンドに、いくつか文法上の変更がある。この詳細については別のブログ投稿で書くつもりだ。

特に、cloneコマンドは現在移植されていない。このことについては後ほどGitoliteの作者Sitarama Chamartyと話し合って、移植をスムーズに行えるようにしなければならない。

Gitoliteについてもうひとつ、緊急のGitoliteの復旧中に、いくつかのトラブルに見舞われた。Sitaramは最優先で我々を助けてくれた。彼はKDEの素晴らしき友であり、ここに謝意を表する。

復旧

さて、問題については分かったので、解決しなければならない。残念ながら、解決はそれほど簡単ではない。

簡単な変更もある。すぐに行ったこととしては、プロジェクトファイルにチェックをかけるようにした。もし、新しく生成されたプロジェクトファイルが、以前のものより1%以上違っていた場合は、以前のファイルも保持するようにした(1500ものレポジトリがある環境では、これは3分以内に15個ものレポジトリを新たに作成するか削除するかしなければならない。これは通常ありえないことである)。このチェックはanongitにも適用する必要があるが、それは長期的な変更の一貫として検討中だ。

大きな問題は、障害の検出だ。プロジェクトファイルは大幅に変わらなかったとしても、ちょっとした障害で、たとえばすべてのanongitからふたつのレポジトリが削除されたりする。たとえ検出して再クローンしたとしても、anongitは壊れた方のクローンを所持することになる。つまり・・・

Gitは思ったほど安全じゃない

(追記:この項目について執筆した後、Git開発者達は、私がテストに使っていた手法について検証した。その結果、私のやり方に誤りも発見された。--no-localのかわりに--no-hardlinksを使っていた。これはふたつの挙動の混同に起因する誤用である。そういうわけで、この項目の情報は完全に正しいというわけではない。とはいえ、疑いなく問題が発生したのに、gitがcloneでexit code 0を返す場合もあるということだ。詳しくはこちらを参照。元の文章は以下の通り)

Gitはかなり安全である。しかし、問題のある操作を行えてしまうこともある。これは、通常のユーザーやシステム管理者には、一見問題のないようにみえる。私はちょっとしたテストを行い、なぜanongitによる再クローンが壊れたのかについて調べた。以下がその内容である。

Corruption of Commit Objects

commit objectが壊れても、何の警告もなくレポジトリをミラークローンできてしまう(しかもexit codeはゼロだ)。そしてツリーを辿ろうとすると、いずれコミットが壊れているエラーに突き当たる。しかし、こ個々が重要なところだが、エラーになるのは、その問題のコミットを含むツリーをたどった時だけだ。何もロケット工学を持ちだしてまで調べるほどじゃない。これが問題になる理由は明白であるが、これはつまり、妥当性を検証したい場合は、ツリーのすべてのrefを辿らなければならないという事だ。

リーナス・トーバルズのGoogle Tech Talkの発表から引用する。

ディスク障害があろうと、メモリー障害があろうと、どんな問題があろうとも、gitなら検出できる。疑念の余地はない。保証されているのだ。悪意ある人間がいようとも、決して成功することはない。たったの20バイト知っていればいい。ツリーを表す160ビットのSHA-1さえ知っていれば、上から下までの完全な記録としてツリーを信頼できる。10年分の記録があろうと、100000個のファイルがあろうと、何百万のリビジョンがあろうと、すべてを信頼できるのだ。なぜならば、gitはとても信頼性があり、基本的なデータ構造はとてもとても単純だからだ。チェックサムすらチェックしている。

リーナスは正しい。ツリーをたどって上から下までの全履歴を信頼できる。ただし、Gitレポジトリは巨大なrefを含むことがあり、その場合はすべての検証しなければ、レポジトリ全体を信頼することはできない。

git fsckで問題を検出できる。

Corruption of Blob Objects

これも問題は同じだが、ひとつ重要な違いがある。この状況では、レポジトリのミラークローンが警告なしに通ってしまうが、ツリーを辿ろうとすると、最初のコミットまでさかのぼるrevlistnにぶちあたる(このブロブをみるのはref/treeだけだ)

これも、git fsckで問題を検出できる。

Mirror clones

ミラークローン(--mirrorフラグをつけてクローンすること)は、クローンをupstreamのレポジトリのrefまで含めて最新にすることである。新しいrefはdownstreamのレポジトリにpullされ、forced updateまで含むすべての変更が、downstreamレポジトリにミラーされる。

しかし、どうもミラークローンはクローンとは別の仕組みらしく、custom packではなく、オブジェクトが単に愚直にコピーされるらしい。その結果、ミラークローンはレポジトリの安全検証を素通りしてしまう。upstreamでの障害はdownstreamの障害になり、exit codeはゼロだ。

将来に向けて

もちろん、KDEシステム管理チームは現在、この災害を将来回避するための方法について議論している。

真先に施された改良としては、anongitはごく最近の障害に備え、24時間前の同期結果を保持するようにしたことだ。これにより、レポジトリを相当最近のリビジョンまで復旧できる。projects.kde.orgの移行先のマシンはZFSファイルシステムを使っており、同期するたびにスナップショットをとる。スナップショットの数は十分な量確保される。これにより、障害からごく近い時点まで復旧できるはずだ。

しかし、これはどちらも付け焼刃的な対応だ。だいぶ前にサーバーで発生した障害は、anongitに伝播するのに十分な時間があり、新しい変更で古い変更を上書きしてしまったり、何らかの理由で全消ししてしまったりする。同期するレポジトリの数を、前回から変更のあったものだけに限定することも検討しているが、これは単に、問題の伝播速度を遅らせるだけだ。同期する前に発生した障害にはどうしようもない。これにより、anongitに伝播した障害ならば巻き戻せるが、これは今回対処した、Gitoliteのref logを使う障害のひとつに対応したに過ぎない。(もちろん、我々は頻繁に障害を起こしているわけではないが、いずれ起こすだろう。同期と同じ頻度でバックアップしない理由はないし、念のためにスナップショットもとっておく)

ミラークローンの利用をやめるということも考えられる。もともと、ミラークローンは使われていなかったのだが、ミラーではないクローンをanongitで使うと、その他のlegitimate, authenticated force pushes, ref deletionsなどの問題もあり、色々と都合が悪かったのだ。それにrefspecを静かに通すのでは、あまり意味がない。ミラーではないクローンと、それに続くミラーモードクローンで、レポジトリ全体をpackで検証するような、ハイブリッド的なアプローチも検証する価値があるだろう。

各レポジトリへの変更を記録して、同期による巻き戻しが妥当なものかどうかを検証することもできたかもしれない。これは可能な話だが、やたらに複雑で、問題も多いだろう。

git fsckをサーバーとanongitで頻繁に走らせるという事もできたかもしれない。projects.kde.orgのマシンのように、スナップショットを取るときに、スナップショットの保持期限が過ぎる前にgit fsckを走らせれば、すべてのレポジトリで整合性あるスナップショットが取られ、ロールバック可能であることが検証できる。ただ、これは障害を検知するために頻繁に行うにはかなり重い処理だ。

projects.kde.orgのマシンで使っているように、git.kde.orgにもZFSを使うことも考えられる。ZFSにはチェックサムがあり、ハードウェアとファイルシステムレベル両方の障害を検出可能だ。これはディスクやメモリーはいずれは故障するという前提にたつ設計であり、ひそかに長年ビット反転を起こしていたような不良品を検出できることで有名だ。ZFSのRAID-Z機構はRAID Write Holeの問題もないし、十分なメモリがあれば、リード性能も素晴らしく、手軽に使えるCopy-on-Writeスナップショットも備えている。KDE Gitレポジトリの容量はたったの25GBであり、スナップショットをとってもそれほどの容量を圧迫せず、サーバーサイドでは悪くない。今すぐにでも使いたいぐらいだ。ただ、ここ数年Linuxですばらしい体験をした身としては、私は今ではZFSオタクの一人に過ぎないし、git.kde.orgを走らせているSUSEでどの程度サポートされているのかもわからない。(私はGentoo、Debian、Ubuntuで問題なくZFSを使えているものの)

とにかく、検討すべきことが山ほどあるのだ。

No comments: