MongoDB ReplicaSet cluster no CentOS7 em 10 minutos (Ansible)

O MongoDB (https://www.mongodb.com/) é um banco de dados de código aberto, gratuito, de alta performance, sem esquemas e orientado à documentos. Esse tipo de banco de dados orientado a documentos tem como característica conter todas as informações importantes em um único documento e ser livre de esquemas, possuir identificadores únicos universais (UUID) e possibilitar a consulta de documentos através de métodos avançados de agrupamento e filtragem e também permite redundância e inconsistência.

Hoje existem duas formas de clusterizar o MongoDB, uma chamada ReplicaSet, que consiste em replicar os dados entre as instâncias de MongoDB (permite maior tolerância a falhas) e a segunda Sharding que consiste me dividir os dados entre as instâncias.

A criação de um ReplicaSet pode ser considerado um dos primeiros passos no processo de escala do MongoDB, pois ele garante redundância dos dados e tolerância à falhas, caso uma ou mais instâncias parem de funcionar, graças ao mecanismo de failover.

https://imasters.com.br/data/escalando-mongodb-replica-sets-parte-01)

Nesse artigo vamos montar uma estrutura de ReplicaSet do MongoDB com três servidores da forma mais simples possível.

PRÉ-REQUISITOS

  • 3 nodes com 1 disco extra attachado (/dev/sdb);
  • Virtualbox;
  • Vagrant;
  • Ansible;
  • Git

Para este tutorial vou utilizar máquinas virtuais com o Vagrant e para deploy Ansible.

Para mais informações sobre o Vagrant:
http://www.tadeubernacchi.com.br/maquinas-virtuais-com-o-vagrant/

Para mais informações sobre o Ansible:
http://www.tadeubernacchi.com.br/ansible-primeiros-passos-e-exemplos/

Vagrantfile deste lab:
https://gist.github.com/tbernacchi/e600bc051b7f63a219d1367c16d6040d

Faça as alterações necessárias, como o path dos arquivos .vdi (Para este artigo adicionei um disco extra para cada instancia) e rede.

MÃOS NA MASSA

Com o Vagrant (https://www.vagrantup.com/) instalado e funcionando corretamente e com as respectivas boxes baixadas (dentro do diretório que contém o Vagrantfile) executamos:

➜  ~ vagrant up

Com as maquinetas up:

➜  ~ vagrant global-status
id name provider state directory
40d16cf mongo01 virtualbox running /Users/tadeu/vagrant/mongo-cluster
0118c9a mongo02 virtualbox running /Users/tadeu/vagrant/mongo-cluster
7e26fe5 mongo03 virtualbox running /Users/tadeu/vagrant/mongo-cluster

Faça o clone desse cara aqui: https://github.com/tbernacchi/mongodb

Obs: Como fui fazendo e aprendendo acabei não separando-o por roles.

E adicione as respectivas entradas DNS no seu arquivo de hosts e certifique-se que os mesmos estão ok com o Ansible:

➜  mongodb git:(master) ✗ ansible all -m ping -i hosts
mongo02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mongo03 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mongo01 | SUCCESS => {
"changed": false,
"ping": "pong"
}

Pau na jaca:

➜  mongodb git:(master) ✗ ansible-playbook -i hosts tasks/main.yml
PLAY [Mongo] *****************************************************************************************************
TASK [Disable seLinux] *******************************************************************************************
[WARNING]: SELinux state change will take effect next reboot
changed: [mongo02]
changed: [mongo03]
changed: [mongo01]
...
...
...

TASK [Enable and start mongod]
*******************************************************************************************************************
changed: [mongo02]
changed: [mongo01]
changed: [mongo03]
PLAY RECAP *******************************************************************************************************
mongo01 : ok=29 changed=23 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
mongo02 : ok=29 changed=23 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
mongo03 : ok=29 changed=23 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Se tudo ocorreu bem teremos uma saída parecida com essa e com o nosso cluster up and running. Vamos agora fazer algumas tarefas administrativas pós instalação começando com a criação de um usuário com privilégios administrativos.

No mongo01 que vai ser nosso PRIMARY:

[root@mongo01 mongo]# mongo
MongoDB shell version v3.6.17
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("227d9ef7-0d75-4343-a694-4014f14d4d93") }
MongoDB server version: 3.6.17

mongo_lab:PRIMARY> use admin;
switched to db admin

Para setarmos o master:

> rs.initiate();
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "mongo01:27017",
"ok" : 1
}

