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

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

Эта статья продолжает наш недавний материал про миграцию RabbitMQ и посвящена MongoDB. Поскольку мы обслуживаем множество кластеров Kubernetes и MongoDB, пришли к естественной необходимости мигрировать данные из одной инсталляции в другую и делать это без простоя. Основные сценарии прежние: перенос MongoDB из виртуального/железного сервера в Kubernetes или же перенос MongoDB в рамках одного кластера Kubernetes (из одного пространства имён в другое).

Наш рецепт предназначен для случаев, когда функционирует старый кластер MongoDB (например, из 3 узлов и находящийся либо уже в K8s, либо на старых серверах), с которым работает приложение, размещённое в Kubernetes:

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

Как мы будем переводить такой кластер в новый production в Kubernetes?

Теория

Общий алгоритм миграции аналогичен описанному в ситуации с RabbitMQ.

Важно отметить, что для возможности переезда требуется, чтобы серверы с MongoDB и Kubernetes находились в одной сети. Узлы кластера MongoDB будут общаться между собой по IP старых серверов (где находятся старые инсталляции MongoDB) и по DNS-именам pod’ов с MongoDB в K8s. Поэтому на железных серверах (со старыми инсталляциями) потребуется пробросить маршруты до pod’ов, а затем настроить их на использование DNS-сервера, работающего в Kubernetes (или же прописать нужные имена в /etc/hosts, хотя в общем случае такой возможности лучше избегать).

Следующий шаг — поднять кластер MongoDB в pod’ах Kubernetes. В нашем случае кластер БД состоит из 3 узлов и каждый узел располагается в отдельном pod’е K8s — впрочем, их число может быть и другим. В ConfigMap’е надо указать адрес мастера MongoDB из старой инсталляции: тогда узлы MongoDB, находящиеся в pod’ах в K8s, сразу начнут синхронизацию с ним.

После того, как все pod’ы поднимутся, образуется кластер MongoDB из 6 узлов:

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

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

После этого можно переключить приложение на использование новых серверов MongoDB:

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

И останется лишь удалить старые узлы из кластера MongoDB, после чего переезд можно считать завершённым:

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

Такую схему мы часто применяем в production и для удобства её использования реализовали в рамках модуля к addon-operator (эту утилиту мы недавно анонсировали), что позволяет распространять типовые конфигурации MongoDB по многим кластерам. Публикацию своих модулей мы планируем провести в скором времени, а пока представляем отдельные инструкции, с которыми можно попробовать предлагаемое решение в действии и без использования addon-operator.

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

Требования

Реквизиты:

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

В описанном ниже примере старый кластер с MongoDB будет назван mongo-old и установлен в том же кластере Kubernetes, где в дальнейшем мы установим и новый (mongo-new).

Готовим старый кластер

1. Для примера, демонстрирующего описанную схему в действии, создадим «старый» (т.е. подлежащий миграции) кластер MongoDB прямо в Kubernetes (в реальности он может находиться и на отдельных серверах вне K8s). Для этого скачаем Helm-чарт:

helm fetch --untar stable/mongodb-replicaset

… и немного отредактируем его, настроив авторизацию:

auth:
  enabled: true
  adminUser: mongo
  adminPassword: pa33w0rd
  # metricsUser: metrics
  # metricsPassword: password
  # key: keycontent
  # existingKeySecret:
  # existingAdminSecret:
  # exisitingMetricsSecret:

Также в values.yaml можно настроить сертификаты и многое другое.

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

helm install . --name mongo-old --namespace mongo-old

После этого будет запущена тестовая «старая» инсталляция MongoDB:

kubectl --namespace=mongo-old get pods

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

Зайдем в pod с её мастером и создадим тестовую базу:

kubectl --namespace=mongo-old exec -ti mongo-old-mongodb-replicaset-0 mongo
use admin
db.auth('mongo','password')
use music
db.artists.insert({ artistname: "The Tea Party" })
show dbs

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

