Tech Sketch Bucket of Technical Chips by TIS Inc.

マルチマスタ型アーキテクチャ & 分散内部DNSのススメ

Pocket

クラウド&ビッグデータ時代の到来に伴い、 Apache Cassandraelasticsearch といった、「全てのノードが対称な役割を果たす」ことで「単一障害点が無く」「ノード追加に比例して性能が向上する」マルチマスタ型アーキテクチャに注目が集まっています。しかしこれらを利用する際、「クライアントはどのノードに接続すればいいのか?」というのが結構悩ましかったりします。今回は、分散内部DNS Murakumo を用いた解決方法を紹介します。


マルチマスタ型アーキテクチャ

言葉を尽くすより、図を見るほうがよっぽどわかりやすいでしょう。Cassandraを例にして説明します。

cassandra-ring.png

この図のように、マルチマスタ型アーキテクチャでは全てのノードが同じ役割を果たします。例えば Apache Hadoop のNameNodeのような、「クラスタを構成するデータノードの状態とデータ断片の位置情報」等の"メタデータだけを管理・提供する役割のノード"はありません。クラスタの状態やデータ断片の位置情報は全てのノードが共有しているため、クライアントはクラスタに参加しているどのノードと通信しても同じ結果を得ることができます。そのためマルチマスタ型アーキテクチャは対障害性が高く、ノード追加に性能が比例しやすいのです。(レプリケーション数や照合数の設定にもよりますが。)

さてどのNodeに接続しよう?

ところがクライアントがマルチマスタ型アーキテクチャのクラスタへ接続する場合、この「メタデータを管理するノードが無い」という特性が問題になります。クライアントは一体どのノードに向けて、接続要求を投げれば良いのでしょうか?
この問題を解決するために、いくつかの解決方法が思いつきます。

クライアント自身もクラスタに参加し、自分自身に問い合わせる

シンプルな解決方法です。クライアント自身がクラスタに参加すれば、自分自身のクラスタプロセスに接続すれば良いので、悩む必要がありません。
ただしアプリケーションサーバがCassandraクラスタに接続するような場合、アプリケーションサーバ自身がCassandraクラスタに参加する、というのは役割分担やメンテナビリティから見るとヘンなので、この解決方法は取りづらくなります。

キモチワルクネ?

クライアントがクラスタノードのリストを覚えておき、生きてるノードへ接続する

これもシンプルな解決方法です。例えば Cassandra FAQ では以下のように紹介されています。MySQL Clusterを作る場合、JDBCドライバ(Connecter/J)に複数の接続先を設定したり、これらのメタデータを管理できるMySQL Proxyを利用したりできますが、そのようなイメージですね。

You can maintain a list of contact nodes (all or a subset of the nodes in the cluster), and configure your clients to choose among them.

この解決方法でもたしかに動きますが、クラスタノードの追加・削除時に全クライアントの設定を変更せねばなりませんし、クラスタノードのリスト上には存在するがサービスを提供しないノードに接続要求を出してしまった場合に備えてタイムアウトと再接続のロジックをクライアントに実装する必要があります。また複数のクライアントが複数の接続を維持する場合、特定ノードに接続が集中しないように接続先ノードを振り分けるロジックも実装する必要があります。

イケテナイナ

ブロードキャストやマルチキャストを送信し、応答してきたクラスタノードへ接続する

美しい解決方法です。クラスタノードを特定のサブネットへまとめるなりマルチキャストアドレスを付与するなりしておき、クライアントがクラスタへ接続する前にブロードキャストやマルチキャストを送信し、応答してきたクラスタノードへ接続すると。一度接続が確立したならば、以降はそのノードと直接通信すれば良いので、ネットワーク負荷も問題にならないでしょう。

ただしクラスタに所属する各ノードには、「ノード間で協調動作しながら、そのクライアントの接続に最適なノードを返す」という「応答デーモン」を常駐させておく必要があります。elasticsearchには Zen Discovery という機能があり、マルチキャストによるクラスタノード探索ができますが、Cassandraには該当する機能が無いため、自分で作らなければなりません。
またパブリッククラウド上にクラスタを構築する場合、ブロードキャストやマルチキャストが利用できない場合があります。例えば AWSでは、ブロードキャストもマルチキャストも利用することができません。

タイオウシテレバナ

ロードバランサを用いる

クライアントから見たときの接続先はロードバランサの外向きIPに固定してしまい、実際の接続先はロードバランサが配下のクラスタノードに振り分ける、という形です。例えば Cassandra FAQ では以下のように紹介されています。

Deploy a load-balancer, proxy, etc.

・・・せっかく複数ノードで対障害性を向上させ、負荷分散を行なっているというのに、ロードバランサを導入してSPOFや性能ボトルネックを作り出してどうすんだ?という気がしてなりません。が、ことAWS VPCでInternal Elastic Load Balancingを用いる場合、ELBはアクセスに応じて性能上限が向上し単一障害点にはならず、配下のインスタンスの負荷状況を判断して適切に接続を振り分ける、と言われています。ので、アリ・・・かもしれません。

やってみよう!

elb1.png

ということで、上図のようなCassandraクラスタをAWSに構築し、検証して見ました。

AWS VPC Internal ELBの設定

elb2.png elb3.png

