「KLab勉強会#2」の資料を公開します

6月22日に「KLab勉強会#2」が開催されました。私もVIVERについて発表をさせていただきました。そのときの資料を公開します。

※6/25追記:KLab勉強会#2の資料を公開します - KLab開発者の部屋に資料ファイルを置いていただきました。
※6/30追記:VIVERを動作や利点については、IPAX 2007の資料でより分かりやすく紹介しています。先にこの資料を見ていただいた方が分かりやすいと思います。


断片的にしかお話しできなかったので、ここで細部まで徹底的にご紹介したいと思います。資料も一緒にご覧ください。対話型プレゼンテーションは、矢印キーでスライドを進めたり戻したりできます。
さらに込み入った内容については、VIVER 1.0.1のソースコードGPLRuby)をご覧ください。

VIVERとは?

通常通りHDDにインストールしたLinuxシステムを、VIVERを使ってディスクレスブータブルにすることができます。VIVERのWebサイトからダウンロードできる「VIVER化キット」を使って、HDDにインストールしてあるLinuxを、1つのファイル(「ディスクイメージ」)にまとめます。1台のコンピュータをそのディスクイメージを記録したメディアから起動すると、ネットワークで接続された他のコンピュータを次々とネットワークブートすることができます。


ブートメディア

最初の1台を起動するメディアには、CDの他にHDDやUSBメモリ、SDカードなども使うことができます。基本的にBIOSがブートに対応しているメディアであれば、何でも利用できます。このメディアを入れ替えて再起動すれば、まったく違うシステムを起動することができます。

最初の1台を起動するメディアには、2つの役割があります。1つはブートローダ(とLinuxカーネルとinitrd)をBIOSにロードさせること、もう1つはLinuxシステムが入ったディスクイメージを記録しておくことです。この2つの役割は別々のメディアに担当させることができ、ブートローダ(とLinuxカーネルとinitrd)はCDに入れておき、ディスクイメージはHDDに置いておく、ということもできます。CDよりHDDの方が読み込みが速いので、起動や動作が速くなります。

VIVERは、コンピュータに接続されたすべてのドライブからディスクイメージを探し出します。ディスクイメージが1つしか見つからなかった場合はそれを利用しますが、複数のディスクイメージが見つかった場合は、「より速そうな」メディアに記録されているディスクイメージを自動的に選択して利用します。つまり、一度CDを使ってブートメディアを作った後でも、ディスクイメージだけをHDDにコピーすれば、次回の起動からは自動的にHDD上のディスクイメージが利用されるようになります。また、明示的にディスクイメージのファイル名を指示したり、ディスクイメージが記録されているメディアのタイプ(IDE接続かSerialATA接続か、フラッシュメモリかHDDか、など。判定が外れることもあります)を指示すると、指示されたメディアから優先的にディスクイメージを探し出します。詳細はVIVER 1.0.1のソースコードのdisks.rbとdisks_*.rbをご覧ください。


ハードウェア自動検出

VIVERには、ほとんどの1CD Linuxが搭載しているのと同じように、ハードウェアを自動に検出して適切なドライバをロードする機能があります。このため、事前にハードウェアの構成を調べて設定ファイルを書いておく、などの手間は必要はありません。また、ネットワークブートする際に、すべてのコンピュータの構成がまったく同じである必要がありません。起動時に接続されているハードウェアの情報を検出し、適切なドライバをロードします。

このハードウェア自動検出機能はVIVER用に独自に開発したもので、Rubyで書かれています。検出の仕組みは大まかに以下のようになっています。

  1. /lib/modules/`uname -r`/modules.pcimapを調べて、VendorID+DeviceIDとドライバ名の対応表を作る
  2. /sys/bus/pci/devicesを参照して、接続されているすべてのPCIデバイスのVendorID、DeviceID、ClassIDを調べる
  3. 1.と2.を照らし合わせて、適切なドライバをロードする
  4. ClassIDを調べて、接続されているPCIデバイスの種別(USBコントローラか、SCSIコントローラか、など)を判別する
  5. USBコントローラの場合、
    1. /lib/modules/`uname -r`/modules.usbmapを調べて、VendorID+ProductIDとドライバ名の対応表と、InterfaceClassIDとドライバ名の対応表を作る
    2. /sys/bus/usb/devicesを参照して、接続されているすべてのUSBデバイスのVendorID、ProductID、InterfaceClassIDを調べる
    3. 1.と2.を照らし合わせて、適切なドライバをロードする
    4. usb-storageをロードする
  6. SCSIコントローラ、FireWireコントローラ、SerialATAコントローラ、IDEコントローラの場合
    1. 各コントローラに合ったストレージ用ドライバをロードする(sd_mod、sr_mod、ide-disk、libata、sbp2など)

Linuxカーネルが元々持っているドライバ名の対応表を利用するため、新しいハードウェアが発売される度にデータベースを更新するなどといったことは必要ありません。Linuxカーネルが新しいハードウェアに対応する度に、検出できるハードウェアが増えていきます。