Заходя в разные pod’ы, я выяснил, что мастером является mongo-old-mongodb-replicaset-0. Впрочем, для более удобного решения этого вопроса после установки Helm-чарта выводится команда, как определить MASTER_POD. В моем случае (для mongo-old из 3 узлов) она выглядит вот так:

for ((i = 0; i < 3; ++i)); do kubectl exec --namespace mongo-old mongo-old-mongodb-replicaset-$i -- sh -c 'mongo --eval="printjson(rs.isMaster())"'; done

На этом подготовка старой инсталляции MongoDB, данные которой будут переноситься, готова.

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

Теперь развернём новую инсталляцию MongoDB, которая будет находиться в Kubernetes и использоваться приложением в production.

NB: Обращаю внимание, что должна использоваться такая же версия MongoDB, что и раньше. В ином случае есть риск получить проблемы совместимости.

По аналогии с предыдущим разделом (где мы имитировали «старую» инсталляции MongoDB), возьмем уже упомянутый Helm-чарт (командой helm fetch) и настроим авторизацию, а также другие параметры, если они используются. Кроме того, исправим файл init/on-start.sh, временно добавив в него на 165 строке адрес мастера, полученный на предыдущем этапе (или же известный вам по инсталляции MongoDB на отдельных серверах):

peers='mongo-old-mongodb-replicaset-0.mongo-old-mongodb-replicaset.mongo-old.svc.cluster.local:27017'

Мы готовы к созданию новой инсталляции MongoDB:

helm install . --name mongo-new --namespace mongo-new

Дожидаемся, пока стартуют все pod’ы (если данных много, то их запуск может длиться часами):

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

Теперь делаем exec в новый pod и смотрим список баз:

kubectl --namespace=mongo-new exec -ti mongo-new-mongodb-replicaset-0 mongo

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

Два кластера MongoDB объединены в один, состоящий из 6 узлов.

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

Из файла init/on-start.sh в новой инсталляции убираем добавленную нами строку:

peers='mongo-old-mongodb-replicaset-0.mongo-old-mongodb-replicaset.mongo-old.svc.cluster.local:27017'

Теперь зайдем в старый мастер кластера и «свергнем» его — тогда в кластере будет назначен новый мастер. Заходим в pod с мастером MongoDB:

kubectl --namespace=mongo-old exec -ti mongo-old-mongodb-replicaset-0 mongo
use admin
db.auth('mongo','password')

После этого меняем приоритеты у узлов и меняем мастера:

cfg = rs.conf()
cfg.members[5].priority = 2
rs.reconfig(cfg)
rs.stepDown(120)

Текущий узел перестал быть мастером — произойдут выборы нового. Поскольку мы поменяли приоритеты, мастером станет нужный нам узел.

NB: По умолчанию у всех узлов MongoDB приоритет равен 1. Выше мы поднимаем до 2 приоритет у нужного нам узла. Таким образом, общим мастером точно становится член нового кластера. Подробнее о том, как устроены эти механизмы в MongoDB, можно почитать в документации.

Отключим старую инсталляцию MongoDB, после чего зайдем в мастер новой и удалим старые узлы:

rs.remove("mongo-old-mongodb-replicaset-0.mongo-old-mongodb-replicaset.mongo-old.svc.cluster.local:27017")
rs.remove("mongo-old-mongodb-replicaset-1.mongo-old-mongodb-replicaset.mongo-old.svc.cluster.local:27017")
rs.remove("mongo-old-mongodb-replicaset-2.mongo-old-mongodb-replicaset.mongo-old.svc.cluster.local:27017")

После этого миграцию можно считать законченной: мы успешно переключились со старого кластера MongoDB на новый!

Итоги

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

Пожалуй, главный нюанс при переносе — это необходимость проброса IP-адресов новых pod’ов на серверы старой инсталляции MongoDB, если она находится вне K8s, и правильного их наименования в DNS (или /etc/hosts). В примере эти шаги не потребовались, поскольку миграция происходила между разными пространствами имён одного и того же Kubernetes-кластера.

P.S.

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

Источник: habr.com