かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

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 をほぼそのまま利用しましたので、この記事には変更した点だけ記載しコード全ては記述しません。分かりやすいサンプルでした。作者の方には感謝です。

参照したサイト・書籍

  1. Clustering Guide
    https://www.rabbitmq.com/clustering.html

  2. Highly Available (Mirrored) Queues
    https://www.rabbitmq.com/ha.html

  3. RabbitMQ の分散構成はどうするのが良さそうか?
    http://krdlab.hatenablog.com/entry/2016/05/14/225230

  4. Part 3: RabbitMQ Best Practice for High Availability
    https://www.cloudamqp.com/blog/2018-01-09-part3-rabbitmq-best-practice-for-high-availability.html

  5. pardahlman/docker-rabbitmq-cluster
    https://github.com/pardahlman/docker-rabbitmq-cluster

  6. Cluster RabbitMQ in Docker
    http://fellowdeveloper.se/2017/05/24/cluster-rabbitmq-in-docker/

  7. haproxy
    https://hub.docker.com/_/haproxy/

  8. HAProxy
    http://www.haproxy.org/

  9. HAProxy Documentation Converter
    https://cbonte.github.io/haproxy-dconv/

  10. 多機能プロクシサーバー「HAProxy」のさまざまな設定例
    https://knowledge.sakura.ad.jp/8084/

  11. 多機能なロードバランサとして使える多機能プロクシサーバー「HAProxy」入門
    https://knowledge.sakura.ad.jp/8078/

  12. ロードバランサーの管理
    https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/load_balancer_administration/

  13. OpenStack Documentation - HAProxy
    https://docs.openstack.org/ja/ha-guide/controller-ha-haproxy.html

  14. mminks/haproxy-docker-logging
    https://github.com/mminks/haproxy-docker-logging

  15. RafPe/docker-haproxy-rsyslog
    https://github.com/RafPe/docker-haproxy-rsyslog

  16. Spring BootでRabbit MQクラスタを利用する
    https://qiita.com/yoshidan/items/7ef893a7ab7ac0f2884b

目次

  1. Docker Compose で RabbitMQ の Clustering 環境を構築する
  2. 動作確認

手順

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 が実行されません。

f:id:ksby:20181202103200p:plain

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.7haproxy:1.8.14-alpine に変更しました。
  • rabbitmq_exporter の RABBIT_USER, RABBIT_PASSWORD の値は .env に定義した RABBITMQ_DEFAULT_USER, RABBITMQ_DEFAULT_PASS を使用するように変更しました。

docker-compose up -d コマンドを実行して起動します。

f:id:ksby:20181202105159p:plain

IntelliJ IDEA の Docker Plugin でログを見ると以下のように出力されていました。

f:id:ksby:20181202105510p:plain f:id:ksby:20181202105614p:plain f:id:ksby:20181202105746p:plain f:id:ksby:20181202110030p:plain

HAProxy は全然ログが出ていませんでした。ログを出したい場合には mminks/haproxy-docker-loggingRafPe/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 downdocker-compose up -d コマンドを実行します。

f:id:ksby:20181202112025p:plain

IntelliJ IDEA の Docker Plugin で haproxy-rsyslog コンテナのログを見ると HAProxy のログが出ていました。

f:id:ksby:20181202112205p:plain

RabbitMQ の管理コンソールを見ると「Nodes」のところに3台表示されていました。

f:id:ksby:20181202113359p:plain

Grafana で見ると Clustering している3台のサーバが全て表示されていました。管理コンソールでもないのに自動でサーバが認識されるんですね。。。

f:id:ksby:20181202113204p:plain

動作確認

Tomcat を起動して動作を確認します。Spring Actuator の health check を見ると rabbitmq が認識されています。Redis と違い、RabbitMQ の Clustering 構成は表示は何も変わりませんでした。

f:id:ksby:20181202131705p:plain

ブラウザから http://localhost:8080/ にアクセスして「貸出希望書籍 CSV ファイルアップロード」から貸出希望書籍を登録すると結果のメールが返ってきます。

f:id:ksby:20181202131950p:plain f:id:ksby:20181202132025p:plain

RabbitMQ の管理コンソールを見るとメッセージが送受信されていることが分かります。

f:id:ksby:20181202132130p:plain

rabbitmq1 のサーバを停止してみます。管理コンソールを見ると rabbitmq1 が Node not running と表示されます。

f:id:ksby:20181202132313p:plain

HAProxy の方でも rabbitmq1 の DOWN が検知されています。

f:id:ksby:20181202132515p:plain

「貸出希望書籍 CSV ファイルアップロード」から貸出希望書籍を登録すると、エラーにならず結果のメールが返ってきました。

f:id:ksby:20181202132638p:plain

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
初版発行。