You are here

MongoDB от теории к настройке Replica Set + Arbiter

Linux: 

MangoDB Replica Set

В этой статье разберем основные вопросы по MongoDB:

  • Установка и настройка MongoDB
  • Настройка конфигурационного файла MongoDB
  • Создание Primary и Secondary в Replica Set
  • Подключение в Replica Set регулятора Arbiter
  • Эксперимент: удаляем Secondary и делаем из него Arbiter
  • Проверка работы Replica Set
  • Разбор ошибок и их решение
  • Варианты создания Replica Set

В нашем случае, развернули Replica Set в локальной сети. За основу взяли дистрибутив Debian 9 и 10 (если настраиваете на CentOS, там примерно всё тоже самое). Материал в статье будет интересен тем, кто первый раз знакомится с MongoDB. Все будем делать поэтапно, с пояснением по каждому моменту.

1. Установка и настройка MongoDB на наши сервера:

Если вы не знаете как поставить MongoDB на ваш Linux, всегда можно обратиться к официальному сайту: docs.mongodb.com (раздел: Install MongoDB > Install MongoDB Community Edition > Install MongoDB Community Edition on Linux >).

Перед началом всех действий, не забываем делать update, далее добавляем репозито́рий: mongodb-org.list и debian-stretch.list:

  1. sudo apt update
  2. sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 4B7C549A058F8B6B
  3. echo "deb http://repo.mongodb.org/apt/debian stretch/mongodb-org/4.2 main" | sudo tee /etc/apt/sources.list.d/mongodb-org.list
  4. echo "deb http://deb.debian.org/debian/ stretch main" | sudo tee /etc/apt/sources.list.d/debian-stretch.list

Делаем update после правки репозитория и ставим libcurl3:

  1. sudo apt update
  2. sudo apt -y install libcurl3

Устанавливаем саму MongoDB: Install MongoDB Server on Debian 10

  1. sudo apt -y install mongodb-org

Проверяем:

  1. sudo apt info mongodb-org

Данные пакеты входят в пакет выше по умолчанию:

  • "mongodb-org hold"
  • "mongodb-org-server hold"
  • "mongodb-org-shell hold"
  • "mongodb-org-mongos hold"
  • "mongodb-org-tools hold"

Стартуем при запуске:

  1. sudo systemctl enable --now mongod

Проверяем статус установленной базы:

  1. sudo systemctl status mongod.service

Повторяем те же действия для остальных. Мы использовали 3 сервера.

2. Настройка конфигурационного файла MongoDB /etc/mongod.conf. Содержимое нашего файла ниже:

  1. # where to write logging data.
  2. systemLog:
  3. destination: file
  4. logAppend: true
  5. path: /var/log/mongodb/mongod.log
  6.  
  7. # network interfaces
  8. net:
  9. port: 27017
  10. bindIp: 0.0.0.0
  11.  
  12. # how the process runs
  13. processManagement:
  14. timeZoneInfo: /usr/share/zoneinfo
  15.  
  16. #security:
  17.  
  18. #operationProfiling:
  19.  
  20. replication:
  21. replSetName: mongoset
  22.  
  23. #sharding:
  24. ## Enterprise-Only Options:
  25. ## Enterprise-Only Options:
  26. #auditLog:
  27. #snmp:

Тут мы прописали "0.0.0.0", чтобы был полный охват. Обратим внимание на следующие строки:

  1. # network interfaces
  2. net:
  3. port: 27017
  4. bindIp: 0.0.0.0

Тут указываем имя replSet, у нас это "mongoset" (имя задаете сами для всего Replica Set):

  1. replication:
  2. replSetName: mongoset

Можно сказать, что основную работу мы сделали. Теперь немного о командах для MongoDB:

  • sudo service mongod start
  • sudo service mongod stop
  • sudo service mongod restart
  • systemctl - стандартные варианты
  • mongo - вход в саму базу

3. Создание Primary и Secondary в Replica Set или Deploy a Replica Set:

* Внимание:
Первая проблема, с которой мы столкнулись, это несоответствие версии MongoDB. Так как мы использовали разные версии Linux, при работе с sources.list внесли разные версии «mongodb-org/4.2» и «mongodb-org/4.0», из-за чего они не работали в Replica Set. На это надо обратить внимание!

