Беспростойная миграция RabbitMQ в Kubernetes

gjnlhrbjftwmdqsokcczdh6x_6o.png

RabbitMQ — написанный на языке Erlang брокер сообщений, позволяющий организовать отказоустойчивый кластер с полной репликацией данных на несколько узлов, где каждый узел может обслуживать запросы на чтение и запись. Имея в production-эксплуатации множество кластеров Kubernetes, мы поддерживаем большое количество инсталляций RabbitMQ и столкнулись с необходимостью миграции данных из одного кластера в другой без простоя.

Данная операция была необходима нам как минимум в двух случаях:

  1. Перенос данных из кластера RabbitMQ, находящегося не в Kubernetes, в новый — уже «кубернетезированный» (т.е. функционирующий в pod«ах K8s) — кластер.
  2. Миграция RabbitMQ в рамках Kubernetes из одного namespace в другой (например, если контуры разграничены пространствами имён, то для переноса инфраструктуры из одного контура в другой).


Предлагаемый в статье рецепт ориентирован на ситуации (но вовсе не ограничен ими), в которых есть старый кластер RabbitMQ (например, из 3 узлов), находящийся либо уже в K8s, либо на каких-то старых серверах. С ним работает приложение, размещённое в Kubernetes (уже там или в перспективе):

0tnvvwspr5t-cwbvwzae5p2h9re.png

… и перед нами стоит задача его миграции в новый production в Kubernetes.

Сначала будет описан общий подход к самой миграции, а уже после этого — технические детали по её реализации.

Алгоритм миграции


Первый, предварительный, этап перед какими-либо действиями — проверка, что в старой инсталляции RabbitMQ включён режим высокой доступности (HA). Причина очевидна — мы ведь не хотим потерять каких-либо данных. Чтобы осуществить эту проверку, можно зайти в админку RabbitMQ и во вкладке Admin → Policies убедиться, что установлено значение ha-mode: all:

fzsjcc5rvcdzqvzrwklrkomoqny.png

Следующий шаг — поднимаем новый кластер RabbitMQ в pod«ах Kubernetes (в нашем случае, например, состоящий из 3 узлов, но их число может быть и другим).

После этого мы объединяем старый и новый кластеры RabbitMQ, получая единственный кластер (из 6 узлов):

brdswxtjfd97eg1jc6yuy0m6w6c.png

Инициируется процесс синхронизации данных между старым и новым кластерами RabbitMQ. После того, как все данные синхронизируются между всеми узлами в кластере, мы можем переключить приложение на использование нового кластера:

mhwupy0059dj_m8xn-guonlq_i0.png

После этих операций достаточно вывести из кластера RabbitMQ старые узлы, и переезд можно считать завершённым:

_2_qcrxnt_1j4hzazxfsagyr9i8.png

Эту схему мы неоднократно применяли у нас в production. Однако для собственного удобства реализовали её в рамках специализированной системы, распространяющей типовые конфигурации RMQ на множествах кластеров Kubernetes (для тех, кому любопытно: речь идёт про addon-operator, о котором мы совсем недавно рассказывали). Ниже будут представлены отдельно взятые инструкции, которые каждый может применить на своих инсталляциях, чтобы попробовать предлагаемое решение в действии.

Пробуем на практике


Требования


Реквизиты очень просты:

  1. Кластер Kubernetes (подойдет и minikube);
  2. Кластер RabbitMQ (может быть и развернут на bare metal, и сделан как обычный кластер в Kubernetes из официального Helm-чарта).


Для описанного ниже примера я развернул RMQ в Kubernetes и назвал его rmq-old.

Подготовка стенда


1. Скачаем Helm-чарт и немного отредактируем его:

helm fetch --untar stable/rabbitmq-ha


Для удобства задаём пароль, ErlangCookie и делаем политику ha-all, чтобы по умолчанию очереди синхронизировались между всеми узлами кластера RMQ:

