Эта статья продолжает наш недавний материал про миграцию RabbitMQ и посвящена MongoDB. Поскольку мы обслуживаем множество кластеров Kubernetes и MongoDB, пришли к естественной необходимости мигрировать данные из одной инсталляции в другую и делать это без простоя. Основные сценарии прежние: перенос MongoDB из виртуального/железного сервера в Kubernetes или же перенос MongoDB в рамках одного кластера Kubernetes (из одного пространства имён в другое).
Наш рецепт предназначен для случаев, когда функционирует старый кластер MongoDB (например, из 3 узлов и находящийся либо уже в K8s, либо на старых серверах), с которым работает приложение, размещённое в 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 узлов:
Обратите внимание, что pod’ы будут долго подниматься, поскольку каждый pod запускается по очереди, а в момент запуска синхронизирует данные с мастера.
После этого можно переключить приложение на использование новых серверов MongoDB:
И останется лишь удалить старые узлы из кластера MongoDB, после чего переезд можно считать завершённым:
Такую схему мы часто применяем в 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
… и немного отредактируем его, настроив авторизацию:
После этого будет запущена тестовая «старая» инсталляция MongoDB:
kubectl --namespace=mongo-old get pods
Зайдем в 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
Заходя в разные 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 на отдельных серверах):
Текущий узел перестал быть мастером — произойдут выборы нового. Поскольку мы поменяли приоритеты, мастером станет нужный нам узел.
NB: По умолчанию у всех узлов MongoDB приоритет равен 1. Выше мы поднимаем до 2 приоритет у нужного нам узла. Таким образом, общим мастером точно становится член нового кластера. Подробнее о том, как устроены эти механизмы в MongoDB, можно почитать в документации.
Отключим старую инсталляцию MongoDB, после чего зайдем в мастер новой и удалим старые узлы:
После этого миграцию можно считать законченной: мы успешно переключились со старого кластера MongoDB на новый!
Итоги
Описанная схема подходит практически для всех случаев, когда нужно перенести MongoDB или просто переехать в новый кластер.
Пожалуй, главный нюанс при переносе — это необходимость проброса IP-адресов новых pod’ов на серверы старой инсталляции MongoDB, если она находится вне K8s, и правильного их наименования в DNS (или /etc/hosts). В примере эти шаги не потребовались, поскольку миграция происходила между разными пространствами имён одного и того же Kubernetes-кластера.