* Внимание: если возникла ошибка типа:
Process: 2928 ExecStart=/usr/bin/mongod —config /etc/mongod.conf (code=exited, status=1/FAILURE)

Необходимо проверить синтаксис /etc/mongod.conf в части IP и наличие не поврежденных (удаленных) каталогов:
/var/log/mongodb и /var/lib/mongodb/. Если их нет (в нашем случае, при замене версии MongoDB, удалили все каталоги, а новые не встали), то создать и прописать права от mongodb:

  1. mkdir /var/lib/mongodb
  2. mkdir /var/log/mongodb
  3. chown mongodb:mongodb /var/lib/mongodb -R
  4. chown mongodb:mongodb /var/log/mongodb -R

Определяем какой из наших серверов будет основным или правильнее сказать Primary, и заходим в MongoDB:

  1. mongo

После входа в MongoDB, отразится приглашение для ввода запросов:

  1. The monitoring data will be available on a MongoDB website with a unique URL accessible to you
  2. and anyone you share the URL with. MongoDB may use this information to make product
  3. improvements and to suggest MongoDB products and deployment options to you.
  4.  
  5. To enable free monitoring, run the following command: db.enableFreeMonitoring()
  6. To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
  7. ---
  8.  
  9. >

Проверяем статус rs.status(). Начальный статус должен быть 0 - startup. Означает что узел не является членом ни одной реплики.:

  1. > rs.status()
  2. {
  3. "operationTime" : Timestamp(0, 0),
  4. "ok" : 0,
  5. "errmsg" : "no replset config has been received",
  6. "code" : 94,
  7. "codeName" : "NotYetInitialized",
  8. "$clusterTime" : {
  9. "clusterTime" : Timestamp(0, 0),
  10. "signature" : {
  11. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  12. "keyId" : NumberLong(0)
  13. }
  14. }
  15. }

Производим "Инициализацию реплики" только для одного (он и будет Primary): Run rs.initiate() on just one and only one mongod instance for the replica set. Для этого используем шаблон с официального сайта:

  1. > rs.initiate({
  2. _id: 'abc',
  3. members: [
  4. {_id: 0, host:'db1:27017'},
  5. {_id: 1, host:'db2:27017'},
  6. {_id: 2, host:'db3:27017'}
  7. ]
  8. })

На основе шаблона, вместо "abc'" пишем наш вариант "mongoset", указанный в файле /etc/mongod.conf. Далее прописываем IP (через данный шаблон можно добавить сразу несколько серверов), мы сделаем один. Остальные добавим позже:

  1. rs.initiate( {
  2. _id : "mongoset",
  3. members: [
  4. { _id: 0, host: "192.168.0.9:27017" }
  5. ]
  6. })

Итогом станет такой вывод:

  1. > rs.initiate( {
  2. ... _id : "mongoset",
  3. ... members: [
  4. ... { _id: 0, host: "192.168.0.9:27017" }
  5. ... ]
  6. ... })
  7. {
  8. "ok" : 1,
  9. "$clusterTime" : {
  10. "clusterTime" : Timestamp(1569240393, 1),
  11. "signature" : {
  12. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  13. "keyId" : NumberLong(0)
  14. }
  15. },
  16. "operationTime" : Timestamp(1569240393, 1)
  17. }
  18. mongoset:SECONDARY>

Проверяем конфигурацию:

  1. mongoset:SECONDARY> rs.conf()

