Cluster Mongo em Docker

Opa!

Hoje o post será mais prático, e queremos trazer para vocês uma forma de criar um cluster de MongoDB utilizando Docker. O próprio Docker tem um tutorial de como você pode criar a sua imagem de mongo e executá-lo dentro do Docker, para quem não viu ainda, segue o link. Para quem não conhece, MongoDB é um tipo de banco de dados orientado a documentos ou seja, utiliza o conceito de dados e documentos auto contidos e auto descritivos, e isso implica que o documento em si já define como ele deve ser apresentado e qual é o significado dos dados armazenados na sua estrutura.

O MongoDB também é chamado de banco de dados NoSql, mas isso é outro assunto 🙂 . O que veremos neste artigo é como você pode montar um cluster de instâncias do mongo utilizando como backend containers Docker. Isso é possível pois o existe na “caixa” do mongo algumas formas de se construir um cluster, uma delas é utilizando o método de replica set, que consiste em ter instâncias  para onde a informação será replicada, e dessa forma garantir a persistência das informações, abaixo segue uma imagem ilustrativa de como isso funciona:

Dessa forma garantimos a persistência e tolerância a falha dos dados que estão sendo armazenados. Neste post mostraremos como utilizar essa abordagem, não entraremos em detalhes mais fundos do Mongo pois não é este o objetivo.

Vamos lá?

Antes de tudo, é recomendável que os servidores possam ser acessíveis via nome, caso não seja possível, você pode adicionar nos hosts os endereços para que isso seja temporariamente possível.

Em seguida precisamos criar o diretório no host onde será persistido os dados do banco:

$ mkdir /opt/mongo
$ mkdir /opt/mongo/data $ cd /opt/mongo

É importante lembrar que essas pastas devem estar criadas em todos os nós, para que dessa forma seja possível persistir os dados em todos os nós.

Agora você precisa gerar uma chave que será utilizada para realizar a sincronia entre as replicas, e garantir que os dados além de trafegar de forma segura, estejam replicados entre todos os nós, essa chave deve ser copiada para todos os nós também.

$ openssl rand -base64 741 > mongodb-keyfile $ chmod 600 mongodb-keyfile $ chown 999 mongodb-keyfile

Configurado este arquivo, precisamos subir um container para realizar algumas tarefas administrativas, para depois subir o cluster propriamente dito:

$ docker run --name mongo -v /opt/mongo/data:/data/db -v /opt/mongo:/opt/keyfile --hostname="node1" -p 27017:27017 -d mongo:2.6.5 --smallfiles

Vamos as configurações agora:

$ docker exec -it mongo /bin/bash
root@node1:/# mongo
> use admin
db.createUser( {
     user: "admin",
     pwd: "SENHA_DE_ADMIN",
     roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
   });

db.createUser( {
     user: "root",
     pwd: "SENHA_DE_ROOT",
     roles: [ { role: "root", db: "admin" } ]
   });
> exit
root@node1:/# exit

Remova o container para que possamos agora subir o ambiente todo:

$ docker stop mongo
$ docker rm mongo

Agora sim vamos subir o ambiente:

$ docker run --name mongo -v /etc/hosts:/etc/hosts -v /opt/mongo/data:/data/db -v /opt/mongo:/opt/keyfile --hostname="node1" --add-host node1.db:${node1} --add-host node2.db:${node2} --add-host node3.db:${node3} -p 27017:27017 -d mongo:2.6.5 --smallfiles --keyFile /opt/keyfile/mongodb-keyfile --replSet "rs0"

Acesse este container para inicializar o cluster:

$ docker exec -it mongo /bin/bash
root@node1:/# mongo
> use admin
> db.auth("root", "SENHA_DE_ROOT");
> rs.initiate()
{
         "info2" : "no configuration explicitly specified -- making one",
         "me" : "node1.db:27017",
         "info" : "Config now saved locally.  Should come online in about a minute.",
         "ok" : 1
}

