お金をかけずにサーバーの勉強をしよう

RabbitMQのクラスタ化(前編)

2023年4月5日

メニューへ戻る

メッセージキューイングシステムの RabbitMQを2台のクラスタにして冗長化します。

仕事をしている時に、システム間の連携でよくセゾン情報システムズHULFTって製品を使っていました。

この製品はサーバー間でファイルを受け渡すことができるのですが、特に良かったのが正常受信時・異常受信時にそれぞれコマンド実行を設定できるところでした。

このおかげで、送信元サーバーの送信ジョブから受信先サーバーでのジョブ(大体は受信したファイルの取り込み)まで連続して実行できまして、大変便利だったのを覚えています。

考え方もシンプルで分かりやすかったのも良かったですね。

沢山の連携ファイルがある場合には「これがないと…」ってソフトでしたね。


一方で、夜間に1回しかデータ連携をせずオンラインになるまでにファイルの連携ができていれば良いというケースもままあり、ファイルサーバーで非同期にやりとりしても良いのではないか、有償ソフトを使わなくても良いのでは?と思うことがありました。

また、もともと送受信双方ともにデータは何かしらの開発言語で扱っているものなのだから「ファイル」という形式でなくても良いのでは?とも思いました。

そこで RabbitMQのようなメッセージブローカーの利用を考えるわけです。


しかし実際の運用を考えるに、メッセージブローカーやファイルサーバーのような第三者を介在させるとなると、そこの冗長性が気になります。
これは謂わば通信インフラですから動いているのが当たり前で、落ちてちゃ困るのです。

RabbitMQには幸いそれ自身にクラスタ化する機能がありますので、2つの RabbitMQを立ち上げて冗長化してみます。

※この手のソフトは偶数台でクラスタ化するとネットワーク断裂時にスプリットブレインという現象を起こすため、奇数台で構成するのが推奨されます。
ここでは実験のための環境として偶数台でやっています。

環境はこの通りです。
2023年4月3日時点での最新版です。

Ubuntu Server22.04.2
Erlang25.3
RabbitMQ3.11.13


Ubuntu Server 22.04のインストールについては
Ubuntu Linux Serverをインストール
Ubuntu Serverの初期設定

RabbitMQのインストールについては
RabbitMQでメッセージを受け渡し

にそれぞれ書いています。

ここで使う環境は、RabbitMQがインストールされているサーバーを2つ、メッセージを送るのと受け取る Lubuntu 22.04のクライアントです。

マシン名IPアドレス
1RabbitMQ1192.168.1.113
2RabbitMQ2192.168.1.114
3Lubuntu2204DHCP


Ubuntu Server に RabbitMQがインストールされている所からスタートです。

まず 2つの RabbitMQを連携させます。
手順はこちら。
Clustering Guide

クラスタ化させる方法はいくつかあるようですが、 rabbitmqctlコマンドを使う方法でやってみます。
ドキュメントではこの箇所です。
Clustering Transcript with rabbitmqctl

前提として RabbitMQがインストールされているサーバーではお仲間のサーバーをマシン名解決できるように、DNSなり hostsファイルなりが構成されている必要があります。

RabittMQ1・RabbitMQ2それぞれで、以下のエントリを /etc/hostsに加えています。

127.0.0.1 localhost
192.168.1.113 RabbitMQ1
192.168.1.114 RabbitMQ2


RabbitMQが複数稼働している環境では、それぞれの RabbitMQをノードネームと呼ぶ名前でお互いを識別しています。
(以降、RabbitMQが稼働しているサーバーを「ノード」と書きます)

当然ながら「一つの地球に一人ずつ一つ」よろしく、この名前は環境下でユニークになっていなければなりません。

ノードネームの規則は [プレフィックス@ホスト名]となっていて、プレフィックスは特に指定しなければ「rabbit」になります。

その結果、この実験環境ではプレフィックス指定をしていないので

となります。


クラスタを構成する複数のノード間に優先度はなく、それぞれが同じ立場です。

これらの間で設定はネットワークで共有されますが、クラスタ構成のデフォルト設定では、肝心のメッセージデータはどこか 1つのノードにあり、他のノードにアクセスすると RabbitMQのノード間通信でメッセージデータは共有される仕組みになっています。

これだとデータを保持しているノードが死んだ場合に保持していたデータが無くなってしまうので、ここではデータのレプリケーションの設定をして、クラスタの全ノードでメッセージデータを保持・同期されるようにしようと思います。