詳細はVIVER 1.0.1のソースコードのhardware.rbとhardware_*.rbをご覧ください。


ブートサーバーの設定が不要

最初にブートメディアから起動する1台が自動的にブートサーバー(DHCPサーバー+TFTPサーバー)になるため、手軽にネットワークブートを行うことができます。この機能はRUNES(Role-based Unified Network Extension System)と名付けたプログラムが担当しています。

RUNESは、起動時に与えられた「ロール」に従って、そのロールに関連づけられた「プラグイン」を実行するものです。プラグインを/etc/RUNES/runesにインストールし、ロールとプラグインの対応を/etc/RUENS/rolesに設定しておきます。

VIVERのWebサイトからダウンロードできる「runes-basic-1.0.1」には、DHCPサーバーを起動するプラグインや、TFTPサーバーを起動するプラグインなどが含まれています。ブートメディアから起動したコンピュータに適用されるロールで、DHCPサーバープラグインとTFTPサーバープラグインが実行されるように設定しておくことで、自動的にブートサーバーがセットアップされます。

runes-basic-1.0.1には、DHCPサーバープラグインやTFTPサーバープラグインの他に、他のホストと重複しないホスト名を決定するプラグイン、X11の自動設定を行うプラグイン、/etc/fstabを作るプラグイン、ディ ストリビューションが持っているネットワーク設定を無効化してVIVER用の設定を適用するプラグイン、ディストリビューションが持っているハードウェア自動検出機能を無効化するプラグインなどが含まれています。Linuxシステムの基礎的な部分をブートするための機能はVIVERが、Linuxシステムの設定を制御する機能はRUNESが担っています。

RUNESは将来的に目指している「分散ネットワークを自動構築するプラットフォーム」のプロトタイプでもあり、「Interfaceによってプラグイン同士が通信する」という概念を含んでいます。これは後述します。


VIVERのネットワークブートのデモ

VIVERウェブサイトのデモムービーをご覧ください。


VIVERの使い方

詳しくはVIVERのWebサイトの、VIVER化 1.0をご覧ください。


VIVERの仕組み

通常のLinuxのブートプロセス

まずは通常のLinuxがどのようにブートしているかを紹介します。

BIOSからブートローダに処理が移ると、ブートローダLinuxカーネルと、「initrd」をメモリ上にロードし、Linuxカーネルに処理を移します。Linuxカーネルは、initrdを/(/はrootfs=ramfsの/専用特殊版)に展開します。initrdは単なるgzipで圧縮されたcpioアーカイブで、中にはinitという名前のプログラム(多くの場合nashスクリプト)と、HDDをmountするためのドライバが入っています。initはLinuxカーネルから直接実行され、HDDをmountするためのドライバをロードし、HDDを/にmountします。HDDをmountするためのドライバは、Linuxシステムをインストールする時点で決定され、initrdに組み込まれます。

VIVERのブートプロセス

initrdとLinuxカーネルがメモリ上にロードされるまでは通常と同じです。しかし、VIVERではinitrdをVIVER用のinitrdに置き換えます(LinuxカーネルもVIVER用のパッチが適用されたものに置き換えます)。このinitrdは非常に高機能化されたもので、すべてのドライバや、ハードウェア自動検出機能、DHCPクライアントなどが含まれています。…つまり、initrdこそがVIVERです。

VIVERにはrubyインタプリタが含まれており、/initからRubyで書かれたスクリプト(つまりVIVER 1.0.1のソースコードそのもの)が実行されます。ここでハードウェアの自動検出やネットワークインターフェースの設定などが行われ、CD上やネットワーク越しのディスクイメージを/にmountします。

この後は通常のLinuxシステムと同じように起動していきます。

VIVER(=initrd)はgzipで圧縮されたcpioアーカイブですから、展開して中身を調べることができます。VIVERのWebサイトからダウンロードできるviver-kit-1.0.1.tar.bz2に含まれているbootsuite/boot/viverrd.gzは、以下のようにして展開できます。

# mkdir work
# cd work
# gunzip -c /path/to/viverrd.gz | cpio -ivd

同じように、またinitrdに戻すことができます。

# find . | cpio --quiet -c -o | gzip -9 -c > ../viverrd-hacked.gz
Copy-on-Write

CD上やネットワークで共有しているディスクイメージは、読み取り専用にしています。書き込みは、個々のコンピュータ上のメモリ(tmpfs)に行います。読み込みはディスクイメージとtmpfsの両方から、新しい方を読み込みます。

これにはunionfsを使っています。


V-FIELD

ネットワークでディスクイメージを共有するために、V-FIELDと名付けたプログラムを利用しています。

V-FILEDは、まず最初にディスクイメージ指定して1台目のノードを起動します。1台目のノードはサーバーとなってディスクイメージを共有します。続いて2台目のノードを起動すると、1台目からディスクイメージの内の一部分をダウンロードしてきて、2台目のノードもサーバーとなります。次々にノードを起動していくと、ディスクイメージ全体が分散保持され、さらに起動していくと、2重、3重になって保持されていきます。ノードの数が多くなるほど、1台が保持する容量が大きいほど、ディスクイメージのサイズが小さいほど、多重化度が増加します。