> rs0:PRIMARY> rs.conf()
{
        "_id" : "rs0",
        "version" : 1,r
        "members" : [
              {
                  "_id" : 0,
                  "host" : "node1.db:27017"
              }
        ]
}

Nos demais nós, suba os containers da seguinte forma:

Servidor 2

$ docker run --name mongo -v /etc/hosts:/etc/hosts -v /opt/mongo/data:/data/db -v /opt/mongo:/opt/keyfile --hostname="node2" -p 27017:27017 -d mongo:2.6.5 --smallfiles --keyFile /opt/keyfile/mongodb-keyfile --replSet "rs0"

Servidor 3

$ docker run --name mongo -v /etc/hosts:/etc/hosts -v /opt/mongo/data:/data/db -v /opt/mongo:/opt/keyfile --hostname="node3" -p 27017:27017 -d mongo:2.6.5 --smallfiles --keyFile /opt/keyfile/mongodb-keyfile --replSet "rs0"

Volte até o servidor 1, acesse o container de mongo e execute os comandos:

$ docker exec -it mongo /bin/bash
root@node1:/# mongo
> use admin
> db.auth("root", "SENHA_DE_ROOT");
> rs0:PRIMARY> rs.add("node2.db") 
> rs0:PRIMARY> rs.add("node3.db") 
> rs0:PRIMARY> rs.status()

O status devem ser algo parecido com este retorno:

rs0:PRIMARY> rs.status()
{
 "set" : "rs0",
 "date" : ISODate("2017-05-16T15:43:30Z"),
 "myState" : 1,
 "members" : [
 {
 "_id" : 0,
 "name" : "node1:27017",
 "health" : 1,
 "state" : 1,
 "stateStr" : "PRIMARY",
 "uptime" : 578713,
 "optime" : Timestamp(1494371417, 1),
 "optimeDate" : ISODate("2017-05-09T23:10:17Z"),
 "electionTime" : Timestamp(1494371963, 1),
 "electionDate" : ISODate("2017-05-09T23:19:23Z"),
 "self" : true
 },
 {
 "_id" : 1,
 "name" : "node2.db:27017",
 "health" : 1,
 "state" : 2,
 "stateStr" : "SECONDARY",
 "uptime" : 577454,
 "optime" : Timestamp(1494371417, 1),
 "optimeDate" : ISODate("2017-05-09T23:10:17Z"),
 "lastHeartbeat" : ISODate("2017-05-16T15:43:29Z"),
 "lastHeartbeatRecv" : ISODate("2017-05-16T15:43:29Z"),
 "pingMs" : 0,
 "syncingTo" : "node1:27017"
 },
 {
 "_id" : 2,
 "name" : "node3.db:27017",
 "health" : 1,
 "state" : 2,
 "stateStr" : "SECONDARY",
 "uptime" : 577354,
 "optime" : Timestamp(1494371417, 1),
 "optimeDate" : ISODate("2017-05-09T23:10:17Z"),
 "lastHeartbeat" : ISODate("2017-05-16T15:43:29Z"),
 "lastHeartbeatRecv" : ISODate("2017-05-16T15:43:29Z"),
 "pingMs" : 0,
 "syncingTo" : "node1:27017"
 }
 ],
 "ok" : 1
}
rs0:PRIMARY>

Caso tenha ocorrido algum erro na sincronização das informações, você poderá visualizar através deste retorno, e claro terá informações para realizar a correção do caso. Existem muitas maneiras de realizar esse tipo de configuração de cluster, algumas mais complexas e outras mais simples, em nosso teste esta foi a forma onde tivemos mais objetividade e assertividade na configuração, baseado nessas informações você pode montar/adaptar os passos que atendem a sua demanda.

Um ponto muito importante nesse ambiente é a forma como ocorre a recuperação em caso de desastre, pois você pode parar o master (primário) e você poderá ver através do rs.status(); o comportamento do cluster elegendo um novo master, ou seja, dessa forma seu cluster ficará quase que 100% a prova de falhas ;).

