Thank you for reading this post, don't forget to subscribe!
RabbitMQ кластер это сразу несколько сервисов, у которых общие пользователи, настройки и даже очереди. Сервисы могут добавляться и удаляться на лету, располагаться на разных краях континента, но для подключённого клиента они будут выглядеть как один большой RabbitMQ сервис. Это хорошо для горизонтального мастшабирования — когда клиентов становится так много, что одиночному брокеру уже не справиться.
Кластеризация — это не то же самое, что репликация и high availability. В первом и втором случае уход одного из узлов в оффлайн никак не повлияет на доступность данных и работу сервиса в принципе. В кластере же узлы не взаимозаменяемы. Да, пользователи и настройки действительно будут дублироваться на каждом их них, где бы тех не создавали. Но очереди сообщений — нет. Так что если какой-то хост ушёл в оффлайн, то его очереди пойдут следом.
Как создать RabbitMQ кластер
Есть несколько способов, но мы будем создавать руками. Чтобы объединить несколько узлов в кластер, нужно удивительно мало телодвижений. А именно — два:
- Всем узлам будущего кластера нужно задать одинаковую Erlang cookie.
- У всех узлов, кроме случайного первого, нужно вызвать команду rabbitmqctl join_cluster .
Erlang cookie — это просто строка, которая в никсовых системах хранится в /var/lib/rabbitmq/.erlang.cookie. Когда cookie одинаковый, узлы понимают, что теперь они товарищи, и могут общаться. Команда join_cluster принимает один параметр — имя узла, с которым будем заводить кластер, и завершает союз. Обычно имя похоже на rabbit@hostname .
Подготавливаем RabbitMQ узлы в Docker
Так как для кластера нужно хотя бы два независимых хоста, самый простой способ получить их — запустить два Docker контейнера с RabbitMQ внутри. В Docker Hub на выбор есть сразу два образа: rabbitmq и rabbitmq:management. Я буду использовать второй, потому что в нём уже есть вэб-админка.
Еще один момент. Чтобы контейнеры могли общаться между собой, они должны находиться в общей сети, иметь внятные имена, да и Erlang cookie тоже надо как-то передать. Проще всего это сделать через docker-compose . Если создать файл конфигурации, то он сам будет присваивать имена, разбираться с сетью, передавать параметры, запускать сервисы, и т.п.
Конфигурируем docker-compose
Как всегда, конфигурация для docker-compose берется из файла docker-compose.yml. Им и займёмся:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
version: '2'
services:
rabbit:
image: rabbitmq:management
hostname: rabbit
ports:
- "15672:15672"
environment:
- RABBITMQ_ERLANG_COOKIE='mysecret'
hamster:
image: rabbitmq:management
hostname: hamster
ports:
- "15673:15672"
environment:
- RABBITMQ_ERLANG_COOKIE='mysecret'
|
Он чуть-чуть длиннее, чем стандартный hello world, но честно-честно, такой же элементарный. Я объясню, что в нём задано:
- Два сервиса (контейнера), которые после запуска превратятся в RabbitMQ узлы с именами rabbit и hamster. Хомяк (hamster) имеет очень опосредованное отношение к кроликам (rabbit), но вы не представляете, как утомительно гуглить кроличьи семейства ночью.
- Имена контейнерных хостов (hostname) заданы явно. Они будут фигурировать в именах RabbitMQ узлов и лучше бы им быть читабельнее.
- Так как RabbitMQ вэб-админка будет выглядывать из контейнера через порт 15672, я открою его и со стороны хоста. Но контейнеров два, так что второй, с хомяком, будет доступен снаружи через порт 15673.
- В официальный образ rabbitmq можно передать Erlang cookie в качестве переменой окружения — RABBITMQ_ERLANG_COOKIE, и тогда не нужно делать это руками через .erlang.cookie файл. Очень удобно. Свою cookie я назвал ‘mysecret’, но подошло бы что угодно.
Запускаем сервисы
docker-compose.yml готов, так что можно запускать:
1
2
3
4
5
6
7
|
docker-compose up
#Creating network "cluster_default" with the default driver
#Creating cluster_hamster_1
#…
#hamster_1 |
#hamster_1 | RabbitMQ 3.6.5. Copyright © 2007-2016 Pivotal Software, Inc.
#.….……
|
Будет очень много логов, среди которых легко не заметить, что оба RabbitMQ сервиса успешно запустились. Прежде чем объединить их в кластер, стоит проверить, работает ли вэб-админка.
Порт мы уже знаем — 15672 для rabbit и 15673 для hamster, но IP адрес зависит от версии Docker и операционной системы. Это будет либо localhost, либо тот IP, который вернёт docker-machine ip или boot2docker ip . Логин и пароль от админки — guest:guest.
Ну что, админка от rabbit работает, и я уверен, что со второй тоже всё хорошо.
Запускаем кластер
Время делать кластер. Второй и последний шаг в создании кластера — вызов команды rabbitmqctl join_cluster на всех брокерах, кроме первого. В нашем случае «все, кроме первого» — это hamster (или rabbit — смотря, кто больше нравится). docker-compose постарался сделать имя контейнера уникальным и добавил свой префикс, так что прежде чем зайти в него, нужно узнать настоящее имя:
1
2
3
4
5
|
docker-compose ps
# Name Command
#---------------------------------------------------
#cluster_hamster_1 docker-entrypoint.sh rabbi …
#cluster_rabbit_1 docker-entrypoint.sh rabbi …
|
cluster_hamster_1. Могло быть и хуже. Заходим:
1
2
|
docker exec -ti cluster_hamster_1 bash
#root@hamster:/#
|
И творим кластер:
1
2
3
4
5
6
|
rabbitmqctl stop_app
#Stopping node rabbit@hamster …
rabbitmqctl join_cluster rabbit@rabbit
#Clustering node rabbit@hamster with rabbit@rabbit …
rabbitmqctl start_app
#Starting node rabbit@hamster …
|
Та-дам! Кластер готов. Проверим, изменилось ли что в админке:
Теперь rabbit говорит, что он — на самом деле это два узла: rabbit и hamster. Это действительно кластер.
Кластерные настройки
Создадим чего-нибудь на одном из узлов. Например, на rabbit. Какую-нибудь очередь сообщений, (‘demoqueue’), и пользователя (‘rabbit’):
создаём очередь
создаём нового пользователя
Теперь идём на админку hamster’а — 127.0.0.1:15673, и смотрим, что творится там. Оказывается, новая очередь вместе с новым пользователем при помощи богов стека TCP/IP перенеслись и сюда:
очереди сообщений
Возле имени очереди есть пометка, что на самом деле она хостится на узле по имени rabbit, но пользователь — самый что ни на есть настоящий.
пользователи
Теперь попробуем еще одну штуку — остановим rabbit:
1
2
3
4
|
$ docker exec -ti cluster_rabbit_1 bash
root@rabbit:/$ rabbitmqctl stop_app
# Stopping node rabbit@rabbit …
root@rabbit:/$
|
Если снова посмотреть на пользователей hamster’а, то ничего особо там не изменится — всё еще две штуки. Но вот возле demoqueue появилась пометка down.
demoqueue is down
Это именно то, о чём я упоминал сначала. Пользователи и настройки копируются между узлами. Очереди — нет.
И еще одно наблюдение — в кластере нет понятия «главного» узла. Мы только что остановили контейнер, к которому подключались «остальные» (целая одна штука), но кластер — всё еще кластер. Все узлы равны, и их можно останавливать и запускать в любом порядке. Кроме одного исключения. Если останавливать все сервисы кластера по одному, то тот, который остановился последним, должен стартовать первым, когда мы начнём включать их назад.
Итого
Кластеризация — это очень неблагодарная задача, если пытаться написать её самому. Но создать кластер с RabbitMQ — вообще не проблема. Нужно просто передать строку-параметр и выполнить одну команду.
Кроме создания кластера руками или через RabbitMQ плагины есть еще один способ: в Docker Hub и github добрые люди выкладывают образы, которые уже сконфигурированы для работы в кластере. Например, docker-rabbitmq-cluster. Кроме Erlang cookie параметра они принимают на вход имя RabbitMQ контейнера, с которым нужно объединиться. Так что создать целый кластер машин можно через docker-compose.yml файл и одну команду.