複数ノードを組み合わせてクラスタ化するのに、設定ファイルを書く方法もありますが、rabbitmqctlコマンドによる設定でいきます。

コマンドによるクラスタ構築の手順のありかが分かりづらかったのですが、こちらにありました。
Clustering Transcript with rabbitmqctl
これに従ってやっていきます。

RabbitMQを「普通の形」で起動します。
[RabbitMQ1][RabbtiMQ2]の両方でやります。

subro@RabbitMQ1:~$ sudo rabbitmq-server -detached
subro@RabbitMQ2:~$ sudo rabbitmq-server -detached

何も出ませんけど、良いのでしょう。

クラスタの状態を両方で確認します。
長いですが全部載せます。

subro@RabbitMQ1:~$ sudo rabbitmqctl cluster_status
Cluster status of node rabbit@RabbitMQ1 ...
Basics

Cluster name: rabbit@RabbitMQ1
Total CPU cores available cluster-wide: 2

Disk Nodes

rabbit@RabbitMQ1

Running Nodes

rabbit@RabbitMQ1

Versions

rabbit@RabbitMQ1: RabbitMQ 3.11.13 on Erlang 25.3

CPU Cores

Node: rabbit@RabbitMQ1, available CPU cores: 2

Maintenance status

Node: rabbit@RabbitMQ1, status: not under maintenance

Alarms

(none)

Network Partitions

(none)


Listeners
Node: rabbit@RabbitMQ1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@RabbitMQ1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0

Feature flags

Flag: classic_mirrored_queue_version, state: enabled
Flag: classic_queue_type_delivery_support, state: enabled
Flag: direct_exchange_routing_v2, state: enabled
Flag: feature_flags_v2, state: enabled
Flag: implicit_default_bindings, state: enabled
Flag: listener_records_in_ets, state: enabled
Flag: maintenance_mode_status, state: enabled
Flag: quorum_queue, state: enabled
Flag: stream_queue, state: enabled
Flag: stream_single_active_consumer, state: enabled
Flag: tracking_records_in_ets, state: enabled
Flag: user_limits, state: enabled
Flag: virtual_host_metadata, state: enabled

[RabbitMQ2]では、「RabbitMQ1」のところが「RabbitMQ2」に変わっているだけでしたので割愛します。


[RabbitMQ1]に[RabbitMQ2]を参加させる形で 2台を紐付けるため、[RabbitMQ2]での作業をします。

RabbitMQを止めます。

subro@RabbitMQ2:~$ sudo rabbitmqctl stop_app
Stopping rabbit application on node rabbit@RabbitMQ2 ...

止まりました。

設定を初期状態にします。

subro@RabbitMQ2:~$ sudo rabbitmqctl reset
Resetting node rabbit@RabbitMQ2 ...

できました。

[RabbitMQ1]とくっつけます。

subro@RabbitMQ2:~$ sudo rabbitmqctl join_cluster rabbit@RabbitMQ1
Clustering node rabbit@RabbitMQ2 with rabbit@RabbitMQ1
Error: unable to perform an operation on node 'rabbit@RabbitMQ1'. Please see diagnostics information and suggestions below.

