2010年4月13日火曜日

IPv4 to IPv6, IPv6 to IPv4 translator

ちょっとした必要に迫られて、IPv4とIPv6のプロトコル変換プログラムを作りました。動作は至ってシンプルで、あるひとつのIPv6アドレスを、別のユニークなIPv4アドレスに変換(また、その逆変換)を行うものです。最近流行っている、NAT64のような、複数のIPv6ノードがひとつのIPv4アドレスを共有して通信するものとは異なります。

そもそもの目的は、IPv6のみを使って構築運用されているネットワークの内部ノードを、外部に公開することでした。もちろん、IPv6を使えば、普通に外部からアクセスできるのですが、まだまだIPv6の利用が一般的ではない現在、IPv4での公開も考えなければなりません。こうした目的の場合、一般的にはIPv4とIPv6のデュアルスタック環境にするところです。しかしながら、必ずしもすべてのIPv6ノードがIPv4でアクセスできる必要はありません。運用されているIPv6ノードの中で、サーバとして公開されるものは極わずかであり、多くのノードはそのサーバのサポートをしているだけだからです。そのようなノードに、貴重なIPv4アドレスを割り当てるのはもったいないですし、またデュアルスタックにすることで運用のコストも大きくなりかねません。

そこで、公開サーバの数を収容するために必要十分なIPv4アドレスを用意して、単純に1対1で対応させてしまおう、ということになります。言ってみれば、IPv6への移行が完了するまでの、つなぎの技術ですね。

仕組み自体はNAT64とDNS64のものとほとんど変わりません。違いはトランスポート層のポート番号の変換処理が必要かどうかです。アドレスを1対1で対応させる場合、ポート番号を処理する必要がないので、変換サーバがNATのような状態を持つ必要がなくなります。状態を持たなくてよいので、負荷分散あるいは耐障害性の向上のために、簡単に多重化できるという利点があります。欠点はNAT64に比べてたくさんのIPv4アドレスが必要になることです。なにせ1対1対応ですから。

実装はtunデバイスを使っています。最近のOSなら標準でサポートしているものも多いのではないでしょうか。tunデバイスを使えば、read(2)システムコールとwrite(2)システムコールで生のIPパケットを読み書きできるので、今回のような目的にぴったりです。ユーザ空間で実装できるので、開発やデバッグも簡単になります。当然、転送速度は遅くなると思いますが、開発の簡単さ、もともとの目的がつなぎの技術であることを考えると、そう悪い選択でもないでしょう。

動作はちょっと複雑です。まず、IPv6ノードがIPv4ノードにアクセスする場合から考えてみます。IPv6ノードはIPv4アドレスを持っていないため、まずIPv4アドレスとIPv6アドレスの対応を定義します。ここでは例として、IPv6ネットワークのプレフィックスを2001:db8:0:0::/64、IPv4グローバルアドレス空間を192.0.2.0/24と仮定しましょう。以下のようなアドレスの対応を定義します。

  • 192.0.2.1 <=> 2001:db8:0:0::100

つまり、外部のIPv4ノードが2001:db8:0:0::100と通信したいと思ったときは、192.0.2.1に対して接続すればよいということになります。192.0.2.1に届いたIPv4パケットは、IPv6に変換されて2001:db8:0:0::100に転送されます。このとき、変換後のIPv6パケットの始点アドレスには、通信相手のIPv4アドレスが識別できるよう、IPv6アドレスの一部にIPv4アドレスを埋め込んでおきます。始点アドレスとして使うIPv6プレフィックスはなんでもよいのですが、仮に64::/64を使うとしましょう。外部のIPv4ノードのアドレスが202.214.86.196だった場合、変換後の始点アドレスは64::cad6:56c4となります。IPv4アドレス4バイト分が、IPv6アドレスの下4バイトに埋め込まれた形になります。

逆方向の通信には、上記の手順を逆に適用します。2001:db8:0:0::100から64::cad6:56c4に対してパケットを送信すると、変換サーバでIPv6宛先アドレスの下4バイトからIPv4宛先アドレス202.214.86.196を取り出します。また、あらかじめ定義されたIPv4とIPv6アドレスの対応表から、始点アドレスとして使うIPv4アドレス(この場合は192.0.2.1)を取り出し、IPv4パケットとして転送するのです。