多重化された状態であれば、動的にノードを追加したり離脱したりできます。1台目のノードを落としても問題ありません。ネットワーク全体で最低限1重化されていれば問題なく動作し続けます。1台目のノードがいなくても、さらにノードを追加することができます。また、ノードが離脱しすぎて、ディスクイメージが欠損してしまった場合でも、ディスクイメージを与えたノードを起動し直せば、また正常な動作を再開します。



分散ネットワークの自己組織化

このブログでは2007年4月8日のエントリ4月21日のエントリなどで紹介している内容です。

分散ネットワークにおけるモジュールは、あるサーバー機能をパッケージとしてまとめ、そのサーバーが提供する機能にアクセスするためのインターフェースを付加したものです。そしてモジュール同士がメッセージをやりとりし、ネットワークを構築していきます。

たとえば、複数のロードバランサが動作している環境で、新たに動的にWebサーバー機能を提供するサーバーを追加するシナリオを想定してみます。新たに追加したサーバーで動作する「Webサーバーモジュール」が、「ネットワーク上のどこか、どこでもいい場所」にある「ロードバランサモジュール」に対して、「ロードバランスしているユニットの一覧が欲しい」などと要求し、ユニットの一覧を得ます。各ユニットのに参加しているサーバーの「リソース管理モジュール」に対して、「平均の負荷はどのくらいか」とメッセージを送ります。得られた負荷指数を用いて、各ユニットの忙しさを計算し、一番忙しそうなユニット(たとえば「サービスX用Webサーバークラスタユニット」)を選びます。そしてロードバランサモジュールに対して「サービスX用Webサーバークラスタユニットに参加する」とメッセージを送ります。

ここで重要なことは4点あります。一つは、「ネットワーク上のどこか、どこでもいい場所にあるロードバランサモジュール」、あるいは「Webサーバーモジュールを実行しているサーバー上で実行されているリソース管理モジュール」などの、メタな情報でIPアドレスが引けるネームサーバーが必要である点、2つ目はモジュール同士がネットワークを越えて通信を行う点、3つ目は各モジュールが他のモジュールからの要求を受け付けるインターフェースを持っている点、4つ目は、インターフェースさえ動作すれば、実装の詳細は重要ではない点です。


RUNESは以上の概念のうち、ローカルマシン内でインターフェースによってプラグイン同士が通信する機能のみを実装しています。他のプラグインに対してメッセージを送るためのAPIがRUNESから提供されています。たとえばDHCPサーバープラグインの実装は、以下のようにして行わています。

  1. IPアドレス管理プラグインに対してメッセージを送り、IPアドレスやサブネットマスク、デフォルトゲートウエイアドレスなどの情報を得る。
  2. TFTPサーバープラグインに対してメッセージを送り、 PXEで利用するブートローダのTFTPサーバー上のファイルパス(つまりdhcpd.confのfilenameオプション)を得る
  3. 1.と2.の情報をもとに、DHCPサーバーの設定ファイルを作成する
  4. DHCPサーバーを起動する

動的な分散ファイルシステム

冗長化や負荷分散が行われた分散ネットワークを構築するには、ファイルの共有が欠かせません。現在はDRBD+NFSやDRBD+GSF2/OCFS2、あるいはレプリケーションされたRDBMSなどが利用されているようですが、「動的にサーバーを追加したり離脱したりできる」「最初は1台でも運用できる」「サーバーを追加するとファイルシステムサイズが増える」「サーバーを離脱させるとファイルシステムサイズが減る」「サーバーを追加すればするほど動的に負荷分散される」などの、「動的」な性質は持っていません。

そこで、動的な分散ファイルシステムを設計しています。詳しくは6月4日のエントリなどをご覧ください。


その他の話

V-FIELDのデモではttyrecを使ってターミナルを録画ものを使いました。ここでターミナルが4つに分割されていますが、これにはvimを使っています。GNU screenだと横にしか分割できませんが、vimは縦にも分割できます。そしてvimの分割した各ウィンドウで、vimshellを起動し、さらにscreenを起動しています。以下の設定を.vimrcに書いておくと、コマンド一発で画面を4つに分割し、それぞれのウィンドウでvimshellを起動し、さらにscreenを実行できます。(vimshellのパッチを当てたvimが必要です)

map <C-W><C-X> :new<CR>:vnew<CR>:vimshell screen<CR><C-W><C-W>:vimshell screen<CR><C-W><C-W>:vnew<CR>:vimshell screen<CR><C-W><C-W>:vimshell screen<CR><C-W><C-W>

VIVERのネットワークブートのデモではParallelsを使っていますが、Parallelsはネットワークブート(PXE)に対応していません。そのため、実はEtherbootを使ってフロッピーディスクから起動しています。よーく見ると、Parallelsのブート画面に「Boot from floppy drive」と書いてあります。