Esperamos ter ajudado, e se tiver alguma dificuldade ou dúvida com relação a este ambiente por favor entre em contato conosco para que possamos ajudar o/. E como sempre, nos ajude divulgando o blog, isso é muito importante para nós.

Grande abraço!

Docker e Flannel

Oi Pessoal!

A intenção hoje é chamar a atenção para um dos recursos que está presente no Docker e que as vezes passa despercebido para muitos dos utilizadores dessa tecnologia, que é trabalhar com rede. Já vimos aqui no blog como trabalhar com Docker Network, mas com a ascensão do docker 1.12 ficou ainda mais fácil mudar a forma de como se utilizar rede, você pode integrar o Docker com diversos plugins e tecnologias de rede, hoje veremos como utilizar o Flannel para ser seu backend de rede.

Para quem não conhece, o Flannel é um projeto que foi inicialmente desenvolvido pela CoreOS, mas que atualmente é mantido, além da própria empresa, pela comunidade. Ele utiliza um serviço de chave-valor para persistir as informações sobre alocação de ip entre hosts e containers, por padrão ele utiliza o Etcd (que por coincidência é desenvolvido e mantido pela CoreOS também 🙂 ), veja abaixo algumas das características do Flannel e a diferença em relação ao Docker Network:

                             Flannel             Docker Overlay Network
Modelo                       VxLAN ou UDP                           VxLAN
Isolamento de aplicação                       CIDR Schema                     CIDR Schema
Protocolos suportados                             Todos                           Todos
Serviço de nome                               Não                             Não
Requer backend externo                               Sim                             Sim
Tipo de encriptação                               TLS                             Não
 vNIC por Container                               Não                             Sim
Restrição de subnet por container                               Não                             Sim

Como podem notar o Flannel tem algumas vantagens e desvantagens em relação ao Docker Network, destacam-se a comunicação encriptada que ele provê e não ter a possibilidade de criar subnet diferente por cliente, ou seja, todos os containers fazem parte da mesma rede. Se você está pensando em utilizar Kubernetes, leve em consideração o Flannel, pois é um dos plugins mais utilizados com esse orquestrador. Vamos colocar a mão na massa?

Instalação e dependências

Servidor Etcd

Nosso exemplo foi baseado no Centos 7 64 bits, mas você pode utilizar em outro S.O sem problema. Vamos lá, primeiramente você deve habilitar o repositório Extras do CentOS, para isso edite o CentOS-Base.repo e coloque habilite-o, veja:

# CentOS-Base.repo
#
# The mirror system uses the connecting IP address of the client and the
# update status of each mirror to pick mirrors that are updated to and
# geographically close to the client. You should use this for CentOS updates
# unless you are manually picking other mirrors.
#
# If the mirrorlist= does not work for you, as a fall back you can try the
# remarked out baseurl= line instead.
#
[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#released updates
[updates]
name=CentOS-$releasever - Updates
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/
gpgcheck=1
enable=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

Agora vamos instalar o backend do Flannel, que é o etcd (serviço de chave valor em memória), o etcd é utilizada para armazenar as informações sobre as redes que serão distribuídas entre os containers. Vamos lá:

[root@host-etcd ~]#  yum install etcd

Edite o /etc/etcd/etcd.conf alterando o ETCD_LISTEN_CLIENT_URLS, em nosso teste colocamos 0.0.0.0, ou seja, o etcd estará acessível através de qualquer ip do host, veja:

ETCD_NAME=default
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379"

Cuide para que a porta 2389 esteja liberado no firewall desse host. Agora vamos aproveitar que estamos no host de etcd e vamos já deixar configurado a rede que será utilizada pelos hosts de Docker. Para isso, crie um arquivo, em nosso lab: rede.json e adicione o seguinte código:

{
 "Network": "192.0.0.0/16",
 "SubnetLen": 22,
 "Backend": {
 "Type": "vxlan",
 "VNI": 1
 }
}

Onde:

  • Network – Bloco de rede que será distribuída para os containers;
  • SubnetLen – Tamanho do bloco alocada para cada host, ou seja, cada host receberá um /22 do bloco 192.0.0.0/16, isso que dizer que poderíamos ter até 1000 containers em 1 host.
  • Type – Tipo do canal que será utilizado, lembra que no Flannel você pode escolher em vxlan ou udp, pois bem, é aqui que você configura isso, por experiência própria, recomendamos utilizar vxlan, pois há menos overhead.

O que faço com isso agora? Importe no etcd ;), lembrando que o etcd trabalha com chave=valor, então execute:

