Как и обещал в прошлый раз, сегодня мы попробуем какую-нибудь из продвинутых фич RabbitMQ. Например, кластеризацию.
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 (C) 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.
Это именно то, о чём я упоминал сначала. Пользователи и настройки копируются между узлами. Очереди — нет.
И еще одно наблюдение — в кластере нет понятия «главного» узла. Мы только что остановили контейнер, к которому подключались «остальные» (целая одна штука), но кластер — всё еще кластер. Все узлы равны, и их можно останавливать и запускать в любом порядке. Кроме одного исключения. Если останавливать все сервисы кластера по одному, то тот, который остановился последним, должен стартовать первым, когда мы начнём включать их назад.
Итого
Кластеризация — это очень неблагодарная задача, если пытаться написать её самому. Но создать кластер с RabbitMQ — вообще не проблема. Нужно просто передать строку-параметр и выполнить одну команду.
Кроме создания кластера руками или через RabbitMQ плагины есть еще один способ: в Docker Hub и github добрые люди выкладывают образы, которые уже сконфигурированы для работы в кластере. Например, docker-rabbitmq-cluster. Кроме Erlang cookie параметра они принимают на вход имя RabbitMQ контейнера, с которым нужно объединиться. Так что создать целый кластер машин можно через docker-compose.yml файл и одну команду.
Спасибо за статью.
Подскажите, довадилось ли сталкиваться с проблемой network partition , и если сталкивались, как ее решить ?
Пожалуйста.
Сожалею, но не сталкивался
копириование очередей задаётся политикой
rabbitmqctl set_policy ha-all «» ‘{«ha-mode»:»all»,»ha-sync-mode»:»automatic»}’