Fedora ServerにおけるRabbitMQクラスター構築ガイド:SELinuxとfirewalldによる高可用性メッセージブローカーシステムの構築

Fedora tutorial - IT technology blog
Fedora tutorial - IT technology blog

実際の問題:Single-node RabbitMQと単一障害点

以前、シングルノードのRabbitMQサーバーが深夜2時にOOMキラーに落とされただけで、e-commerceの注文処理システムが完全に停止する場面を目の当たりにしました。キューにペンディングされていたメッセージはすべて消え、200以上のコンシューマープロセスを手動で再起動し、devチームが通常状態に復旧するまで3時間近くかかりました。

これはRabbitMQのバグではなく、アーキテクチャの問題です。単一障害点がある場合、問いは「落ちるかどうか」ではなく「いつ落ちるか」です。RabbitMQクラスターは複数ノード間でキューのメタデータをレプリケーションすることでこの問題を解決します。Quorum Queueを使えば、メッセージデータ自体もレプリケーションされるため、途中でノードがダウンしてもメッセージは失われません。

Fedoraをメインの開発マシンとして2年間使っており、パッケージの更新速度には満足しています。しかし、本番環境のFedora Serverにデプロイすると、SELinuxとfirewalldが予告なくトラブルを引き起こすことがあります。RabbitMQは特に「つまずきやすく」、多数のマイナーなポートを開放し、さまざまなパスへの書き込みが必要なため、デフォルトのSELinuxポリシーではカバーしきれないケースが生じます。

RabbitMQクラスターの代表的な3つのデプロイ方法

方法1:ベアメタルクラスター(OSに直接インストール)

各サーバーに直接rabbitmq-serverパッケージをインストールし、Erlangクッキーを同期してからrabbitmqctl join_clusterでクラスターに参加させます。この方法ではファイルディスクリプター制限、メモリウォーターマーク、各ファイルのSELinuxコンテキストまで、すべての詳細を細かくコントロールできます。

メリット:コンテナーのオーバーヘッドがなく、systemd/journald経由の監視が容易で、Prometheus node_exporterとの連携もスムーズです。Erlang 26はFedora 40の公式リポジトリに収録されているため、CentOS StreamのようにEPELや外部リポジトリを追加する必要はありません。

デメリット:SELinuxとfirewalldを手動で設定する必要があります。ローリングアップグレードには慎重な作業が求められます。開発環境の再現が難しい側面もあります。

方法2:永続ボリュームを使ったPodmanクラスター

rabbitmq:3-managementイメージを使用してポッドを作成するか、Podman Composeを利用します。PodmanはFedoraのデフォルト選択肢であり、Dockerよりもルートレスコンテナーのサポートが優れており、バックグラウンドで動作するデーモンも不要です。

メリット:ポータブルで、新しいイメージをプルするだけで簡単にアップグレードでき、コンテナーが分離されているためアプリケーションレベルでSELinuxを設定する必要がありません。

デメリット:外部に公開するポートのfirewalld設定は依然として必要です。複数のPodmanホスト間のネットワークにはVPNやオーバーレイネットワークなどの追加レイヤーが必要です。SELinuxラベル:zを付け忘れた永続ボリュームは、デバッグが厄介なパーミッションエラーを引き起こします。

方法3:RabbitMQ Cluster OperatorによるKubernetes

HelmでRabbitMQ Cluster Operatorをデプロイします。Operatorがクラスターへの参加・離脱、ローリングアップグレード、スケールイン・アウトを自動的に管理します。

メリット:完全自動化でセルフヒーリング機能があり、Kubernetesの監視スタックとの統合も優れています。

デメリット:Kubernetesのオーバーヘッドは無視できません。コントロールプレーンを正しく動かすだけで最低3つのワーカーノードが必要です。ベアメタルVPS上の小〜中規模ワークロードには明らかにオーバースペックです。

Fedora Serverにはどの方法を選ぶべきか

現在のインフラに応じて選択します:

  • VPSベアメタル2〜3台構成で最大限のコントロールが必要かつKubernetesなし → ベアメタルクラスター
  • Podmanインフラが既存、ポータビリティと再現性が必要 → Podmanクラスター
  • Kubernetes稼働中でエコシステムへの統合が必要 → K8s Operator