[root@host-etcd ~]# etcdctl set /mundodocker/network/config < rede.json

Onde:

  • /mundodocker/network/config – chave
  • rede.json – valor

Agora vamos ver se essas informações foram persistidas no etcd:

[root@host-etcd ~]#etcdctl get /mundodocker/network/config
{
 "Network": "192.0.0.0/16",
 "SubnetLen": 22,
 "Backend": {
 "Type": "vxlan",
 "VNI": 1
 }
}

 

Servidores Docker

Não esqueça, em todos os hosts temos que ativar o repositório de extras do CentOS, da mesma forma que foi feita acima. Vamos as instalações das dependências, o docker é claro:

[root@host-docker01 ~]# curl -fsSL https://get.docker.com/ | sh

Agora o Flannel:

[root@host-docker01 ~]# yum install flannel -y

Vamos as configurações agora, começamos pelo Flannel, para isso, edite o arquivo: /etc/sysconfig/flanneld, modificando as linhas:

FLANNEL_ETCD = Servidor do Etcd.
FLANNEL_ETCD_KEY = Key definida no Etcd.

Veja como deve ficar:

[root@host-docker01 ~]# cat /etc/sysconfig/flanneld | grep -v ^#
FLANNEL_ETCD="http://host-etcd:2379"
FLANNEL_ETCD_KEY="/mundodocker/network"

Agora inicie o serviço do Flannel:

[root@host-docker01 ~]# systemctl enable flanneld
[root@host-docker01 ~]# systemctl start flanneld

Note que ele criará uma interface de rede chamada flannel.1, que será utilizada para comunicação entre os hosts. Note também que será criado um arquivo dentro de: /run/flannel/subnet.env com as informações sobre a rede que o host assumiu, esse arquivo será utilizado daqui a pouco.

Agora vamos configurar nosso serviço de Docker para utilizar a rede Flannel configurada anteriormente, para isso, edite o arquivo: /lib/systemd/system/docker.service adicionando a linha: EnvironmentFile=-/run/flannel/subnet.env e alterando a forma como o Docker será iniciado, veja como deve ficar:

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target docker.socket
Requires=docker.socket
 
[Service]
Type=notify
EnvironmentFile=-/run/flannel/subnet.env
ExecStart=/usr/bin/docker daemon -H fd:// $OPTIONS $DOCKER_STORAGE_OPTIONS --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU}
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
 
[Install]
WantedBy=multi-user.target

Agora basta iniciar/reiniciar o serviço do Docker:

[root@host-docker01 ~]# systemctl enable docker.service
[root@host-docker01 ~]# systemctl start docker.service

Não esqueça de fazer isso em todos os hosts de Docker que você quer colocar nessa rede.

 

Vamos testar?

Basta você criar um container em cada host, em nosso teste criamos um baseado na imagem do CentOS:

Host 1:

[root@host-docker01 ~]# docker run -it centos /bin/bash

Host 2:

[root@host-docker02 ~]# docker run -it centos /bin/bash

Agora basta você pingar de um container para o outro, ou se quiser fazer um teste mais real, você pode instalar um banco de dados ou servidor web em um dos containers e tentar acessar via ip da rede Flannel pelo outro container. Como você deve ter notado esse processo é bem simples e resolve grandes problemas com rede no Docker, como por exemplo a comunicação entre multi-host.

Por hoje era isso, espero que tenha ajudado, e como sempre, nos ajude divulgando o blog 😉

Abraço!