mDNS+DNS-SDで分散メタネームサーバを作るアイディア
またしても書きながら考えるメモ。
dRuby+Ringか、mDNS+DNS-SDか。今はDNS-SDの方が良さそうな気がしている。なんというか普通にDNS-SDそのもの。ただしSRVレコードは勝手に定義。そう堅いこと言わずに。
mDNSは、DNSをマルチキャストで行う。いわゆるクライアントサーバー型ではない。個々のノードが、マルチキャストされてくるmDNS要求に対して、マルチキャストで応える。そしてキャッシュ。どうやらmDNSは、通常の(ユニ/エニーキャストの)DNSとは異なり、プッシュ型でレコードの変更を伝播させることができる。ココポイント。
DNS-SDは、DNSのSRVレコードと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._tcpのIPアドレスを解決し、_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
うーむ。これは微妙。