ここではベアメタルクラスターを解説します。VPS上のFedora Serverではこれが最も一般的なケースであり、最初に正しく設定しないとSELinuxとfirewalldが最も大きな摩擦を生む場面でもあります。

Fedora Serverへの3ノードRabbitMQクラスターの構築

ステップ0:ホスト名と/etc/hostsの準備

Fedora Server 40以降が3台必要です。今回は以下を使用します:

  • rmq1 — 192.168.1.10
  • rmq2 — 192.168.1.11
  • rmq3 — 192.168.1.12

3つのノードすべての/etc/hostsに追記します。RabbitMQクラスターはIPアドレスではなくホスト名でノードを識別するため、ここを間違えるとクラスター参加時の「node not found」エラーのほとんどの原因になります:

sudo tee -a /etc/hosts <<EOF
192.168.1.10 rmq1
192.168.1.11 rmq2
192.168.1.12 rmq3
EOF

宣言した名前と一致するホスト名を設定します:

# 各ノードでそれぞれ実行する
sudo hostnamectl set-hostname rmq1   # rmq2またはrmq3に適宜変更する

ステップ1:ErlangとRabbitMQのインストール

Fedora 40では公式リポジトリにErlang 26が収録されているため、CentOS StreamのようにEPELや外部リポジトリを追加する必要はありません:

# 3つのノードすべてで実行する
sudo dnf install -y erlang rabbitmq-server

# バージョンを確認する
erl -version
rabbitmqctl version

# サービスを有効化して起動する
sudo systemctl enable --now rabbitmq-server

ステップ2:Erlangクッキーの同期 — 最もよく見落とされるステップ

ErlangノードはErlangクッキーが同一でないと通信できません。クッキーは/var/lib/rabbitmq/.erlang.cookieに文字列として保存されています。クッキーが一致しないと「authentication failed」エラーが発生しますが、これはネットワークやファイアウォールのエラーと区別しにくく、初めてクラスターを設定する際に最もよくスキップされるステップです。

# rmq1上で:現在のクッキーを読み取る
sudo cat /var/lib/rabbitmq/.erlang.cookie
# 出力例: WKDXQABCMNOPQRSTUVWX

# 変更前にサービスを停止する
sudo systemctl stop rabbitmq-server

# rmq2とrmq3上で:rmq1のクッキーで上書きする
echo -n "WKDXQABCMNOPQRSTUVWX" | sudo tee /var/lib/rabbitmq/.erlang.cookie
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie

# 3つのノードすべてで再起動する
sudo systemctl start rabbitmq-server

ステップ3:RabbitMQ向けSELinuxの設定

FedoraにはRabbitMQクラスターが使用するすべてのポートに対応したSELinuxポリシーがデフォルトで用意されているわけではありません。ポート25672(Erlang distribution)と4369(EPMD)はしばしばブロックされますが、監査ログが明確でないためファイアウォールのエラーと混同しやすいです。

まず、SELinuxが何をブロックしているか確認します:

sudo ausearch -m avc -ts recent | grep rabbitmq
# または
sudo journalctl -u rabbitmq-server | grep -i denied

amqp_port_tタイプにポートを追加します:

# SELinuxポート管理ツールをインストールする
sudo dnf install -y policycoreutils-python-utils

# AMQP、Management UI、Erlang distribution、EPMDのポートを追加する
sudo semanage port -a -t amqp_port_t -p tcp 5672
sudo semanage port -a -t amqp_port_t -p tcp 15672
sudo semanage port -a -t amqp_port_t -p tcp 25672
sudo semanage port -a -t amqp_port_t -p tcp 4369

# 確認する
sudo semanage port -l | grep amqp

ポートを開放してもパーミッションエラーが発生する場合は、監査ログからカスタムSELinuxモジュールを作成します:

sudo ausearch -c 'beam.smp' --raw | audit2allow -M rabbitmq_custom
sudo semodule -i rabbitmq_custom.pp

# モジュールがロードされているか確認する
sudo semodule -l | grep rabbitmq

ステップ4:firewalldの設定

RabbitMQクラスターにはノード間で4つの主要ポートが必要です:

  • 4369/tcp — EPMD(Erlang Port Mapper Daemon)— ノード探索
  • 5672/tcp — AMQPプロトコル — コンシューマー/プロデューサーの接続
  • 25672/tcp — Erlang distribution — ノード間クラスター通信
  • 15672/tcp — Management Web UI(送信元IPを制限することを推奨)