コードはgithubで公開しています。興味のある方は http://github.com/keiichishima/map646 にアクセスしてみてください。forkも大歓迎です。

2009年11月20日金曜日

自分で作るネットワークストレージ (2)

ファイルシステムを作ると言っても、OSネイティブのファイルシステムを作るのは大変です。しかしながら、最近はFUSE (Filesystem in Userspace)を使って手軽にファイルシステムを試作できるようになりました。以前だったら躊躇していたところですが、気軽に試せそうです。

FUSEはOS内部で発生したファイルシステムイベントをユーザー空間のプログラムに通知し、そこで必要な操作を代行させることでOSネイティブのファイルシステムと同様の操作性をファイルシステム利用者に提供しています。イベントの処理といっても、実際はイベントに対応したコールバック関数を記述する形になりますから、特に実装が複雑なわけではありません。FUSEでは様々なイベントに対応するコールバック関数が定義されていますが、すべての関数を実装しなければならないわけではありません。たとえば、シンボリックリンクをサポートしないのであれば、シンボリックリンクに関係する関数を実装する必要はありません。とりあえず今回対応するのは以下の関数です。試作にMacOS XのXcodeテンプレートを使うので、関数名がCocoa APIっぽく書かれていますが、簡単に読み替えることができると思います。

  • ディレクトリ一覧 (contentsOfDirectoryAtPath:error:, opendir(2), readdir(2)など)
  • ファイル情報取得 (attributesOfItemAtPath:userData:error:, stat(2))
  • ファイル情報設定 (setAttributes:ofItemAtPath:userData:error:, chmod(2)など)
  • ファイルオープン (openFileAtPath:mode:userData:error:, open(2))
  • ファイルクローズ (releaseFileAtPath:userData:, close(2))
  • ファイル読み込み (readFileAtPath:userData:buffer:size:offset:error:, read(2))
  • ファイル書き込み (writeFileAtPath:userData:buffer:size:offset:error:, write(2))
  • 新規ディレクトリ作成 (createDirectoryAtPath:attributes:error:, mkdir(2))
  • 新規ファイル作成 (createFileAtPath:attributes:userData:error:, open(2))
  • ファイル移動 (moveItemAtPath:toPath:error:, rename(2))
  • ディレクトリ削除 (removeDirectoryAtPath:error:, rmdir(2))
  • ファイル削除 (removeItemAtPath:error:, unlink(2))

FUSEで読み書き可能なファイルシステムを作る場合、上記の関数を実装すればとりあえず動きます。あとは、これらのイベントの裏で、適切に版管理システムと連携させてあげればよいわけです。版管理システムが提供するインターフェースは以下のようなものになるでしょう。

  • チェックアウト (checkout)
  • 更新 (update)
  • 追加 (add)
  • 複製 (copy)
  • 移動 (move)
  • 削除 (delete)
  • コミット (commit)

今回、版管理システムとしてSubversionを念頭に置いています。ですが、Subversionと同様の概念で構築されているシステムであれば、上記のような抽象化された版管理インターフェースと実際の動作を対応させることで、差し替えもできるのではないかと思います。

FUSEが提供するファイル操作と、版管理システムが提供するファイル操作をじっと見比べてみると、お互いの機能が似ていることがわかります。FUSE側に存在せず、版管理側に存在する操作としてチェックアウトとコミット、逆にFUSEには存在し、版管理システムに存在しない操作としてはファイルのオープンやクローズ、ファイル情報の操作などがあります。これらの差を埋めれば二つのシステムが相互に繋がりそうです。Subversionを前提にがっつり実装してもよいのですが、将来の拡張性を考えて、版管理抽象層を定義し、以下のAPIを版管理抽象層からFUSEファイルシステム側に提供することにしました。

  • 初期化 (revisionControlSetup:)
  • チェックアウト (revisionControlSetup:)
  • 追加 (revisionControllAdd:)
  • 削除 (revisionControlRemove:)
  • 複製 (revisionControlCopy:)
  • 更新 (revisionControlUpdate:)
  • コミット (revisionControlCommit:)
  • タッチ (revisionControlTouch:)
  • 終了処理 (revisionControlCleanup:)
  • 実パス名取得 (revisionControlRealPathForPath:)
  • 制限ファイル名取得 (revisionControlNameIsReservedAtPath:)