Проверяем статус rs.status() и видим, что "mongoset:SECONDARY" сменилось на "mongoset:PRIMARY":

  1. mongoset:PRIMARY> rs.status()
  2. {
  3. "set" : "mongoset",
  4. "date" : ISODate("2019-09-23T12:21:34.027Z"),
  5. "myState" : 1,
  6. "term" : NumberLong(1),
  7. "syncingTo" : "",
  8. "syncSourceHost" : "",
  9. "syncSourceId" : -1,
  10. "heartbeatIntervalMillis" : NumberLong(2000),
  11. "optimes" : {
  12. "lastCommittedOpTime" : {
  13. "ts" : Timestamp(1569241287, 1),
  14. "t" : NumberLong(1)
  15. },
  16. "lastCommittedWallTime" : ISODate("2019-09-23T12:21:27.422Z"),
  17. "readConcernMajorityOpTime" : {
  18. "ts" : Timestamp(1569241287, 1),
  19. "t" : NumberLong(1)
  20. },
  21. "readConcernMajorityWallTime" : ISODate("2019-09-23T12:21:27.422Z"),
  22. "appliedOpTime" : {
  23. "ts" : Timestamp(1569241287, 1),
  24. "t" : NumberLong(1)
  25. },
  26. "durableOpTime" : {
  27. "ts" : Timestamp(1569241287, 1),
  28. "t" : NumberLong(1)
  29. },
  30. "lastAppliedWallTime" : ISODate("2019-09-23T12:21:27.422Z"),
  31. "lastDurableWallTime" : ISODate("2019-09-23T12:21:27.422Z")
  32. },
  33. "lastStableRecoveryTimestamp" : Timestamp(1569241237, 1),
  34. "lastStableCheckpointTimestamp" : Timestamp(1569241237, 1),
  35. "members" : [
  36. {
  37. "_id" : 0,
  38. "name" : "192.168.0.9:27017",
  39. "ip" : "192.168.0.9",
  40. "health" : 1,
  41. "state" : 1,
  42. "stateStr" : "PRIMARY",
  43. "uptime" : 925,
  44. "optime" : {
  45. "ts" : Timestamp(1569241287, 1),
  46. "t" : NumberLong(1)
  47. },
  48. "optimeDate" : ISODate("2019-09-23T12:21:27Z"),
  49. "syncingTo" : "",
  50. "syncSourceHost" : "",
  51. "syncSourceId" : -1,
  52. "infoMessage" : "",
  53. "electionTime" : Timestamp(1569240393, 2),
  54. "electionDate" : ISODate("2019-09-23T12:06:33Z"),
  55. "configVersion" : 1,
  56. "self" : true,
  57. "lastHeartbeatMessage" : ""
  58. }
  59. ],
  60. "ok" : 1,
  61. "$clusterTime" : {
  62. "clusterTime" : Timestamp(1569241287, 1),
  63. "signature" : {
  64. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  65. "keyId" : NumberLong(0)
  66. }
  67. },
  68. "operationTime" : Timestamp(1569241287, 1)
  69. }
  70. mongoset:PRIMARY>

Самый важный элемент кода выше "stateStr" : "PRIMARY":

  1. "_id" : 0,
  2. "name" : "192.168.0.9:27017",
  3. "ip" : "192.168.0.9",
  4. "health" : 1,
  5. "state" : 1,
  6. "stateStr" : "PRIMARY"

Ну вот, мы добавили первый сервер, который сразу стал "PRIMARY"! Продолжим...

3.1. Дальше нам надо добавить остальные сервера из нашей сети, по шаблону с официального сайта:

  1. > rs.add("<Secondary server ip>:27017")

Добавляем второй сервер или "SECONDARY" из под "mongoset:PRIMARY":

  1. mongoset:PRIMARY> rs.add("192.168.0.5:27017")
  2. {
  3. "ok" : 1,
  4. "$clusterTime" : {
  5. "clusterTime" : Timestamp(1569260777, 1),
  6. "signature" : {
  7. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  8. "keyId" : NumberLong(0)
  9. }
  10. },
  11. "operationTime" : Timestamp(1569260777, 1)
  12. }