rabbitmqPassword: guest
rabbitmqErlangCookie: mae9joopaol7aiVu3eechei2waiGa2we
definitions:
policies: |-
  {
    "name": "ha-all",
    "pattern": ".*",
    "vhost": "/",
    "definition": {
      "ha-mode": "all",
      "ha-sync-mode": "automatic",
      "ha-sync-batch-size": 81920
    }
  }


2. Устанавливаем чарт:

helm install . --name rmq-old --namespace rmq-old


3. Заходим в админку RabbitMQ, создаём новую очередь и добавляем несколько сообщений. Они понадобятся для того, чтобы после миграции мы могли удостовериться, что все данные сохранились и мы ничего не потеряли:

depb_oq1aa_rpozeaymiik6acqm.png

Тестовый стенд готов: у нас есть «старый» RabbitMQ с данными, которые нужно перенести.

Миграция кластера RabbitMQ


1. Для начала развернём новый RabbitMQ в другом пространстве имён с такими жеErlangCookie и паролем для пользователя. Для этого проделаем описанные выше операции, изменив конечную команду по установке RMQ на следующую:

helm install . --name rmq-new --namespace rmq-new


2. Теперь требуется объединить новый кластер со старым. Для этого заходим в каждый из pod«ов нового RabbitMQ и выполняем команды:

export OLD_RMQ=rabbit@rmq-old-rabbitmq-ha-0.rmq-old-rabbitmq-ha-discovery.rmq-old.svc.cluster.local && \
  rabbitmqctl stop_app && \
  rabbitmqctl join_cluster $OLD_RMQ && \
  rabbitmqctl start_app


В переменной OLD_RMQ находится адрес одного из узлов старого кластера RMQ.

Эти команды остановят текущий узел нового кластера RMQ, присоединят его к старому кластеру и снова запустят.

3. Кластер RMQ из 6 узлов готов:

1k1zcrq3-hn-k_k6y10z-ktteqc.png

Необходимо подождать, пока сообщения синхронизируются между всеми узлами. Нетрудно догадаться, что время синхронизации сообщений зависит от мощностей железа, на котором развёрнут кластер, и от количества сообщений. В описываемом сценарии их всего 10, поэтому данные синхронизировались моментально, но при достаточно больших количестве сообщений синхронизация может длиться часами.

Итак, статус синхронизации:

32rbglvg5f9vnxjaee0ewii0pmo.png

Здесь +5 означает, что сообщения уже находятся ещё на 5 узлах (кроме того, что указан в поле Node). Таким образом, синхронизация прошла успешно.

4. Остается лишь переключить в приложении адрес RMQ на новый кластер (конкретные действия здесь зависят от используемого вами технологического стека и другой специфики приложения), после чего можно попрощаться со старым.

Для последней операции (т.е. уже после переключения приложения на новый кластер) заходим на каждый узел старого кластера и выполняем команды:

rabbitmqctl stop_app
rabbitmqctl reset


Кластер «забыл» о старых узлах: можно удалять старый RMQ, на чём переезд будет закончен.

Примечание: Если вы используете RMQ с сертификатами, то принципиально ничего не меняется — процесс переезда будет осуществляться точно так же.

Выводы


Описанная схема подходит практически для всех случаев, когда нам нужно перенести RabbitMQ или просто переехать в новый кластер.

В нашем случае сложности возникали только один раз, когда к RMQ обращались из множества мест, а у нас не было возможности везде поменять адрес RMQ на новый. Тогда мы запускали новый RMQ в том же пространстве имён с одинаковыми лейблами, чтобы он попадал под уже существующие сервисы и Ingress«ы, а при запуске pod«а руками манипулировали лейблами, удаляя их в начале, чтобы на пустой RMQ не попадали запросы, и добавляя их обратно после синхронизации сообщений.

Такую же стратегию мы применяли при обновлении RabbitMQ на новую версию с изменённой конфигурацией — всё работало как часы.

P.S.


В качестве логического продолжения этого материала мы готовим статьи про MongoDB (миграция с железного сервера в Kubernetes) и MySQL (как мы готовим эту СУБД внутри Kubernetes). Они будут опубликованы в ближайшие месяцы.

P.P. S.


Читайте также в нашем блоге:

© Habrahabr.ru