RabbitMQ(2.8向け)のHAクラスタ構成

RabbitMQ Highly Available Queues and Clustering using Amazon EC2 という AWS 環境での RabbitMQ クラスタ構築に関する面白い記事を見かけた。

知識の再確認のため、以下をメモ。

  1. RabbitMQ のクラスタの構築
  2. HA:Active-Standby(Shared Nothing)
  3. HA:Active-Standby(Shared Storage)
  4. HA:Active-Active(Mirrored Queue)

RabbitMQ のクラスタリングの特徴

  • Erlang/OTPの 分散フレームワークをベースにしている。
  • ノード間でCookie(/var/lib/rabbitmq/.erlang.cookie)を同じにする
  • ノード名(デフォルトはホスト名)をクラスタ間で重複しないようにする
  • キューのメタ情報とキューが管理されているノードの場所はクラスタの各ノードで共有される
  • キューの実データは管理している1ノードにしか存在しない
  • 各ノードはRAMノードとDiskノードのどちらかを選べる
  • Diskノードはメタ情報をRAMだけでなくディスクにも保存する。
  • クラスタ全体で少なくとも1ノードはDiskノードでなければいけない。
  • クラスタ内のすべてのDiskノードが落ちている場合、多くの更新系処理が行えなくなる

事前準備

WebUI を有効にする

クラスタステータスはWebUI から確認するとわかりやすいので有効にしておく。http://HOST:55672/ のアドレスから管理画面にアクセスできる(デフォルトのID/password は guest/guest)

george@u1204a:~/rabbitmq_server-2.8.7$ sudo rabbitmq-plugins list
[ ] amqp_client                       2.8.7
[ ] eldap                             2.8.7-gite309de4
[ ] erlando                           2.8.7
[ ] mochiweb                          2.3.1-rmq2.8.7-gitd541e9a
[ ] rabbitmq_auth_backend_ldap        2.8.7
[ ] rabbitmq_auth_mechanism_ssl       2.8.7
[ ] rabbitmq_consistent_hash_exchange 2.8.7
[ ] rabbitmq_federation               2.8.7
[ ] rabbitmq_federation_management    2.8.7
[ ] rabbitmq_jsonrpc                  2.8.7
[ ] rabbitmq_jsonrpc_channel          2.8.7
[ ] rabbitmq_jsonrpc_channel_examples 2.8.7
[ ] rabbitmq_management               2.8.7
[ ] rabbitmq_management_agent         2.8.7
[ ] rabbitmq_management_visualiser    2.8.7
[ ] rabbitmq_mochiweb                 2.8.7
[ ] rabbitmq_shovel                   2.8.7
[ ] rabbitmq_shovel_management        2.8.7
[ ] rabbitmq_stomp                    2.8.7
[ ] rabbitmq_tracing                  2.8.7
[ ] rfc4627_jsonrpc                   2.8.7-gita5e7ad7
[ ] webmachine                        1.9.1-rmq2.8.7-git52e62bc
$ sudo rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
  mochiweb
  webmachine
  rabbitmq_mochiweb
  amqp_client
  rabbitmq_management_agent
  rabbitmq_management
Plugin configuration has changed. Restart RabbitMQ for changes to take effect.

$ sudo /etc/init.d/rabbitmq-server restart

Cookie を揃える

各ノードで /var/lib/rabbitmq/.erlang.cookie を同じにする。

1. RabbitMQのクラスタの構築

3ノード(u1204a, u1204b, u1204c)のクラスタを組んでみる

初期状態

u1204a だけの状態

rabbitmq@u1204a:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204a ...
[{nodes,[{disc,[rabbit@u1204a]}]},{running_nodes,[rabbit@u1204a]}]
...done.

RAMノードのクラスタを追加

cluster の引数に JOIN 先ノード名(rabbit@u1204a)を指定。

$ rabbitmqctl cluster rabbit@u1204a
Clustering node rabbit@u1204b with [rabbit@u1204a] ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl start_app
Starting node rabbit@u1204b ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204b ...
[{nodes,[{disc,[rabbit@u1204a]},{ram,[rabbit@u1204b]}]},
 {running_nodes,[rabbit@u1204a,rabbit@u1204b]}]
...done.

diskノードをクラスタに追加

RAM ノードの場合とことなり、cluster の引数に自分自身のノード名(rabbit@u1204c)も含める