# 3つのノードすべてでクラスター通信ポートを開放する
sudo firewall-cmd --permanent --add-port=4369/tcp
sudo firewall-cmd --permanent --add-port=5672/tcp
sudo firewall-cmd --permanent --add-port=25672/tcp

# Management UI — 内部ネットワークのみに制限する
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="15672" accept'

# リロードする
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports

ステップ5:クラスターへの参加

Erlangクッキーの同期とポートの開放が完了したら、node2とnode3をnode1に参加させます:

# rmq2上で:
sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@rmq1
sudo rabbitmqctl start_app

# rmq3上で:
sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@rmq1
sudo rabbitmqctl start_app

# 任意のノードからクラスターの状態を確認する
sudo rabbitmqctl cluster_status

正しい出力ではrunning_nodesに3つのノードがすべて表示されます:rabbit@rmq1、rabbit@rmq2、rabbit@rmq3。

ステップ6:Quorum Queueと管理者ユーザーの作成

Classic Mirrored QueueはRabbitMQ 3.9から非推奨になりました。代わりにQuorum Queueを使用します。Raftコンセンサスに基づくレプリケーションで、より安全で予測可能な動作を実現します。3ノードクラスターでは、Raftはコミットに過半数(2/3)が必要なため、1ノードがダウンしてもキューは正常に機能し続けます。

# Management Pluginを有効化する
sudo rabbitmq-plugins enable rabbitmq_management

# 管理者ユーザーを作成する — 本番環境ではguestを使用しないこと
sudo rabbitmqctl add_user admin StrongPassword123
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

# デフォルトのguestユーザーを削除する
sudo rabbitmqctl delete_user guest
# CLIでQuorum Queueを作成する
sudo rabbitmqadmin declare queue name=order_queue durable=true \
  arguments='{"x-queue-type":"quorum"}'

クラスターのテスト:あるノードからパブリッシュ、別のノードからコンシューム

Pythonで簡単に確認します — rmq2にパブリッシュし、rmq3からコンシュームします:

import pika

# rmq2からパブリッシュする
conn = pika.BlockingConnection(
    pika.ConnectionParameters(
        host='192.168.1.11',
        credentials=pika.PlainCredentials('admin', 'StrongPassword123')
    )
)
channel = conn.channel()
channel.queue_declare(
    queue='order_queue', durable=True,
    arguments={'x-queue-type': 'quorum'}
)
channel.basic_publish(exchange='', routing_key='order_queue', body='test-message')
conn.close()
print("Published")

次に、同様のコードで192.168.1.12(rmq3)からコンシュームします。Quorum Queueが3つのノード全体にデータをレプリケーションしているため、メッセージを読み取ることができます。rmq2を停止してから再度コンシュームしてみてください — キューは正常に動作し続けます。これが紙の上だけでなく、本物のHAです。

よくあるエラーと対処法

“Node ‘rabbit@rmq2’ not running” クラスター参加時 → /etc/hostsのホスト名を確認します。ホスト名の誤りが最大の原因です。sudo rabbitmqctl statusを実行して、ノードが自分自身をどのように認識しているか確認してください。

“Authentication failed” 参加時 → Erlangクッキーが一致していません。サービスを停止し、rmq1のクッキーを再コピーしてパーミッションを400に設定してから再起動します。

“Permission denied” firewalldでポートを開放しているのに → firewalldではなくSELinuxのブロックです。以前、firewalldのブロックだと思い込んで半日デバッグしましたが、実際にはSELinuxがbeam.smpのポート25672へのバインドを許可していませんでした。教訓:ausearchだけでなく/var/log/audit/audit.logを直接確認すること — ログがローテーションされると、ausearch -ts recentではコンテキストを見逃してしまいます。

ネットワーク分断後のクラスタースプリットブレイン → Quorum QueueはRaftを使用:過半数のノード(≥2/3)を持つパーティションが書き込みを継続し、残りのパーティションは自動的にブロックされます。データの不整合は発生しません — これがClassic Mirrored QueueからQuorum Queueへ移行すべき主な理由です。

Share: