RubyのCassandraクライアントで指定したIPに接続できない怪現象。
rubyのcassandraクライアントから別サーバのcassandraに接続する場合に、指定したIPに接続されずローカルホストに接続してしまう怪現象が発生してハマったのでメモ。
(正直、Forkwellのパブリケーションに追加したい衝動に駆られて書きましたw)
環境
○サーバ1
・cassandra 1.0.8
ip: 192.168.0.11
keyspace: ExampleKeyspace
column family: ExampleCF
※optionの指定なしで作成
○サーバ2
・ruby1.9.3
・cassandra-0.12.1
・thrift-0.7.0
・thrift_client-0.7.1
現象確認
まず、サーバ2からサーバ1のcassandraにcassandra-cliから接続できることを確認。
$ cassandra-cli -h192.168.0.11 -kExampleKeyspace
Connected to: "Test Cluster" on 192.168.0.11/9160
Welcome to Cassandra CLI version 1.0.8Type 'help;' or '?' for help.
Type 'quit;' or 'exit;' to quit.[default@ExampleKeyspace]
問題なし。
次は、Rubyから接続してみる。
こんなスクリプトを用意して、
require 'rubygems'
require 'cassandra'client = Cassandra.new('ExampleKeyspace', '192.168.0.11:9160')
client.insert('ExampleCF', SimpleUUID::UUID.new.to_s, {})
スクリプトを実行。
$ ruby insert.rb
/usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift-0.7.0/lib/thrift/transport/socket.rb:53:in `rescue in open': CassandraThrift::Cassandra::Client::TransportException
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift-0.7.0/lib/thrift/transport/socket.rb:36:in `open'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift-0.7.0/lib/thrift/transport/framed_transport.rb:37:in `open'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift_client-0.7.1/lib/thrift_client/connection/socket.rb:11:in `connect!'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift_client-0.7.1/lib/thrift_client/abstract_thrift_client.rb:105:in `connect!'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift_client-0.7.1/lib/thrift_client/abstract_thrift_client.rb:144:in `handled_proxy'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/thrift_client-0.7.1/lib/thrift_client/abstract_thrift_client.rb:60:in `describe_keyspace'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/cassandra.rb:177:in `schema'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/columns.rb:31:in `column_family_property'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/columns.rb:20:in `column_name_class_for_key'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/columns.rb:12:in `column_name_class'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/helpers.rb:21:in `extract_and_validate_params'
from /usr/local/ruby-1.9.3-p125/lib/ruby/gems/1.9.1/gems/cassandra-0.12.1/lib/cassandra/cassandra.rb:442:in `insert'
from test.rb:6:in `'
エラー発生。
原因
ソースを追ってみると、cassandraクライアントのデフォルトの挙動は、インスタンス生成時に指定したサーバに書き込みに行くのではなく、
指定したサーバのクラスタリング情報を読み取り、リング上のIP1つに対して書き込みに行く模様。
サーバ1からcassandraのリング情報を取得してみる。
$ nodetool -hlocalhost ring
Address DC Rack Status State Load Owns Token
127.0.0.1 datacenter1 rack1 Up Normal 752.7 KB 100.00% 17515755121036504136650759168839277356
つまり、リング上のIPが127.0.0.1になっているから、サーバ2から127.0.0.1へ書き込みにいっているということっぽい。
サーバ2にはcassandraが立ち上がっていないため、ネットワークエラーになる。
対象ソース:
cassandra-0.12.1/lib/cassandra/cassandra.rb1052 def all_nodes
1053 if @auto_discover_nodes && !@keyspace.eql?("system")
1054 temp_client = new_client
1055 begin
1056 ips = (temp_client.describe_ring(@keyspace).map {|range| range.endpoints}).flatten.uniq
1057 port = @servers.first.split(':').last
1058 ips.map{|ip| "#{ip}:#{port}" }
1059 ensure
1060 temp_client.disconnect!
1061 end
1062 else
1063 @servers
1064 end
1065 end
対策
解決策としては、ぱっと2つ思いつく。
1. リング情報にcassandraサーバのプライベートIPを設定する。
2. リング情報ではなく指定したサーバ情報にアクセスするように設定する。
対策1はクラスタリングへのアクセスをCassandraに任せる場合、対策2はサーバ2とCassandraの間に別のロードバランサーなどを挟む場合かな?
(2の場合Consistency LevelをQUORUMとかALLにしたらどうなるんだろう?試してない。)
解決策1:
cassandra.yamlの設定を変更。
175 # Address to bind to and tell other Cassandra nodes to connect to. You
176 # _must_ change this if you want multiple nodes to be able to
177 # communicate!
178 #
179 # Leaving it blank leaves it up to InetAddress.getLocalHost(). This
180 # will always do the Right Thing *if* the node is properly configured
181 # (hostname, name resolution, etc), and the Right Thing is to use the
182 # address associated with the hostname (it might not be).
183 #
184 # Setting this to 0.0.0.0 is always wrong.
185 listen_address: 192.168.0.11 # ローカルIPを指定
再起動してリング上のIPを確認。
$ nodetool -h localhost ring
Address DC Rack Status State Load Owns Token
192.168.0.11 datacenter1 rack1 Up Normal 765.67 KB 100.00% 17515755121036504136650759168839277356
解決策2:
cassandraクライアントのdisable_node_auto_discovery!メソッドを呼べば、
インスタンス生成時に指定したIPのcassandraサーバに直接接続するようになる。
require 'rubygems'
require 'cassandra'client = Cassandra.new('ExampleKeyspace', '192.168.0.11:9160')
client.disable_node_auto_discovery! # ここ
client.insert('ExampleCF', SimpleUUID::UUID.new.to_s, {})