rabbitmq@u1204c:~$ rabbitmqctl cluster rabbit@u1204a rabbit@u1204c
Clustering node rabbit@u1204c with [rabbit@u1204a,rabbit@u1204c] ...
...done.
rabbitmq@u1204c:~$ rabbitmqctl start_app
Starting node rabbit@u1204c ...
...done.
rabbitmq@u1204c:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204c ...
[{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]},
 {running_nodes,[rabbit@u1204a,rabbit@u1204b,rabbit@u1204c]}]
...done.

Web UI を確認

ノードをひとつ落とす

ノード u1204c を落とします。-n 引数で操作対象のノード名を指定します。

rabbitmq@u1204a:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204a ...
[{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]},
 {running_nodes,[rabbit@u1204b,rabbit@u1204c,rabbit@u1204a]}]
...done.
rabbitmq@u1204a:~$ rabbitmqctl -n rabbit@u1204c stop_app
Stopping node rabbit@u1204c ...
...done.
rabbitmq@u1204a:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204a ...
[{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]},
 {running_nodes,[rabbit@u1204b,rabbit@u1204a]}]
...done.

running_nodes から u1204c が消えています。

クラスタからノードを外す

u1204b をクラスタから外します。rabbitmqctl stop したあとで  rabbitmqctl reset します。

rabbitmq@u1204b:~$ rabbitmqctl stop_app
Stopping node rabbit@u1204b ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl reset
Resetting node rabbit@u1204b ...
...done.

rabbitmq@u1204a:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204a ...
[{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]}]},
 {running_nodes,[rabbit@u1204c,rabbit@u1204a]}]
...done.

RAM ノードとして追加されていた u1204b がノード一覧から消えています。

よくあるエラーパターン

ノード操作時のよくあるパターンをまとめてみた。

stop_app するまえに reset した場合

$ rabbitmqctl reset
Resetting node rabbit@u1204a ...
Error: mnesia_unexpectedly_running
$ rabbitmqctl stop_app
Stopping node rabbit@u1204a ...
...done.

クッキーがjoin先クラスタと違っている場合

$ rabbitmqctl cluster rabbit@u1204a
Clustering node rabbit@u1204b with [rabbit@u1204a] ...
Error: unable to connect to node rabbit@u1204b: nodedown

DIAGNOSTICS
===========

nodes in question: [rabbit@u1204b]

hosts, their running nodes and ports:
- u1204b: [{rabbit,40144},{rabbitmqctl3334,51254}]

current node details:
- node name: rabbitmqctl3334@u1204b
- home dir: /var/lib/rabbitmq
- cookie hash: 2ePLuBjG+y15B4w0gBRFwg==

Erlang のバージョンが異なっている場合

わかりにくいエーラが出力される。
http://rabbitmq.1065348.n5.nabble.com/Unable-to-cluster-td12282.html

$ rabbitmqctl cluster rabbit@u1204a
Clustering node rabbit@u1204c with [rabbit@u1204a] ...
Error: {unable_to_join_cluster,
           [rabbit@u1204a],
           {aborted,
               {function_clause,
                   [{mnesia_schema,cs2list,
                        [{cstruct,schema,set,
                             [rabbit@u1204b],
                             [rabbit@u1204a],
                             [],0,read_write,false,[],[],false,schema,
                             [table,cstruct],
                             [],[],
                             {{1351,317797,344820},rabbit@u1204a},
                             {{11,0},{rabbit@u1204b,{1351,318892,577271}}}}]},
                    {mnesia_schema,do_merge_schema,1},
                    {mnesia_tm,apply_fun,3},
                    {mnesia_tm,execute_transaction,5},
                    {mnesia_schema,schema_coordinator,3}]}}}

RabbitMQのバージョンが異なる場合


$ rabbitmqctl cluster rabbit@u1204a
Clustering node rabbit@u1204b with [rabbit@u1204a] ...
Error: {version_mismatch,[add_ip_to_listener,exchange_event_serial,
                          exchange_scratch,gm,ha_mirrors,mirrored_supervisor,
                          remove_user_scope,semi_durable_route,topic_trie,
                          user_admin_to_tags,add_queue_ttl,
                          multiple_routing_keys],
                         [add_ip_to_listener,exchange_event_serial,
                          exchange_scratch,gm,ha_mirrors,mirrored_supervisor,
                          remove_user_scope,semi_durable_route,topic_trie,
                          topic_trie_node,user_admin_to_tags,add_queue_ttl,
                          multiple_routing_keys]}

