mDNS+DNS-SDで分散メタネームサーバを作るアイディア

またしても書きながら考えるメモ。

dRuby+Ringか、mDNS+DNS-SDか。今はDNS-SDの方が良さそうな気がしている。なんというか普通にDNS-SDそのもの。ただしSRVレコードは勝手に定義。そう堅いこと言わずに。


mDNSは、DNSマルチキャストで行う。いわゆるクライアントサーバー型ではない。個々のノードが、マルチキャストされてくるmDNS要求に対して、マルチキャストで応える。そしてキャッシュ。どうやらmDNSは、通常の(ユニ/エニーキャストの)DNSとは異なり、プッシュ型でレコードの変更を伝播させることができる。ココポイント。
DNS-SDは、DNSSRVレコードとTXTレコードにいろいろと情報を書き込んで、ネットワークプリンタなどのサービスを発見するのに使おうというもの。
mDNSとDNS-SDは互いに依存しているわけではないが、組み合わせると効果的。


で、このmDNS+DNS-SDをVIVERで使おうというアイディア…自体はかなり前からあるのだけど、やっと実装にこぎ着けそう。
AvahiのD-Busインターフェースを使って、起動したサービスをすべてSRVレコードを使ってパブリッシュする。サービスというのは、NFSストレージサービスとか、VRRPサービスとか。実体はRubyのクラス(のシングルトンなインスタンス)。



TestWebService(_viver-test-web-service._tcp)というサービスが、VRRPを使って複数のノードと冗長化/負荷分散構成を組むというシナリオを机上の空論してみる。

LoadBalancer(_viver-load-balancer._tcp)は、VIVERのAPIに対して、ConfigurationService(_viver-configuration-service._tcp)を検索するように依頼する。見つかったら(と言うか見つかるまでブロックする)、そのノードに対して設定ファイルを要求する。設定ファイルには、LoadBalancerがバランスするべき実サーバーセットの名前と、それぞれのセットで使うべきVRRPの仮想IPアドレスとVIDが書かれている。たとえば、:TestWebService => 10。(あと、たぶんVRRPサービスにkeepalivedの設定ファイルのテンプレートが渡されている)

LoadBalancerはVRRPサービスをlocal_inject(依存性を注入)し、自ホストで動いているVRRPサービスへの参照を取得する。そのVRRPサービスに対して、設定ファイルに書かれている仮想IPアドレスとVIDのVRRPセットに参加するように要求する。VRRPは要求を処理し、VRRPに参加する。


以上で冗長化。続いて負荷分散。


TestWebService(_viver-test-web-service._tcp)は、VIVERのAPIに対して、_viver-load-balancer._tcpが見つかったら知らせる(メソッドを呼ぶ)ように依頼する。そのメソッドが呼ばれたら、_viver-load-balancer._tcpIPアドレスを解決し、_viver-load-balancer._tcpが動いているホストへの参照を得る(druby://ipaddr:port)。そしてそのホストのVIVER APIを呼び、:LoadBalancerへの参照を得る(dRuby)。最後にそのLoadBalancerのaddRealServcer(server_set_name, real_server_reference)メソッドを呼ぶ(dRuby)。server_set_nameには:TestWebServiceを、real_server_referenceには、VIVERのAPIで自ホストへの参照(druby://ipaddr:port)を渡す。



VIVERのAPIで実装する必要があるのは、_Service._Protocolを検索(ブラウズ)するメソッド、サービスごとに_Service._Protocolをパブリッシュするフレームワーク、local_injection、ホスト自体のオブジェクト(DRbObject)、そのオブジェクトのgetService(service_name)メソッド。

で、これは割と普通に実装できそう。mDNS+DNS-SDには、AvahiのD-Busインターフェース+RAAのruby-dbusか、RAAのnet-mdnsというライブラリが使えそう。local_injectionは前に実装したものがある。dRubyは本買ってきて、今勉強中。Ringを使おうとする(+例外処理もがんばる)と難しそうだけど、DRbだけなら何とかなりそう。



一番最初のノードはどうやって起動するかという問題がある。HDDに記録しておくか、LiveCDでも使うか、適当にPCを持ち込むとかするしかない。これはどうしようもないか。一度最初のノードでストレージサービスとConfigurationServiceを起動してしまえば、あとは自動で何とかできる。



たとえば設定を変更したくなった(クラスタの構成を変えたくなった)とする。まずコンソールとして適当なノードを作る。ノートPCでもなんでも。そこでirbを動かして、_viver-configuration-serivce._tcpを探す。見つかったら、IPアドレスを解決し、そのホストへの参照を得る。そのホストのgetService(service_name) APIを呼んで、ConfigurationServiceへの参照を得る。changeConfiguration(configuratoin)を呼ぶ。と、管理と監視は分散オブジェクトで。



実際に動いたらおもしろそう。




設定ファイルを書いてみる。

def VRRP.config_vid(vid, file, network)
	{:vid => vid, :template => include(file), :network => network}
end

BOOT_SHARE_VRRP = 10
CONTENT_SHARE_VRRP = 11
TESTWEBSERVICE_BALANCE_VRRP = 12

standard_net = {
	'command' => 'dhcp eth0',
	:network => {
		'lan0' => 'eth0',
	}
}

storeGroup = NodeClass.new
storeGroup.config = {
	:NetworkManager => standard_net,
	:Storage => {
		['/dev/sda1', '/mnt/boot', 'ext3', 'rw'],
		['/dev/sda2', '/mnt/content', 'ext3', 'rw']
	},
	:NFS => {
		'boot_share' => {
			:path => '/mnt/content',
			:replicate => {
				:method => 'rsync+vrrp',
				:vrrp => [BOOT_SHARE_VRRP, 'vrrp.erb', 'lan0'],
			}
		},
		'content_share' => {
			:path => '/mnt/boot',
			:replicate => {
				:method => 'rsync+vrrp'
				:vrrp => [CONTENT_SHARE_VRRP, 'vrrp.erb', 'lan0'],
			}
		}
	},
	:BootService => {
		'web' => {
			:method => 'rsync',
			:path => '/mnt/boot/web',
			'rootsize' => '400m',
		}
	}
}

node('kame',   hwaddr("00:16:cb:8f:e3:04")) = storeGroup.new
node('tokage', hwaddr("00:16:cb:8f:e3:05")) = storeGroup.new


balancerGroup = NodeClass.new
balancerGroup.config = {
	:NetworkManager => standard_net,
	:DSRBalance => {
		'testwebservice' => {
			:replicate => [TESTWEBSERVICE_BALANCE_VRRP, 'vrrp.erb', 'lan0']
		}
	}
}
node('blctest', hwaddr("00:16:cb:8f:e3:06")) = balancerGroup.new


testWebServiceGroup = NodeClass.new
testWebServiceGroup.config = {
	:NetworkManager => standard_net,
	:Storage => {
		'content' => ['content_share', '/var/www', :NFS, 'rw'],
	}
	:ApacheServer => {
		'lan0:80' => {
			:template => 'httpd.conf.erb',
			:balancer => 'testwebservice',
		}
	}
}

node(:adhoc, :default) = webServiceGroup.new

うーむ。これは微妙。