Para verificar quem é o master:

mongo_lab:PRIMARY> db.isMaster()
{
"hosts" : [
"mongo01:27017"
],
"setName" : "mongo_lab",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "mongo01:27017",
"me" : "mongo01:27017",


Setamos o user admin:

mongo_lab:PRIMARY> use admin;
switched to db admin

Criamos um usuário administrativo:

mongo_lab:PRIMARY> db.createUser({ "user" : "tadeuadmin", "pwd": "#Senhalinda2000", "roles" : [{role: "root", db: "admin"}, {role: "__system", db: "admin"}, {role: "userAdminAnyDatabase", db: "admin"}, {role: "dbAdminAnyDatabase", db: "admin"},{role: "readWriteAnyDatabase", db: "admin"},{role:"clusterManager",db:"admin"}]})
Successfully added user: {
"user" : "tadeuadmin",
"roles" : [
{
"role" : "root",
"db" : "admin"
},
{
"role" : "__system",
"db" : "admin"
},
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
},
{
"role" : "dbAdminAnyDatabase",
"db" : "admin"
},
{
"role" : "readWriteAnyDatabase",
"db" : "admin"
},
{
"role" : "clusterManager",
"db" : "admin"
}
]
}

Autenticamos:

mongo_lab:PRIMARY> db.auth("tadeuadmin", "#Senhalinda2000");
1

Com o host PRIMARY setado e com o usuário administrativo criado vamos adicionar as réplicas SECONDARY:

mongo_lab:PRIMARY> rs.add( { host: "mongo02:27018", priority: 0, votes: 0 } );
{
"ok" : 1,
"operationTime" : Timestamp(1582034294, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1582034294, 1),
"signature" : {
"hash" : BinData(0,"MKeBwuC0poEoLO9fCTDadpcUDJ8="),
"keyId" : NumberLong("6794770822142623746")
}
}
}

mongo_lab:PRIMARY> rs.add( { host: "mongo03:27019", priority: 0, votes: 0 } )
{
"ok" : 1,
"operationTime" : Timestamp(1582034470, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1582034470, 1),
"signature" : {
"hash" : BinData(0,"vr3aybL7nDkFjPLE6eu3yRzUBGU="),
"keyId" : NumberLong("6794770822142623746")
}
}
}

Para verificar o estado do cluster:

mongo_lab:PRIMARY> rs.status()

TESTANDO A REPLICAÇÃO

Para verificar em que database estamos trabalhando:

mongo_lab:PRIMARY> db
admin

Vamos criar um database chamado inventory de acordo com a doc:

mongo_lab:PRIMARY> use inventory;
switched to db inventory

mongo_lab:PRIMARY> show databases;
admin 0.000GB
config 0.000GB
local 0.000GB
mongo_lab:PRIMARY>

Verificamos que o mesmo ainda não foi “criado”, vamos populá-lo:

mongo_lab:PRIMARY> db.inventory.insertMany([
… { item: "journal", qty: 25, status: "A", size: { h: 14, w: 21, uom: "cm" }, tags: [ "blank", "red" ] },
… { item: "notebook", qty: 50, status: "A", size: { h: 8.5, w: 11, uom: "in" }, tags: [ "red", "blank" ] },
… { item: "paper", qty: 10, status: "D", size: { h: 8.5, w: 11, uom: "in" }, tags: [ "red", "blank", "plain" ] },
… { item: "planner", qty: 0, status: "D", size: { h: 22.85, w: 30, uom: "cm" }, tags: [ "blank", "red" ] },
… { item: "postcard", qty: 45, status: "A", size: { h: 10, w: 15.25, uom: "cm" }, tags: [ "blue" ] }
… ]);

Verificamos novamente:

mongo_lab:PRIMARY> show databases;
admin 0.000GB
config 0.000GB
inventory 0.000GB
local 0.000GB

Agora no mongo02 na réplica:

[root@mongo02 ~]# mongo localhost:27018 -u "mongoadmin" -p "#Senhalinda2000" --authenticationDatabase "admin"
MongoDB shell version v3.6.17
connecting to: mongodb://localhost:27018/test?authSource=admin&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("bcc10fa1-3acf-446e-bd54-d3ce82a64f7f") }
MongoDB server version: 3.6.17
mongo_lab:SECONDARY>