Итогом станет, появление следующего фрагмента в команде mongoset:PRIMARY> rs.status()

  1. ...
  2. {
  3. "_id" : 1,
  4. "name" : "192.168.0.5:27017",
  5. "ip" : "192.168.0.5",
  6. "health" : 1,
  7. "state" : 2,
  8. "stateStr" : "SECONDARY",
  9. "uptime" : 10,
  10. "optime" : {
  11. "ts" : Timestamp(1569260777, 1),
  12. "t" : NumberLong(3)
  13. },
  14. ...

Весь вывод не отражаю, суть понятна! После добавления третьего SECONDARY у нас будут примерно такие статусы при вводе запроса rs.status():

  • Primary
  • Secondary
  • Secondary

3.2. Другой путь: при котором мы добавляем Primary и Secondary не по отдельности, а сразу все сервера, через шаблон выше:

* Внимание: может возникнуть ошибка, если на начальном этапе вы сразу добавили не один сервер, а несколько. Связанная Host Name. В нашем случае, оказалось, что при выводе команды rs.status() в поле отвечающем за информацию по IP подтянулось значение HostName. Вместо "192.168.0.5:27017", было "debian".

Как исправить? Из под строки ввода запросов MongoDB (проблемного сервера) набираем последовательно следующий список значений, со своим IP:

  1. > cfg = rs.conf()
  2. > cfg.members[0].host = "192.168.0.11:27017"
  3. > rs.reconfig(cfg)

Если не примет последнюю команду, так как у вас скорее всего будет Secondary, пишем её так:

  1. rs.reconfig(cfg, {force : true})

После чего, перепроверяем через rs.status(), может понадобиться перезагрузка MongoDB, чтобы встало верное значение. Этой же командой можно менять IP в случае переезда MongoDB Secondary с одного IP на другое.

Остальное повторно писать не буду, так же как и выше.

4. Подключение в Replica Set регулятора Arbiter

Если рассматривать "Secondary + Secondary + Primary", то такая система вполне себе рабочая, в случае отказа одного из серверов, оставшиеся два сами перевыберут нужного Primary. Если в этом будет необходимость. Но это не так круто, как использования для этих целей Arbiter. Теперь подробнее.

Обращаясь к официальному сайту, мы можем найти такой шаблон:

  1. rs.addArb("m1.example.net:27017")

Но перед тем как добавлять Arbiter из под Primary, нужно еще пару шагов. Нужно создать директорию под нужды Arbiter:

  1. mkdir /data/arb

Дальше ввести данные по шаблону:

  1. mongod --port 27017 --dbpath /data/arb --replSet rs --bind_ip localhost,<hostname(s)|ip address(es)>

И только после этого, добавить сам Arbiter (192.168.0.20):

  1. mongoset:SECONDARY> rs.addArb("192.168.0.20:27017")

На выводе запроса rs.status(), получим такой вывод (это часть вывода!):

  1. {
  2. "_id" : 1,
  3. "name" : "192.168.0.20:27017",
  4. "ip" : "192.168.0.20",
  5. "health" : 1,
  6. "state" : 7,
  7. "stateStr" : "ARBITER",
  8. "uptime" : 15,
  9. "lastHeartbeat" : ISODate("2019-09-24T05:57:10.275Z"),
  10. "lastHeartbeatRecv" : ISODate("2019-09-24T05:57:10.525Z"),
  11. "pingMs" : NumberLong(0),
  12. "lastHeartbeatMessage" : "",
  13. "syncingTo" : "",
  14. "syncSourceHost" : "",
  15. "syncSourceId" : -1,
  16. "infoMessage" : "",
  17. "configVersion" : 4
  18. }

В итоге у нас такая связка: "ARBITER + Secondary + Primary"

5. Эксперимент: удаляем Secondary и делаем из него Arbiter
Это скорее не раздел для действий, а эксперимент

Мы уже умеем добавлять Primary и Secondary к нему. Но, нам нужно реализовать воображаемую ситуацию: один Secondary сделать ARBITER. Для этого нам надо удалить Secondary, и добавить его уже как ARBITER.

Удаляем ранее добавленный Secondary (IP 192.168.0.5) из под Primary:

  1. > rs.remove("192.168.0.5:27017")
  2. {
  3. "ok" : 1,
  4. "$clusterTime" : {
  5. "clusterTime" : Timestamp(1569300827, 2),
  6. "signature" : {
  7. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  8. "keyId" : NumberLong(0)
  9. }
  10. },
  11. "operationTime" : Timestamp(1569300827, 2)
  12. }

Проверяем что получилось, через rs.status(). Итогом станет, что Secondary пропадет из списка. Далее добавляем папки для ARBITER (см. выше) и сам ARBITER через команду на Primary:

  1. mongoset:PRIMARY> rs.addArb("192.168.0.5:27017")
  2. {
  3. "ok" : 1,
  4. "$clusterTime" : {
  5. "clusterTime" : Timestamp(1569304485, 1),
  6. "signature" : {
  7. "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
  8. "keyId" : NumberLong(0)
  9. }
  10. },
  11. "operationTime" : Timestamp(1569304485, 1)
  12. }

Но запрос статуса, выведет вместо "stateStr" : "ARBITER", значение:

  1. ...
  2. "stateStr" : "(not reachable/healthy)"
  3. ...

Танцы с бубном, привели к следующему решению (не призываю к действию, у вас может быть более логичное решение). Нужно почистить папку на сервере "192.168.0.5 Secondary":

  1. /var/lib/mongodb

Выйти из MongoDB, а на Primary ввести запрос, проверить статус:

  1. mongoset:PRIMARY> rs.addArb("192.168.0.5:27017")
  2. ...
  3. rs.status()
  4. ...
  5. "stateStr" : "ARBITER"
  6. ...

Надо отметить, что мой PRIMARY временно стал SECONDARY, но после активации ARBITER, все встало на свои места. В данном эксперименте были пропущенные такие шаги как:

  • отключение и включение SECONDARY: для удаления данных из папки
  • Ввод команды rs.status() несколько раз

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

6. Проверка работы Replica Set

Самое главное: проверить работу базы!

И так, Тестирование. Добавим базу на Primary и просмотрим на Secondary.
PRIMARY 192.168.0.9
SECONDARY 192.168.0.14

  1. mongoset:PRIMARY> show dbs
  2. admin 0.000GB
  3. config 0.000GB
  4. local 0.000GB
  5. mongoset:PRIMARY> use seasonal
  6. switched to db seasonal
  7. mongoset:PRIMARY> db.seasonal.insertOne({ name: "Mango", season: "Summer"})
  8. {
  9. "acknowledged" : true,
  10. "insertedId" : ObjectId("5d890c160f4f81aae0160b84")
  11. }
  12. mongoset:PRIMARY> show dbs
  13. admin 0.000GB
  14. config 0.000GB
  15. local 0.000GB
  16. seasonal 0.000GB
  17. mongoset:PRIMARY>

Проверяем на Secondary:
* тут надо дать пояснение: просто так в базу через Secondary "не зайти/не увидеть"! Так как доступ к базе только из под PRIMARY. Но есть лазейка, использовать rs.slaveOk(). Но на официальном сайте крайне не рекомендуют её применять, что мы и вам советуем. Но у нас база для тестов, была не была:

  1. mongoset:SECONDARY> rs.slaveOk()
  2. mongoset:SECONDARY> show dbs
  3. admin 0.000GB
  4. config 0.000GB
  5. local 0.000GB
  6. seasonal 0.000GB
  7. mongoset:SECONDARY>

Всё работает.

7. Разбираем оставшиеся вопросы

Почему важно понимать сколько у вас SECONDARY+PRIMARY+ARBITER:

Важно! Если у вас в связке "1 PRIMARY и 2 SECONDARY", то при выходе из строя PRIMARY, пройдет голосование. Выбор будет между 2х SECONDARY, кто-то из них станет PRIMARY. В итоге останется один SECONDARY и один PRIMARY.
По умолчанию выбор будет случайный среди определенных кандидатов. Можно задавать кто кем будет, назначая вес приоритета выбора.
Суть голосования: в связке с 3 серверами, голосование проходит один раз, большинством голосов: 2/3 против 1/3. Т.е. Если умрут 2 сервера из трех, то последний SECONDARY не сможет назначить себя PRIMARY, так как его голос будет 1/3 от изначальных 3. Это важно понимать!

Другой вариант, у вас 1 PRIMARY, 1 ARBITER, 3 SECONDARY, т.е. всего 5! Если у вас упадет PRIMARY, то большинством 4/5 будет переизбран новый PRIMARY из 3-х оставшихся SECONDARY. Если умрет еще один, т.е. по факту у вас уже 2 умерло, и 3 живых. Голосование пройдет как 3/5. А если умирает уже 3 сервера из 5, то голосование не пройдет, так как голоса будут 2/5 против уже умерших 3/5.

Поэтому нужно понимать сколько вы закладываете серверов и какая отказоустойчивость будет. Надеюсь наш пример и небольшие эксперименты будут для вас полезны!

Ну и на последок, если решите удалить:

  1. sudo service mongod stop
  2. sudo apt-get purge mongodb-org*
  3. sudo rm -r /var/log/mongodb
  4. sudo rm -r /var/lib/mongodb

Источник: http://linuxsql.ru