いくつか、版管理システムの操作一覧にないAPIを追加しています。初期化と終了処理はよいとして、タッチ、実パス名取得、制限ファイル名取得は、手元の作業コピーでの操作と版管理されているデータを結びつけるために必要となります。

今回、版管理システムを基礎としてファイルシステムを構築するので、ファイルへの操作はオリジナルのファイルではなく、手元に作られた作業コピーに対して行われます。作業結果は、最終的に版管理システムにコミットしなければならないので、どのファイルが操作されたのか履歴を記録しておく必要があります。タッチAPIは、作業コピーで修正されたファイルを版管理抽象層に通知します。

実パス名取得APIは、版管理システム内で使われているパス名を、作業コピー上でのパス名に変換するためのAPIです。今回の試作では、版管理システム内のディレクトリ構造を、手元のPCの特定ディレクトリにマウントして使うことを想定しています。FUSEから通知されるパスはマウントされたディレクトリからの総体パスになりますが、実際に操作できるファイルは作業コピーとして取り出したファイルパスになります。作業コピーを取り出した場所は版管理抽象層しか知らないので、実際のファイルパス名を知りたい場合に抽象層に問い合わせるAPIが必要になります。

制限ファイル名取得APIは、ファイルシステムとして利用できないファイル名を判断するためのAPIです。たとえば、版管理システムとしてSubversionを使う場合、.svnという名前のファイル名、ディレクトリ名は使えません。このAPIを通じて、後ろで使われている版管理システムで禁止されているファイル名を取得します。

とりあえずFUSE関数を上述の版管理抽象層APIで実装してみると、以下のようになります。

  • (FUSE初期化関数)
    • 初期化
    • チェックアウトあるいは更新
  • ディレクトリ一覧
    • 更新
  • ファイル情報取得
    • (作業コピーのファイル情報を取得)
  • ファイル情報設定
    • (作業コピーのファイル情報を取得)
  • ファイルオープン
    • タッチ
  • ファイルクローズ
    • コミット
  • ファイル読み込み
    • (作業コピーのファイルを読み込み)
  • ファイル書き込み
    • (作業コピーのファイルに書き込み)
  • 新規ディレクトリ作成
    • (作業コピーにディレクトリ作成)
    • 追加
  • 新規ファイル作成
    • (作業コピーにファイル作成)
    • 追加
  • ファイル移動
    • (移動元を移動先に)複製
    • (移動元を)削除
  • ディレクトリ削除
    • 削除
  • ファイル削除
    • 削除
  • (FUSE終了関数)
    • コミット
    • 終了処理

まぁ、いろいろと問題はあるのですが、一応動くものにはなります。ソースコードをsourceforgeに公開していますので、興味のある人は覗いてみてください。sourceforgeに登録しているコードは、上記以上の機能も実装していますが、それはまた別の機会に。

次回に続きます。

2009年11月11日水曜日

自分で作るネットワークストレージ (1)

ネットワークストレージという考え方は昔からありましたが、それが単体サービスとして有償で提供されるようになったのはここ数年でしょうか。Dropboxなどに代表されるネットワークストレージサービスには一定の利用者がいるようで、解説記事もよく見るようになりました。

データをサーバに保持しておいて、ネットワーク経由でどこからでもアクセスするという考え方は新しいものではありません。ただ、そういったサービスを構築するには、高度なサーバ設定、ネットワーク設定の知識が必要だったため、一般の人が手軽に始めるというわけにはいきませんでした。近年のネットワークストレージサービスは、サーバ設定をサービス提供者側に分離し、クライアントとネットワークの設定を専用アプリケーションにすることでその敷居を下げ、技術者以外の利用者の取り込みに成功したのだと思います。

さて、特に昔からコンピュータネットワークを使っている人には共感してもらえると思いますが、自分のデータを自分の管理の行き届かない場所に預けるのは心配なものです。便利なネットワークストレージサービスですが、大切なデータをそこに保存しても大丈夫なのかどうか、つい考えてしまいます。そうはいっても便利なサービスは使いたい、ということで、自分で運用できないものかと考えるのは技術者として自然なことです。

では、自分にとってネットワークストレージサービスに必要な機能は何か、と考えてみると、以下のような要求がでてきました。

  1. 自分で運用できる
  2. ファイルシステムとして操作できる
  3. ネットワークに接続していなくてもある程度操作できる