マイナーバージョンだけが異なる場合は、管理画面で警告メッセージが表示される。


参照

2. High Availability : Active-Standby 構成(Shared Nothing)

※画像は RabbitMQ In Action から

HAProxy を利用して、お手軽に Shared Nothing な Active-Standby 構成を構築。

Active-Standby 構成(Shared Nothing)の特徴

  • フェイルオーバー時の接続先変更をHAProxyで行う
  • データは共有されておらず 各 RabbitMQ で閉じている
  • Active のデータが壊れていても、Standby機には影響なし
  • RabbitMQ のバージョンがActive-Standby で異なっていても問題ない。
  • Activeノードが復活すれば、キューは再び Active 側に振り分けられるようになる。

“RabbitMQ In Action” では1つのマシン内に HAProxyと ノード名とポートをかえた2つの RabbitMQ を起動して、Active-Standby を構築していた。

以下の /etc/haproxy/haproxy.cfg では HAProxy と u1204a/u1204c の 2つの RabbitMQ がそれぞれ別マシンであることを前提にしている。

global
        log 127.0.0.1   local0
        log 127.0.0.1   local1 notice
        maxconn 4096
        user haproxy
        group haproxy
        daemon

defaults
        log     global
        mode    tcp
        option  tcplog
        option  dontlognull

listen  rabbitmq_local_cluster 127.0.0.1:5672
        mode tcp
        balance roundrobin
        server  u1204a u1204a:5672 check inter 5000 rise 2 fall 3
        server  u1204c u1204c:5672 backup check inter 5000 rise 2 fall 3

listen  private_monitoring :8101
        mode http
        option httplog
        stats enable
        stats uri /stats
        stats refresh 5s

listen  private_monitoring :8101
        mode http
        option httplog
        stats enable
        stats uri /stats
        stats refresh 5s

あとは u1204a を $ rabbitmqctl stop_app/start_app しつつキューを投げると、failover 時の処理を確認できる。

HAProxy の Web 管理画面は次のURL で確認可能 : http://HOST:8101/stats

キャプチャの赤枠を見るとわかるように、ノード u1204a は Ack が”Y(es)”, ノード u1204c は Bck が”Y(es)” になっている。

参照

3. High Availability : Active-Standby 構成(Shared Storage)

分散ストレージを利用して Active-Standby な HA 構成を組む。

※画像は RabbitMQ In Action から

RabbitMQ のサイトのドキュメントでは、この構成の代わりに Active-Active を使えと目立つように書かれている。

High availability with Pacemaker and DRBD

This page documents a legacy technique for achieving active-passive high availability with RabbitMQ. Active-active mirrored queues are easier to use and do not impose a delay at failover.

http://www.rabbitmq.com/pacemaker.html

Active-Standby 構成(Shared Storage)の特徴

  • 各RabbitMQ のノード名と Cookie を揃えておく
  • DRBD を共有ディスクに利用
  • Corosync をクラスタ制御に利用
  • pacemaker で RabbitMQ と DRBD クラスタのリソース管理をする
  • 1+1構成と2+1構成の両方を解説

未検証

参照

4. High Availability : Active-Active構成

active-active HA は RabbitMQ 2.6 で導入された。

※画像は RabbitMQ In Action から

active-active HA の特徴

  • キューの “x-ha-policy” 属性を “all”(または “node”)で宣言する。
    • all: This policy ensures that a mirror of the queue will exist on every node in the cluster.
    • nodes: This policy allows you to specify the exact nodes on which you wish mirrors to exist.
  • 非active-active HA で宣言されたキューを active-active にすることはできない
  • ノードの追加はあとからでもOK

mirrored-queue を構築
まずは 2 diskノードなクラスタを組む。

rabbitmq@u1204a:~$ rabbitmqctl stop_app
Stopping node rabbit@u1204a ...
...done.
rabbitmq@u1204a:~$ rabbitmqctl reset
Resetting node rabbit@u1204a ...
...done.
rabbitmq@u1204a:~$ rabbitmqctl  start_app
Starting node rabbit@u1204a ...
...done.

2台目を disk ノードで追加

rabbitmq@u1204b:~$ rabbitmqctl stop_app
Stopping node rabbit@u1204b ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl reset
Resetting node rabbit@u1204b ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl cluster rabbit@u1204a rabbit@u1204b
Clustering node rabbit@u1204b with [rabbit@u1204a,rabbit@u1204b] ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl start_app
Starting node rabbit@u1204b ...
...done.
rabbitmq@u1204b:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204b ...
[{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]},
 {running_nodes,[rabbit@u1204a,rabbit@u1204b]}]
