Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その22 )( Docker で RabbitMQ の環境を構築する2(RabbitMQ の Clustering 構成) )
概要
記事一覧はこちらです。
Spring Boot 1.5.x の Web アプリを 2.0.x へバージョンアップする ( その21 )( Docker で RabbitMQ の環境を構築する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- Docker で RabbitMQ の Clustering 環境を構築します。
- いろいろ調べてみたところ、RabbitMQ の Clustering 環境を構築するには HAProxy を使うようです。
- HAProxy を複数立ち上げて keepalived で冗長化した上で、HAProxy の backend に RabbitMQ の Clustering 環境を配置するというのが理想なのですが、RabbitMQ 以外に HAProxy + keepalived の冗長化構成まで対応していたらいつ終わるか分からないな。。。と思ったので、HAProxy(1台)+ RabbitMQ(3台)の構成にします。
- 今回は pardahlman/docker-rabbitmq-cluster をほぼそのまま利用しましたので、この記事には変更した点だけ記載しコード全ては記述しません。分かりやすいサンプルでした。作者の方には感謝です。
参照したサイト・書籍
Clustering Guide
https://www.rabbitmq.com/clustering.htmlHighly Available (Mirrored) Queues
https://www.rabbitmq.com/ha.htmlRabbitMQ の分散構成はどうするのが良さそうか?
http://krdlab.hatenablog.com/entry/2016/05/14/225230Part 3: RabbitMQ Best Practice for High Availability
https://www.cloudamqp.com/blog/2018-01-09-part3-rabbitmq-best-practice-for-high-availability.htmlpardahlman/docker-rabbitmq-cluster
https://github.com/pardahlman/docker-rabbitmq-clusterCluster RabbitMQ in Docker
http://fellowdeveloper.se/2017/05/24/cluster-rabbitmq-in-docker/HAProxy
http://www.haproxy.org/HAProxy Documentation Converter
https://cbonte.github.io/haproxy-dconv/多機能プロクシサーバー「HAProxy」のさまざまな設定例
https://knowledge.sakura.ad.jp/8084/多機能なロードバランサとして使える多機能プロクシサーバー「HAProxy」入門
https://knowledge.sakura.ad.jp/8078/ロードバランサーの管理
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/load_balancer_administration/OpenStack Documentation - HAProxy
https://docs.openstack.org/ja/ha-guide/controller-ha-haproxy.htmlmminks/haproxy-docker-logging
https://github.com/mminks/haproxy-docker-loggingRafPe/docker-haproxy-rsyslog
https://github.com/RafPe/docker-haproxy-rsyslogSpring BootでRabbit MQクラスタを利用する
https://qiita.com/yoshidan/items/7ef893a7ab7ac0f2884b
目次
手順
Docker Compose で RabbitMQ の Clustering 環境を構築する
.env に以下の設定を追加します。
.......... RABBITMQ_VERSION=3.7.8-management RABBITMQ_ERLANG_COOKIE=Uzkm93w5e1Lz8AcP RABBITMQ_DEFAULT_USER=rabbitmq RABBITMQ_DEFAULT_PASS=12345678 RABBITMQ_DEFAULT_VHOST=/
- 以下の行を追加します。
RABBITMQ_ERLANG_COOKIE=Uzkm93w5e1Lz8AcP
RABBITMQ_DEFAULT_USER=rabbitmq
RABBITMQ_DEFAULT_PASS=12345678
RABBITMQ_DEFAULT_VHOST=/
- Clustering 環境の RabbitMQ 全てに同じ RABBITMQ_ERLANG_COOKIE を設定する必要があるので、.env ファイルに定義してそれを使用するようにします。
- RABBITMQ_DEFAULT_USER, RABBITMQ_DEFAULT_PASS もついでに .env に移動します。
- いろいろなサンプルを見ると全て
RABBITMQ_DEFAULT_VHOST=/
を定義していたので、.env に追加して使用するようにします。
docker/rabbitmq ディレクトリを作成した後、その下に cluster-entrypoint.sh, haproxy.cfg を配置します。この2つのファイルは pardahlman/docker-rabbitmq-cluster にあるファイルをそのまま持ってきました。ただし改行コードは LF に変更します。改行コードを変更しないと cluster-entrypoint.sh が実行されません。
docker-compose.yml に RabbitMQ の Clustering 構成の設定と HAProxy の設定を追加します。
# 起動したコンテナに /bin/sh でアクセスする場合には以下のコマンドを実行する # docker exec -it rabbitmq /bin/sh # # 起動したコンテナの rabbitmq に rabbitmqctl で接続して管理コマンドを実行するには以下のコマンドを実行する # docker exec -it rabbitmq rabbitmqctl ... ############################################################################# # 単体 Redis サーバ # rabbitmq: # image: rabbitmq:${RABBITMQ_VERSION}-alpine # container_name: rabbitmq # hostname: rabbitmq # ports: # - "5672:5672" # - "15672:15672" # environment: # - RABBITMQ_DEFAULT_USER=rabbitmq # - RABBITMQ_DEFAULT_PASS=12345678 # ############################################################################# # RabbitMQ Clustering rabbitmq1: image: rabbitmq:${RABBITMQ_VERSION}-alpine container_name: rabbitmq1 hostname: rabbitmq1 environment: - RABBITMQ_ERLANG_COOKIE - RABBITMQ_DEFAULT_USER - RABBITMQ_DEFAULT_PASS - RABBITMQ_DEFAULT_VHOST rabbitmq2: image: rabbitmq:${RABBITMQ_VERSION}-alpine container_name: rabbitmq2 hostname: rabbitmq2 depends_on: - rabbitmq1 environment: - RABBITMQ_ERLANG_COOKIE volumes: - ./docker/rabbitmq/cluster-entrypoint.sh:/usr/local/bin/cluster-entrypoint.sh entrypoint: /bin/sh -c /usr/local/bin/cluster-entrypoint.sh rabbitmq3: image: rabbitmq:${RABBITMQ_VERSION}-alpine container_name: rabbitmq3 hostname: rabbitmq3 depends_on: - rabbitmq1 environment: - RABBITMQ_ERLANG_COOKIE volumes: - ./docker/rabbitmq/cluster-entrypoint.sh:/usr/local/bin/cluster-entrypoint.sh entrypoint: /usr/local/bin/cluster-entrypoint.sh haproxy: image: haproxy:1.8.14-alpine container_name: haproxy volumes: - ./docker/rabbitmq/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro depends_on: - rabbitmq1 - rabbitmq2 - rabbitmq3 ports: - "5672:5672" - "15672:15672" rabbitmq_exporter: image: kbudde/rabbitmq-exporter:latest container_name: rabbitmq_exporter ports: - "9419:9419" environment: - RABBIT_URL=http://${HOST_IP_ADDRESS}:15672 - RABBIT_USER=${RABBITMQ_DEFAULT_USER} - RABBIT_PASSWORD=${RABBITMQ_DEFAULT_PASS} - RABBIT_CAPABILITIES=bert,no_sort - PUBLISH_PORT=9419
- 記述している内容は基本的には pardahlman/docker-rabbitmq-cluster の通りなのですが、以下の点を変更しています。
- image は
rabbitmq:${RABBITMQ_VERSION}-alpine
に変更しました。RabbitMQ のバージョン番号は .env に定義した RABBITMQ_VERSION を利用し、かつ-alpine
のイメージを利用します。 - 各コンテナに
container_name: ...
の設定を追加しました。 - environment の設定は
=...
の部分を削除しました。.env に同名の環境変数を定義しており、=...
を記述しなくても環境変数の値がそのまま適用されます。 - volumes に記述する cluster-entrypoint.sh のホスト側のパスを
./docker/rabbitmq/cluster-entrypoint.sh
に変更しました。 - haproxy の image を
haproxy:1.7
→haproxy:1.8.14-alpine
に変更しました。
- image は
- rabbitmq_exporter の RABBIT_USER, RABBIT_PASSWORD の値は .env に定義した RABBITMQ_DEFAULT_USER, RABBITMQ_DEFAULT_PASS を使用するように変更しました。
docker-compose up -d
コマンドを実行して起動します。
IntelliJ IDEA の Docker Plugin でログを見ると以下のように出力されていました。
HAProxy は全然ログが出ていませんでした。ログを出したい場合には mminks/haproxy-docker-logging か RafPe/docker-haproxy-rsyslog を参考にすればよさそうです。
RafPe/docker-haproxy-rsyslog を利用してみます。docker-compose.yml に haproxy-rsyslog の設定を追加します。
haproxy: image: haproxy:1.8.14-alpine .......... haproxy-rsyslog: image: rafpe/docker-haproxy-rsyslog container_name: haproxy-rsyslog volumes: - ./docker/rabbitmq/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro depends_on: - haproxy
docker-compose down
、docker-compose up -d
コマンドを実行します。
IntelliJ IDEA の Docker Plugin で haproxy-rsyslog コンテナのログを見ると HAProxy のログが出ていました。
RabbitMQ の管理コンソールを見ると「Nodes」のところに3台表示されていました。
Grafana で見ると Clustering している3台のサーバが全て表示されていました。管理コンソールでもないのに自動でサーバが認識されるんですね。。。
動作確認
Tomcat を起動して動作を確認します。Spring Actuator の health check を見ると rabbitmq が認識されています。Redis と違い、RabbitMQ の Clustering 構成は表示は何も変わりませんでした。
ブラウザから http://localhost:8080/ にアクセスして「貸出希望書籍 CSV ファイルアップロード」から貸出希望書籍を登録すると結果のメールが返ってきます。
RabbitMQ の管理コンソールを見るとメッセージが送受信されていることが分かります。
rabbitmq1 のサーバを停止してみます。管理コンソールを見ると rabbitmq1 が Node not running
と表示されます。
HAProxy の方でも rabbitmq1 の DOWN が検知されています。
「貸出希望書籍 CSV ファイルアップロード」から貸出希望書籍を登録すると、エラーにならず結果のメールが返ってきました。
rabbitmq2, 3 の停止 → rabbitmq1 の起動を連続で行うと。。。、
- HAProxy の方で rabbitmq2, 3 の DOWN は認識されましたが、いつまで経っても rabbitmq1 の UP が認識されません。
- Web アプリケーションの方では、以下の TimeoutException のエラーが延々と出続けていました。画面も全く表示されません。
Caused by: java.util.concurrent.TimeoutException: null at com.rabbitmq.utility.BlockingCell.get(BlockingCell.java:77) at com.rabbitmq.utility.BlockingCell.uninterruptibleGet(BlockingCell.java:120) at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36) at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:494) at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:315) at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:1104) at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:1054) at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:994) at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:457) ... 9 common frames omitted
- rabbitmq1 を停止→起動し直しただけではまだ HAProxy の方で rabbitmq1 の UP を認識せず、rabbitmq2, 3 を起動すると rabbitmq1, 2, 3 の UP が認識されました。
どうも RabbitMQ の方で rabbitmq2 か 3 の master になっているサーバが rabbitmq1 に master を引き継げないまま落ちてしまうと、rabbitmq1 のコンテナを起動してもその中の RabbitMQ サーバが起動しないようです。RabbitMQ でほぼ同時にサーバが停止して master を別のサーバに引き継げなかった場合、slave のサーバだけを起動しても RabbitMQ は復旧せず、master のサーバを起動しないとダメなのでしょう。
master がきちんと引き継がれるようにする(全サーバを1度にダウンさせない)ことに注意してサーバを DOWN、UP させながらいろいろ試してみましたが、メッセージの送受信に問題が出ることはありませんでした。
とりあえず Clustering 構成には出来たようです。Queue 間でのメッセージのコピー?等も確認してみたかったのですが、どこか別にやることにします。
履歴
2018/12/02
初版発行。