要求1は、そもそもの発端なので当然です。要求2は利便性を確保するために重要です。ネットワーク上にデータを保存できても、それを直接ローカルアプリケーションで操作できなければ面倒です。OSのファイルシステムの一部として見えている、もしくは同等の操作性が提供されている必要があります。要求3も、現実の環境を考えると必須といえます。ネットワーク環境は日々向上しているものの、ネットワークに接続できない場所は必ずあるものです。その間、フル機能ではないにせよ、ある程度の操作ができないと使い勝手が落ちてしまいます。

これらの条件を満たすもので、現状一番近いものは版管理システムです。旧くはRCSやCVS、その後現れたSubversion、最近使われる事が多いGitなどが有名です。これらの版管理システムは一種のファイルシステム機能を提供しています。ただ、元々プログラムのソースコードの改変履歴を記録する目的で設計されたシステムですから、OSのファイルシステムと同様には操作できません。ファイルを更新しようとした場合、まず版管理システムに保存されているデータを、通常のファイルシステム上に複製(チェックアウト)し、その複製に対して編集を行い、編集完了後に書き戻し(チェックイン)といった、通常のファイル操作とは異なる粒度でのファイル操作が必要になります。

それならば、その部分をうまく隠蔽し、通常のOSレベルでのファイル操作と同等のインターフェースを提供できれば、自分の欲しいネットワークストレージになるのではないか、しかも版管理機能のおまけ付き、という考えが浮かびます。

実際、そういう試みは過去にありました。かなり古いプロジェクトですが、CVSサーバをNFSマウントするVirtual CVS Filesystemというソフトウェアがsourceforgeに登録されています。ただし、プロジェクトが活動停止してからかなり時間が経っているようです。また、FUSEを用いたSVNレポジトリアクセス用ファイルシステムSvnFSというのもあります。こちらは開発が継続しているようです。ただ、どちらも読み取り専用ファイルシステムで、ネットワークストレージとしては中途半端な状態です。ここはやはり、読み書きできるストレージシステムが欲しいところではないでしょうか。

ということで次回に続きます。

2009年7月22日水曜日

FUSEオペレーションの可視化

FUSE (Filesystem in Userspace) はvfs/vnode APIをユーザ空間のプログラムから利用できるようにするための機構で、OSでサポートされないファイルシステムを容易に追加できます。sshサーバ上のディレクトリにアクセスするためにsshfs-fuseを使っている人も多いのではないでしょうか。

vfs/vnodeアクセスがユーザ空間から見えるので、それを利用すればファイルシステムへのアクセスの様子が見えるはず、ということでUbiGraphを使ってsshfs-fuseのアクセスを可視化してみました。

まずは、sshfs-fuseでマウントしたディレクトリ上で、次のスクリプトを実行してみました。

cd /sshfs
for i in 0 1 2 3 4 5 6 7 8 9 10
do
mkdir $i
cd $i
for j in a b c d e f g
do
mkdir $j
done
done
sleep 2
cd /sshfs
rm -rf 0


11レベルの階層ディレクトリを作り、それぞれの階層に7個のディレクトリを作成した後、すべてのディレクトリを削除しています。各ノードは、ファイル/ディレクトリの実体に最初にアクセスされた段階で描画されます。ですから、初期状態では、トップディレクトリにあるオブジェクトしか見えていません。グラフのノードはファイルかディレクトリを表しており、緑色のノードがディレクトリ、青いノードがファイルです。アクセスされたオブジェクトは、色が赤くなるとともに、そのオブジェクトのファイル名/ディレクトリ名が表示されます。

続いて、sshfs-fuseでマウントしたディレクトリ上で、wgetをコンパイルしてみました。

cd work
tar zxf wget-1.11.tar.gz
cd wget-1.11
sh configure
make
make distclean
cd ..
rm -rf wget-1.11

wget-1.11.tar.gzを展開すると、ファイルの生成に合わせてディレクトリ木が徐々に伸びていくのが分かります。



configureの過程ではwget-1.11を展開したディレクトリ直下のファイルが頻繁にアクセスされており、makeが始まると進み具合に応じて各ディレクトリのファイルが順次アクセスされていくのが見えます。

単にアーカイブを展開、コンパイル、削除しただけですが、可視化することでまた違った感じがします。

2009年6月12日金曜日

Xen Guest OSのOfflink Migration

Live Migrationの課題

ライブマイグレーションは、移動元と移動先のHost OSが、同一のサブネットワークに接続されていることを前提としています。なぜならば、Guest OSが利用するネットワーク環境はHost OSのネットワーク環境に依存しているため、Host OSとInternetとの接続状態が変更されると、それに応じてGuest OSのネットワーク設定を変更しなければなりません。

Network Mobility Basic Support (NEMO BS)

NEMO BSはIPv6モバイルルータを実現するプロトコルで、RFC3963として標準化されています。NEMO BSを実装したモバイルルータは、自身が抱えるネットワークに固定のアドレスを提供しつつ、任意のネットワークに自由に接続できるようになります。

Host OSのモバイルルータ化

Live Migrationの問題である、Host OSのネットワーク環境の変化は、NEMO BSを組み合わせることで解決できます。Guest OSが接続するネットワークを、NEMO BSの機能として提供される固定ネットワークにすることで、NEMO BSの設定情報を共有するHost OS間で、Host OSの物理的な位置に関わらずGuest OSをMigrationできます。

Interop Tokyo 2009での実験

Interop Tokyo 2009では、NEMO BS機能を追加したXenサーバを2台作成し、それぞれのサーバを異なるサブネットワークに配置しました。Guest OSは、いずれかのXenサーバで動作します。

Guest OSのMigrationは、通常のXen Guest OSのMigrationと同様の手順で実施されます。異なっている点は、Migration先のXenサーバが、同一サブネットワークではなく、異なるサブネットワーク上に配置してある点です。通常、このような構成ではGuest OSのMigrationは成功しません。Guest OSが他方のXenサーバに移動した時点で、ネットワークから切り離されてしまうためです。

本実験では、Guest OSをMigrationすると同時に、移動元のXenサーバ上で動作しているモバイルルータ機能を停止し、他方のXenサーバでモバイルルータ機能を開始します。Guest OSが接続しているネットワークには、モバイルルータによって固定のネットワークが提供されています。このネットワークは実際にはInterop Tokyo 2009のShowNetネットワーク内に配置されたホームエージェントによって管理されており、論理的にはShowNetネットワークの一部になります。

Gust OSは二つのモバイルルータ間を移動しますが、モバイルルータが提供するネットワークアドレスは変化しないため、Guest OSはHost OSが接続したネットワークが変わったことを意識する必要が無くなります。

トラフィックのグラフ

実験では、Guest OSから定常的なストリーミングトラフィックを送信しながら、5分間隔でふたつのHost OSの間をMigrationさせています。それぞれのXenサーバのインターフェースのトラフィックを示します。グラフからGuest OSがふたつのXenサーバの間を移動していたことがわかります。

謝辞

本実験の実施に際して、ShowNet NOCメンバーのみなさまに多大なご支援をいただきました。ここに感謝いたします。

2009年6月11日木曜日

Xenカーネルの再構築

XenのLive Migrationで遊ぶために、LinuxのインストールやNFS rootの設定やらに四苦八苦しつつも、なんとか基本的なMigrationまではできる環境を構築できた。本当にやりたかったことは、このLive Migrationを、異なるふたつのネットワークセグメントに配置されているXenサーバ間で実行することだったりする。

アイデアとしてはシンプルで、XenサーバをNetwork Mobility (NEMO) 機能を持ったモバイルルータとし、Guest OSが接続するネットワークをMobile Networkとして運用するというものだ。ふたつのXenサーバを用意して、それぞれを同じモバイルルータとして設定しておく。Guest OSのMigrationと同時に、元のHost OSのモバイルルータ機能を停止し、Migration先のHost OSのモバイルルータ機能を有効化すれば、Guest OSからみるとネットワーク的には移動していないように見える。

ところが、Xenを導入したDebianでNetwork Mobilityのプログラム (NEPL) を起動させようとしてみたところ、動作しないことが判明した。どうやら、NEPLが必要としているカーネル機能が組み込まれていないことが原因らしい。最近のLinuxは多くの機能が動的にロードされるモジュールとなっているが、中には動的モジュールとして分離できない拡張機能も少ないながら存在する。今回問題になったのはxfrmのポリシー拡張機能 (CONFIG_XFRM_SUB_POLICY) だった。この機能がカーネルに組み込まれていないと、そもそもMobile IPv6やNEMOの機能を利用することができない。しかし、カーネルのモジュールディレクトリには、Mobile IPv6やNEMOの基本部分を実装したmip6.koが収められており、一見Mobile IPv6/NEMOを利用できるかのように見えてしまう。これはまぎらわしい。

