Как перенести MongoDB на другой сервер с помощью репликации

Как перенести MongoDB на другой сервер с помощью репликации

На моем VPS за 5 баксов наконец закончилось место, и MongoDB не может нормально работать (и отваливается первой). Хотелось научиться переносить бд с одного сервера на другой без остановки (хотя сейчас это не так критично, потому что все приложения уже упали ха-ха, даже через Compass не могу подключиться). В этой статье я покажу, как перенести MongoDB с одного сервера на другой с помощью репликации.


🔧 Зачем использовать репликацию

Репликация MongoDB позволяет создать несколько серверов, где:

  • PRIMARY принимает все записи,
  • SECONDARY синхронизирует данные и может использоваться для чтения или бэкапа.

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


⚙️ Подготовка серверов

На старом сервере:

  • Уже установлена и запущена MongoDB (у меня древняя версия 4.2). Проверить версию можно так mongod --version
  • Есть пользователь admin с правами root (если нет, то создадим так, подключившить к mongo shell на сервере):
use admin
db.createUser({
  user: "admin",
  pwd: "StrongPass123",
  roles: ["root"]
})

На новом сервере:

Установлен Docker, т. к планируется запуск Mongo в контейнере.


🚀 1. Создаём реплика-сет на старом сервере

Открываем Mongo shell:

mongo -u admin -p StrongPass123 --authenticationDatabase admin

Инициализируем реплика-сет:

rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "old.ip.address:27017" } // IP старого сервера
  ]
})

Проверяем:

rs.status()

Должен появиться статус PRIMARY.


🔐 2. Настраиваем ключ безопасности

MongoDB требует keyFile для репликации между узлами с включённой авторизацией. Это позволяет вам ограничить круг ip, который может подключиться к вашей базе для репликации. Есть вариант вообще отключить авторизацию, но тогда есть вероятность что вашу бд могут дропнуть какие нибудь боты.

Создаём ключ на старом сервере:

openssl rand -base64 756 > mongodb-keyfile
sudo mv mongodb-keyfile /etc/mongodb-keyfile
sudo chmod 400 /etc/mongodb-keyfile
sudo chown mongodb:mongodb /etc/mongodb-keyfile

Для этого ключа важно выставить права (400) и владельца mongodb (что и сделано выше).

В /etc/mongod.conf добавляем:

replication:
  replSetName: rs0
security:
  keyFile: /etc/mongodb-keyfile
  authorization: enabled

replSetName - имя реплики
keyFile - путь к ключу.

Перезапускаем MongoDB:

sudo systemctl restart mongod

Если при старте будет ошибка bad file или yaml-cpp: unknown escape character, значит, файл содержит переносы строк или кавычки — нужно убедиться, что ключ в одну строку и без пробелов в начале. Иногда mongo может пытаться прочесть ваш файл как yml, мне помогло прописать путь до файла в кавычках.


🐳 3. Запускаем MongoDB на новом сервере в Docker

На новом сервере создаём mongodb-keyfile с тем же содержимым:

echo "тот_же_ключ" > mongodb-keyfile
chmod 400 mongodb-keyfile

Создаём docker-compose.yml:

version: "3.8"

services:
  mongo:
    image: mongo:4.2
    container_name: mongo
    restart: unless-stopped
    volumes:
      - ./data:/data/db
      - ./mongodb-keyfile:/etc/mongodb-keyfile
    command: >
      mongod
      --replSet rs0
      --keyFile /etc/mongodb-keyfile
      --bind_ip_all
    ports:
      - "27017:27017"

Запускаем:

docker compose up -d

🔗 4. Добавляем новый сервер как реплику

Подключаемся к PRIMARY (на старом сервере) и добавляем ноду:

mongo -u admin -p StrongPass123 --authenticationDatabase admin
rs.add({ host: "new.ip.address:27017" })

Проверяем:

rs.status()

Сначала в ответе статуса для новой ноды будет содержаться параметр state STARTUP2. Через некоторое время новая нода станет SECONDARY — это значит, что синхронизация завершена.


🕵️ 5. Проверяем синхронизацию

Когда статус SECONDARY без ошибок, данные уже полностью скопированы.

Можно убедиться:

rs.printSecondaryReplicationInfo()

Если всё ок — база на новом сервере полностью актуальна.


🔁 6. Переключаем PRIMARY

Теперь можно безопасно «переехать»:

Останавливаем запись на старый сервер (или поднимаем приложение с переменной MONGODB_URI указывающей на новый IP).

Подключаемся к старому серверу:

rs.stepDown()

Теперь новая нода станет PRIMARY.

Можно передать параметр времени в секундах на сколько ваша старая нода перестанет быть PRIMARY (если вы просто экспериментируете с нодами)

rs.stepDown(60)

🧱 7. Удаляем старый сервер из реплики

После проверки что все данные скопированны, на новом сервере заходим в mongo shell в докере:

docker exec -it containerName mongo -u admin -p StrongPass123 --authenticationDatabase admin

И удаляем старую ноду

rs.remove("old.ip.address:27017")

⚠️ Важно

Т к теперь БД сконфигурирована как replice-set, подключаться к ней нужно с дополнительным параметром (replicaSet=rs0):

admin:[email protected]:27017/db_name?authSource=admin&authMechanism=DEFAULT&replicaSet=rs0

⚠️ Подводные камни

Вот на чём можно споткнуться (и на чём мы действительно споткнулись 😅):

Ошибки с keyFile

Файл должен быть одинаков на всех серверах.
Права только 400.
Без кавычек, без переносов строк.

Неправильный host
При инициализации реплика-сета Mongo может автоматически подставить внутреннее имя ubuntu-s-1vcpu....
Нужно вручную заменить:

cfg = rs.conf()
cfg.members[0].host = "old.ip.address:27017"
rs.reconfig(cfg, { force: true })

Ошибка Unauthorized при rs.initiate()
Значит ты выполнил команду, когда уже включена авторизация, но не залогинился как admin (не передал --authenticationDatabase admin).

Firewall и порты
Обязательно надо открыть порт 27017 между серверами.


💡 Бонус: Можно добавить третью ноду-«арбитер», которая не хранит данные, но поддерживает отказоустойчивость:

rs.addArb("192.168.1.100:27017")

На этом все. Подписывайтесь на мой Telegram-канал про разрабтку и не только!
Всем добра! :)