...done.

キューを追加

rabbitmq@u1204a:~$ python publish.py
publish complete
rabbitmq@u1204a:~$ python publish.py
publish complete

Web管理画面でキューを確認

キューの Parameters に “HA” の文字がある。

キューの詳細を見ると、Parameters に “x-ha-policy” があり、Node と Mirrors に active-active のノード名がある。

片方を落とす
u1204a を落として片肺運転にする

rabbitmq@u1204a:~$ rabbitmqctl  stop_app
Stopping node rabbit@u1204a ...
...done.

rabbitmq@u1204b:~$ rabbitmqctl cluster_status
Cluster status of node rabbit@u1204b ...
[{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]},
 {running_nodes,[rabbit@u1204b]}]
...done.

ノード「rabbit@u1204b」だけが running 状態。

u1204b だけの片肺状態でキューを追加

rabbitmq@u1204b:~$ python publish.py
publish complete
rabbitmq@u1204b:~$ rabbitmqctl list_queues
Listing queues ...
test_queue      3
...done.

u1204a を復活させる

rabbitmq@u1204a:~$ rabbitmqctl  start_app
Starting node rabbit@u1204a ...
...done.
rabbitmq@u1204a:~$ rabbitmqctl  cluster_status
Cluster status of node rabbit@u1204a ...
[{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]},
 {running_nodes,[rabbit@u1204b,rabbit@u1204a]}]
...done.
rabbitmq@u1204a:~$ rabbitmqctl  list_queues
Listing queues ...
test_queue      3
...done.

キューを確認すると、3つと正しいが、管理画面では何やらメッセージが表示されている。

  • 青(+0) :  There are no synchronised mirrors
  • 赤(+1) :  Unsynchronised mirrors: rabbit@u1204a

u1204a ノードの復活後も、キューの同期が行われていない。
試しに、この状態で u1204b を停止させて、u1204a でキューを確認すると

rabbitmq@u1204b:~$ rabbitmqctl  stop_app
Stopping node rabbit@u1204b ...
...done.
rabbitmq@u1204a:~$ rabbitmqctl  list_queues
Listing queues ...
test_queue      0
...done.

理由は Highly Available Queues#Unsynchronised Slaves に書かれてある。

A node may join a cluster at any time. Depending on the configuration of a queue, when a node joins a cluster, queues may add a slave on the new node. At this point, the new slave will be empty: it will not contain any existing contents of the queue, and currently, there is no synchronisation protocol.

Thus a newly added slave provides no additional form of redundancy or availability of the queue’s contents until the contents of the queue that existed before the slave was added have been removed.

http://www.rabbitmq.com/ha.html#unsynchronised-slaves

ノードの復活はクラスタから見ればノードの追加と同じ扱い。
ノード追加前のキューがさばかれないと、同期は行われない仕組みになっている。

Mirrored Queue 用のコード

from pika.adapters import BlockingConnection
connection = BlockingConnection()
channel = connection.channel()
def callback(ch, method, properties, body):
print " [x] Received %r" % (body,)
channel.basic_consume(callback,
queue='test_queue',
no_ack=True)
channel.start_consuming()
view raw consume.py hosted with ❤ by GitHub
# http://karlgrz.blogspot.jp/2012/10/rabbitmq-highly-available-queues-and.html
from pika.adapters import BlockingConnection
from pika import BasicProperties
connection = BlockingConnection()
channel = connection.channel()
client_params = {"x-ha-policy": "all"}
exchange_name = 'public'
queue_name = 'test_queue'
routing_key = 'test_routing_key'
channel.exchange_declare(exchange=exchange_name, type='topic')
channel.queue_declare(queue=queue_name, durable=True, arguments=client_params )
channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key=routing_key)
channel.basic_publish(exchange=exchange_name, routing_key=routing_key, body='testing mirroring!', properties=BasicProperties(content_type="text/plain", delivery_mode=2))
# delivery_mode 1 : nonpersistent
# delivery_mode 2 : persistent
print "publish complete"
connection.close()
view raw publish.py hosted with ❤ by GitHub

参照

One thought on “RabbitMQ(2.8向け)のHAクラスタ構成

Leave a reply to HA Proxy を使って Sensu の RabbitMQ を HA 構成にしてみた | cloudpack技術情報サイト Cancel reply