ともかく、カーネルの再構築が必要なので、その作業を進めることにする。XenのHost OSサポートはまだLinuxカーネルのメインラインに取り込まれていないので、再構築の際にはXenパッチを適用する必要がある。今回、Xen Host OSカーネルはAPTを使ってバイナリパッケージをインストールしているが、APTにはパッケージをソースから再構築する手段も提供されている。

今回使ったバイナリパッケージはlinux-image-2.6.26-2-xen-686なので、まずはこのパッケージをソースから再構築するために必要なツール群をインストールする。これもAPTを利用すれば簡単だ。APT便利だなぁ。

# apt-get build-dep linux-image-2.6.26-2-xen-686

これで必要なツール群がインストールされる。続いて、カーネルパッケージのソースを入手する。

# apt-get source linux-image-2.6.26-2-xen-686


カレントディレクトリにソースと必要なパッチが展開されるので、それを使ってパッケージの再構築をおこなう。単に再構築するだけならば、ここからさらに次のコマンドを実行するだけでよい。

# dpkg-buildpackage

これで、バイナリパッケージとして配布されている物と同じパッケージを再構築できる。もちろん、今回の目的はカーネルの組み込み機能を変更することなので、そのまま構築するだけでは意味がない。パッケージ構築時に参照されるカーネルスイッチは、ソースが展開されたディレクトリにあるlinux-2.6-2.6.26/debian/config/configファイルに列挙してある。なるほど確かに、先のオプションスイッチ (CONFIG_XFRM_SUB_POLICY) は定義されていない。このスイッチを有効化して、パッケージを再構築するが、単にdpkg-buidpackageコマンドで再構築すると、カーネルABIの一貫性チェックで失敗するので、linux-2.6-2.6.26/debian/abi/2.6.26-2/ディレクトリの名前を変更 (あるいは削除) しておく。このディレクトリには、コンパイルされたカーネルのシンボル情報とアドレス情報が記述されており、実際に構築されたカーネルがこの情報と一致しない限りパッケージは作成されない。

これでようやくXen + NEMOカーネルが完成した。ソースを展開したディレクトリに、.debパッケージができているはずなので、これをdpkgコマンドでインストールし、ようやく準備完了。

# dpkg -i linux-headers-2.6.26-2-common-xen_2.6.26-15lenny2_i386.deb

# dpkg -i linux-headers-2.6.26-2-xen-686_2.6.26-15lenny2_i386.deb

# dpkg -i linux-image-2.6.26-2-xen_2.6.26-15lenny2_i386.deb


# dpkg -i linux-modules-2.6.26-2-xen_2.6.26-15lenny2_i386.deb

2009年6月2日火曜日

NFS rootによるXen Guest OS

Xenには、あるHost OSから、同一セグメント上にある別のHost OSにGuest OSを移動させる機能があり、migrationと呼ばれている。今回はmigration可能なGuest OSを作成するときの手順をメモしておく。

Migrationといっても、Guest OSのすべてのコンポーネントがHost OS間を移動するわけではない。移動するのはGuest OSのメモリイメージである。すなわち、ストレージに関してはmigration前後で同じ物を参照しておかなければならない。結果として、ストレージをすべてネットワークの向こう側に配置することになるのだけれど、一点注意しておかなければならないことがある。それは、Guest OSの起動に使われるrootパーティションもネットワークストレージに置いておかなければならないと言う点だ。

今回はネットワークストレージとしてNFSを使うこととし、以下の手順でNFS rootパーティションから起動するGuest OSを準備した。
  1. 通常のXen Guest OSイメージを作成する。
  2. 作成したイメージをNFS公開ディレクトリに展開する。
  3. NFS機能を組み込んだkernelを作成する。
  4. 作成したkernelでGuest OSを起動する。