Vamos verificar o nosso banco inventory:

mongo_lab:SECONDARY> show dbs;
2020-02-19T22:16:52.924+0000 E QUERY [thread1] Error: listDatabases failed:{
"operationTime" : Timestamp(1582150642, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1582150642, 1),
"signature" : {
"hash" : BinData(0,"Q+gUMXPNW31187ppc5AKjRNeJBI="),
"keyId" : NumberLong("6794770822142623746")
}
}
} :

FAAMMMMMMMMM – Opaaaa, deu ruim, pera…esta mensagem é de uma proteção que existe nas instâncias secundárias devido a eventuais inconsistências que podem existir entre o servidor primário e os secundários. Nesse modelo, todas as escritas devem ser feitas somente no servidor primário, mas as leituras podem ser feitas nos servidores secundários. Para permitir a leitura basta usar o seguinte comando nos servidores secundários:

mongo_lab:SECONDARY> rs.slaveOk()

Agora sim:

mongo_lab:SECONDARY> show dbs;
admin 0.000GB
config 0.000GB
inventory 0.000GB
local 0.000GB

Vamos fazer uma busca desse node:

mongo_lab:SECONDARY> use inventory;
switched to db inventory

mongo_lab:SECONDARY> db.inventory.find({}).pretty()
{
"_id" : ObjectId("5e4dafa27ec36fc6ffaf3369"),
"item" : "planner",
"qty" : 0,
"status" : "D",
"size" : {
"h" : 22.85,
"w" : 30,
"uom" : "cm"
},
"tags" : [
"blank",
"red"
]
}
{
"_id" : ObjectId("5e4dafa27ec36fc6ffaf3368"),
"item" : "paper",
"qty" : 10,
"status" : "D",
"size" : {
"h" : 8.5,
"w" : 11,
"uom" : "in"
},
"tags" : [
"red",
"blank",
"plain"
]
}
{
"_id" : ObjectId("5e4dafa27ec36fc6ffaf3367"),
"item" : "notebook",
"qty" : 50,
"status" : "A",
"size" : {
"h" : 8.5,
"w" : 11,
"uom" : "in"
},
"tags" : [
"red",
"blank"
]
}
{
"_id" : ObjectId("5e4dafa27ec36fc6ffaf3366"),
"item" : "journal",
"qty" : 25,
"status" : "A",
"size" : {
"h" : 14,
"w" : 21,
"uom" : "cm"
},
"tags" : [
"blank",
"red"
]
}
{
"_id" : ObjectId("5e4dafa27ec36fc6ffaf336a"),
"item" : "postcard",
"qty" : 45,
"status" : "A",
"size" : {
"h" : 10,
"w" : 15.25,
"uom" : "cm"
},
"tags" : [
"blue"
]
}

ALGUNS COMANDOS

# Mostra as collections:
> show collections;

# Seleciona tudo de uma collection:
> db.collection.find({}).pretty();

# Remove o conteudo de uma collection:
> db.collection.deleteMany({});

# Remove todos os documentos de uma collection:
> db.collection.remove( { } );

# Verificar o tamanho de uma collection:
> db.last.stats( { scale: 1024 } );

# Verificar o tamanho do banco em KB
> db.runCommand( { dbStats: 1, scale: 1024 });

# Verificar usuários:
> db.getUsers();


CONCLUSÃO
Já fazia um tempinho que me interessei por MongoDB e pela linguagem NoSQL, na verdade tudo que é novo em tecnologia me chama a atenção e como já havia muita coisa anotada perdida pelos meus .txt sobre esse banco resolvi escrever esse artigo tentando ao máximo automatizar seu processo com Ansible. Como disse no começo do artigo esse é um setup bem simples, espero que gostem e que possa ajudar alguém. Quem sabe em um próximo não o aprimoramos 😉 – Abraços e até a próxima!

Referências:
https://docs.mongodb.com/manual/replication/
https://docs.mongodb.com/manual/tutorial/getting-started/
https://www.devmedia.com.br/introducao-ao-mongodb/30792
https://imasters.com.br/banco-de-dados/mongodb-para-iniciantes-em-nosql