Cassandraは内部的に 3つのTCPポートを用います 。デフォルト設定では、クライアントがクラスタに接続する際には9160、クラスタノードの内部情報を得るために7199を用います。そこでそれらのTCPポートを配下のインスタンスへ転送し、TCP:9160でインスタンスの死活チェックを行うようにInternal ELBを設定します。

動いた!

elb4.png

elb5.png

Internal ELBに対してCassandraクラスタ情報を問い合わせると、クラスタノードの情報が得られました。Cassandraへのデータ検索や登録も、Internal ELBに投げればOKです。

ノード障害時も問題ないよ!

elb7.png

クラスタノードダウン時でも、Internal ELBが障害ノードを自動的に切り離すため、Cassandraクラスタへの接続は継続して行えます。

シカシッ

Internal ELBのタイムアウトがネックになる

AWSのInternal ELBは、無通信の場合 60秒でコネクションを切断します 。現時点では、この設定を変更する手段がありません。そのため、クライアントがCassandraクラスタに接続したままクエリを投げずに60秒以上経過すると、以降はExceptionが発生してしまうのです。

elbB.png

クライアントがこの例外を補足して再接続すれば良いのですが、プログラム的にも応答性能的にも微妙な感じです。。。

モウスコシナンダガナァ

DNSを上手く使える・・・か?

実は Cassandra FAQ では、以下のように「DNSラウンドロビン」の利用が推奨されていたりします。

Use round-robin DNS and create a record that points to a set of contact nodes (recommended).

ことをAWSに限定すれば、クラスタノードのインスタンスにTagを付け、EC2 API経由で「特定のタグがついたインスタンスのリスト」を得ることによって、クラシックなDNSラウンドロビンのようにクラスタに参加するノードのリストを取得することができます。「クライアントがクラスタノードのリストを覚えておく方法」よりは洗練されていますが、接続先ノードの振り分け、接続要求のタイムアウトと再接続等のロジックをクライアントに実装する必要があるのは同じです。

・・・いっそのこと、「対障害性が高く」「ノードのヘルスチェックも行え」「複数ノードを振り分けて名前解決する機能に特化した内部DNS」を導入し、適切に振り分けられた単一レコードを返す高度なDNSラウンドロビンを行わせるほうが早い気がしてきました。

そのような内部DNSの実装として、 Murakumo があります。

Murakumoとは

Murakumoは、クックバッドで開発された内部DNS用ミドルウェアです。Cassandraと同様単一障害点の無いマルチマスタ型アーキテクチャを持っており、Ruby実行環境があれば簡単にインストールできます。
詳細は 開発された菅原さんのスライド を確認してください。

やってみよう!

murakumo5.png

ということで、上図のようなMurakumoクラスタとCassandraクラスタをAWSに構築し、検証して見ました。クライアントがMurakumoクラスタに所属していることがポイントです。

AWS VPC を内部DNSを利用するように設定する

murakumo6.png

AWS VPCではVPC内に所属するインスタンスの名前解決を行うために 専用のDNSサービスが提供されています 。このVPC内で利用するDNSサービスの設定を「優先的に自分自身に名前解決を行わせる」ように変更することで、Murakumoに名前解決を行わせることができるようになります。

Murakumoノードの設定(クライアント)

murakumo7.png

Cassandraクライアントには、自分自身のIPアドレスとホスト名のみ設定すればOKです。Murakumoクラスタに参加した段階で、この情報がMurakumoクラスタに参加するすべてのノードに伝播します。

Murakumoノードの設定(Cassandraノード)

murakumo8.png

murakumo9.png

各Cassandraクラスタノードは、自分自身のIPアドレスとホスト名だけでなく、DNSラウンドロビンで用いられるエイリアス名(上記の例では"cluster")を登録します。またエイリアスにはCassandraプロセスのヘルスチェックを定義します。(上記の例ではTCP:9160をチェック)

Murakumoクラスタの確認

murakumoC.png

Murakumoクラスタを起動してみると、Murakumoクラスタに参加している全てのノードの名前情報が共有されていることがわかります。

動いた!

murakumoD.png

murakumoE.png

Cassandraクラスタのエイリアス名に対して接続すると、高度なDNSラウンドロビンによってイイカンジのノードのIPアドレスが返されて、クライアントとそのノードとの接続が直接確立します。(ラウンドロビンの戦略は、Murakumoの設定ファイルで定義することができます)

Internal ELBの場合のように途中でロードバランサが挟まったりしませんので、タイムアウトによるコネクション切断も起きません。

ノード障害時も問題ないよ!

murakumoF.png

murakumoG.png

Cassandraクラスタのノードがダウンしている場合、Murakumoクラスタが障害を検知し、エイリアスの名前解決時にそのCassandraノードが選択されなくなります。よってCassandraクラスタの一部のノードがダウンしている場合でも、生きているノードへ接続できるため、Cassandraクラスタへの接続は継続して行えます。

スンバラシイィィ!

最後に

Murakumoはまだ開発中のため、実際の本番環境へ適用するためには様々な検証をする必要があります。しかしこれからのクラウド&ビッグデータ時代に向け、このような「内部DNSを利用する」という解決策があることを知っておくと、アーキテクチャ設計の幅が広がるでしょう。

エンジニア採用中!私たちと一緒に働いてみませんか?