Debianにはxen-toolsのAPTパッケージが用意されており、簡単にDebianのXen Guest OSを作成することができる。
# xen-create-image --hostname=guest --ip=10.0.0.64 --netmask=255.255.255.0 --gateway=10.0.0.1 --dir
--hostname、--ip、--netmask、--gatewayはそれぞれ、作成されるGuest OSのノード情報となる。--dirスイッチを指定するとGuest OSのディスクイメージが/home/xen/domains/ディレクトリの下に作成される。evmsやlvmといったボリューム管理システムを使っている場合は、オプションスイッチで新しいボリュームにGuest OSイメージを作成することもできる。他にも多くのスイッチがあるので、細かく制御したい方はマニュアルページを参照のほど。

作成されるディスクイメージは単一ファイル (上記の例の場合は /home/xen/domains/guest/disk.img) になっているので、ループバックマウントしてNFS公開ディレクトリにコピーする。

続いてGuest OS用のkernelを再構築する。通常、Host OSにXenをインストールした時点で、Guest OSのkernelとして利用できるkernelイメージが/boot/ディレクトリにインストールされている。しかし、このイメージはrootパーティションをNFS上に置く場合は利用できない。Debian kernel では (おそらく他の多くのDistributionでも)、NFS機能はダイナミックモジュールとして提供されるのが普通である。そのため、rootパーティションがNFS上にあると、NFS機能を有効化するためのダイナミックモジュールを読み込むことができなくなる。よって、NFS機能をモジュールとしてではなく、kernel組み込み機能として作成しておかなければならない。また、NFSをrootパーティションとしてマウントするための特別なkernelスイッチも有効にする必要がある。

Linux kernel再構築の詳細は省略するが、以下のスイッチ、および関連スイッチを調整することでNFS機能をkernelに組み込むことができる。
CONFIG_NFS_FS=y
CONFIG_ROOT_NFS=y
スイッチには依存関係があるので、make menuconfigを使って依存関係を壊さないように調整するようにしよう。少なくとも、僕は依存関係を手動で調整するのはあきらめた。今回はさらに、NFS関連設定に加え、XenのGuest OSとして動作するためのスイッチも有効化することを忘れないように。

作成したカーネルを適当な位置にコピーし、Xen Guest OSの設定ファイルを作成する。
kernel = '/boot/bzImage'
memory = '128'
root = '/dev/nfs'
nfs_server = '10.0.0.2'
nfs_root = '/export/xen/domains/guest'
extra = 'ip=10.0.0.64:10.0.0.2:10.0.0.1:255.255.255.0::eth0:'
name = 'guest'
vif = [ '10.0.0.64',mac=00:16:3E:E2:0A:2F' ]
on_poweroff = 'destroy'
on_reboot = 'restart'
on_crash = 'restart'
通常のGuest OSの設定ファイルと異なるのは、rootディレクティブに/dev/nfsが指定されること、nfs_serverディレクティブでNFSサーバのIPアドレスを指定する必要があること、nfs_rootディレクティブでNFSサーバのマウントポイントを指定する必要があること、extraディレクティブにNFSのrootパーティションから起動するためのkernelスイッチを指定する必要があるあたりだろうか。kernelスイッチに関しては、Linuxのソースを展開したディレクトリにある、Documentation/filesystems/nfsroot.txtに詳しい説明が書かれている。それによれば、ip=スイッチの意味は
ip=[client-ip]:[server-ip]:[gw-ip]:[netmask]:[hostname]:[device]:[autoconf]
とのことだ。hostnameは今回利用しないので空欄にしてある。autoconfについては、dhcp、bootp、rarpなどを指定することでIPアドレスを自動設定できるようだが、今回は静的にアドレスをつけているので、ここも空欄にしてある。

後は、NFSサーバで適切に公開設定を行い、通常の手順でXen Guest OSを作成する。設定に問題がなければ、Guest OSの設定ファイルで指定したkernelを読み込み、起動した後、NFSサーバ経由でrootパーティションがマウントされ、通常のブートプロセスが開始する。

さて、現在のところ、LinuxのNFS実装は、まだIPv4しかサポートしていないようだ。少なくとも、Debianの通常のNFSサポートではIPv6アドレスを指定してNFSマウントすることができない。もちろん、NFS rootパーティションをIPv6経由でマウントすることもできない。少しGoogleしてみたところ、NFSのIPv6サポートを進めている開発者は存在するようで、2008年のLinux Storage & Filesystems WorkshopにてIPv6 support for Linux NFSという発表が行われていたようだ。2008年後半を目標に開発を進めると書かれていたので、近いうちにIPv6サポートが利用できるようになるのかもしれない。