Most common reasons for this are:

 * Target node is unreachable (e.g. due to hostname resolution, TCP connection or firewall issues)
 * CLI tool fails to authenticate with the server (e.g. due to CLI tool's Erlang cookie not matching that of the server)
 * Target node is not running

In addition to the diagnostics info below:

 * See the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more
 * Consult server logs on node rabbit@RabbitMQ1
 * If target node is configured to use long node names, don't forget to use --longnames with CLI tools

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

attempted to contact: [rabbit@RabbitMQ1]

rabbit@RabbitMQ1:
  * connected to epmd (port 4369) on RabbitMQ1
  * epmd reports node 'rabbit' uses port 25672 for inter-node and CLI tool traffic
  * TCP connection succeeded but Erlang distribution failed
  * suggestion: check if the Erlang cookie is identical for all server nodes and CLI tools
  * suggestion: check if all server nodes and CLI tools use consistent hostnames when addressing each other
  * suggestion: check if inter-node connections may be configured to use TLS. If so, all nodes and CLI tools must do that
   * suggestion: see the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more


Current node details:
 * node name: 'rabbitmqcli-267-rabbit@RabbitMQ2'
 * effective user's home directory: /var/lib/rabbitmq
 * Erlang cookie hash: FT9QutcKgpnZTLFc2LgNGw==

エラーになりました。

[Erlang cookie]の不一致と出ています。

RabitMQのクラスタを構成するノードは、この「Erlang cookie」というもので制御作業や動作の制限をしています(セキュリティのため)。

クラスタの全ノードで、「Erlang cookie」は同じにしないといけない所、それぞれインストール時に作られたファイルに設定されています。

Ubuntuの場合だと以下 2つのファイルになります。

rabbitmqctlコマンド用実行ユーザーのホームディレクトリの .erlang.cookie
サーバープロセス用/var/lib/rabbitmq/.erlang.cookie

[RabbitMQ1]と[RabbitMQ2]の /var/lib/rabbitmq/.erlang.cookie ファイルの中身はこのようでした。

subro@RabbitMQ1:~$ sudo cat /var/lib/rabbitmq/.erlang.cookie
NEFWMEEQKOQHVBNRLYIZ
subro@RabbitMQ2:~$ sudo cat /var/lib/rabbitmq/.erlang.cookie
CHJSBYCLKBDOEIEDTTFF

違っていましたので、[RabbitMQ2]に[RabbitMQ1]のファイルをコピーして上書きしました。

※このファイルはテキストファイルではあるのですが、オリジナルのファイル([RabbitMQ1]のファイル)には末尾に「\n」が入っていません。
これを真似て vimなどのテキストエディタで新規ファイルを作ったのですが、目に見える文字は同じながら末尾に「\n」が入ってしまい別物となってしまいました。

subro@RabbitMQ2:~$ sudo cat /var/lib/rabbitmq/.erlang.cookie
NEFWMEEQKOQHVBNRLYIZ

この後 rabbitmqctlコマンド実行時に、認証で拒否されるようになってしまったので、RabbitMQのサーバープロセスを再起動しました。

subro@RabbitMQ2:~$ sudo systemctl restart rabbitmq-server


では、改めて[RabbitMQ1]とくっつけます。

subro@RabbitMQ2:~$ sudo rabbitmqctl join_cluster rabbit@RabbitMQ1
Clustering node rabbit@RabbitMQ2 with rabbit@RabbitMQ1

今度は上手くいったようです。

RabbitMQを再開します。

subro@RabbitMQ2:~$ sudo rabbitmqctl start_app
Starting node rabbit@RabbitMQ2 ...

再開しました。


これで、クラスタになってるはずですが…。

再びクラスタの状態を両方で確認します。

subro@RabbitMQ1:~$ sudo rabbitmqctl cluster_status
Cluster status of node rabbit@RabbitMQ1 ...
Basics

Cluster name: rabbit@RabbitMQ1
Total CPU cores available cluster-wide: 4

Disk Nodes

rabbit@RabbitMQ1
rabbit@RabbitMQ2

Running Nodes

rabbit@RabbitMQ1
rabbit@RabbitMQ2

Versions

rabbit@RabbitMQ1: RabbitMQ 3.11.13 on Erlang 25.3
rabbit@RabbitMQ2: RabbitMQ 3.11.13 on Erlang 25.3

CPU Cores

Node: rabbit@RabbitMQ1, available CPU cores: 2
Node: rabbit@RabbitMQ2, available CPU cores: 2

Maintenance status

Node: rabbit@RabbitMQ1, status: not under maintenance
Node: rabbit@RabbitMQ2, status: not under maintenance

Alarms

(none)

Network Partitions

(none)

Listeners

Node: rabbit@RabbitMQ1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@RabbitMQ1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@RabbitMQ2, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@RabbitMQ2, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0

Feature flags

Flag: classic_mirrored_queue_version, state: enabled
Flag: classic_queue_type_delivery_support, state: enabled
Flag: direct_exchange_routing_v2, state: enabled
Flag: feature_flags_v2, state: enabled
Flag: implicit_default_bindings, state: enabled
Flag: listener_records_in_ets, state: enabled
Flag: maintenance_mode_status, state: enabled
Flag: quorum_queue, state: enabled
Flag: stream_queue, state: enabled
Flag: stream_single_active_consumer, state: enabled
Flag: tracking_records_in_ets, state: enabled
Flag: user_limits, state: enabled
Flag: virtual_host_metadata, state: enabled

変りましたね。
[RabbitMQ2]でも同じ結果になりました。

[RabbitMQ1][RabbitMQ2]がリストに上がるようになっています。

他に使えるCPU数がノードの足し上げになっていました。(ピンク行)
負荷分散もしているという事なのでしょう。


RabbitMQのクラスタ化(中編)」では、メッセージデータのレプリケーション設定と、クライアントとの間で実際にメッセージのやり取りをしてみたいと思います。