Assim como hosts de virtualização baseados em tecnologias como Linux KVM, VMWare vSphere, Microsoft Hyper-V, etc, hosts que hospedam containers também demandam ambientes que ofereçam alta disponibilidade, afinal, a grande densidade que servidores apresentam nos dias atuais, com dezenas de máquinas virtuais em média em cada host, e de centenas as milhares de containers em média, certamente leva a necessidade da implantação de Clusters Fail Over.
Cluster não só dão a possibilidade de fazer com que estes hosts atuem de forma conjunta e coordenada, como proveem recursos de alta disponibilidade e balanceamento de carga.
Este artigo irá demonstrar que o processo de escalabilidade dentro do ambiente de cluster sob Docker Swarm é muito simples, bem como, a alta disponibilidade de containers também será proporcionada neste mesmo ambiente.
No final deste artigo veremos também a instalação, configuração e operações básicas sob a ferramenta de orquestração Portainer, ferramenta essa que permitirá realizar diversas operações em nosso Cluster através de uma interface web muito intuitiva, com dashboards bem elaborados e com baixo consumo de recurso sob o host que estiver sendo executada.
Resumidamente, neste artigo estaremos:
- Instalando e configurando o Docker em três hosts com Debian 9;
- Configurando um Cluster com Docker SWARM;
- Criando um serviço que utilizará uma imagem do Apache e operará sob o cluster Docker;
- Testando o mecanismo de load balance;
- Realizando a escalação de instâncias do container do Apache;
- Ativando um instância como serviço da solução de orquestração Portainer;
- Realizando operações básicas sob a interface Web do Portainer.
- Configurando autenticação para o portal do orquestrador Portainer
Iremos denominar cada um dos três hosts que irão compor o Cluster de:
- containerhost01 – 192.168.1.11
- containerhost02 – 192.168.1.12
- containerhost03 – 192.168.1.13
Instalando e configurando o Docker em três hosts com Debian 9
Os passos a seguir deverão ser executados nos três servidores:
– Instale os pacotes que serão dependências no restante do processo de instalação do Docker Engine:
sudo apt install -y curl apt-transport-https \ software-properties-common ca-certificates
– Adicione a chave do repositório:
curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add -
– Adicione o repositório ao sistema:
sudo add-apt-repository "deb https://apt.dockerproject.org/repo/ \ debian-$(lsb_release -cs) \ testing"
– Atualize o banco de dados de referência de pacotes do sistema:
sudo apt-get update -y
– Instale o pacote do Docker Engine:
sudo apt-get install -y docker-engine
– Execute os daemons do Docker e ative o serviço na inicialização do sistema:
sudo systemctl start docker sudo systemctl enable docker
– Adicione o atual usuário logado no grupo de usuário Docker. Essa ação fará com que não seja necessária a utilização do comando sudo para utilização docker.
sudo gpasswd -a "${USER}" docker
– Reinicie o sistema para testar a inicialização do serviço do Docker:
sudo reboot
– Liste os containers criados (em execução e não em execução):
docker ps -a
Obviamente a listagem não retornará nada, pois não existem container criados.
Configurando um Cluster com Docker SWARM
Docker swarm é um gerenciador e orquestrador de cluster Docker, possibilitando que você tenha de ter vários Docker engines (hosts com Docker Engine em execução) atuando de forma conjunta para servir os contêineres com alta disponibilidade e escalabilidade.
O Docker Swarm realiza balanceamento de carga, descoberta automática de novos serviços, escala o ambiente em momento de alta demanda e resolve automaticamente em caso de falhas em serviços em contêineres.
Em uma configuração básica, você inicializa seus container utilizando o comando docker run (que automaticamente faz o pull da imagem em servidor de repositório de imagens (denominado registry), cria o container e dispara a execução deste container).
Quando estamos utilizando uma configuração de Cluster através do Docker Swarm, a execução dos containers utilizará a abordagem denominada serviços.
Um Cluster Docker Swarm possui dois tipos de elementos: Manager e Worker.
Um nó é uma instância de um Docker Engine em execução em um host que esteja participando do cluster Swarm.
Iniciado um serviço de container no cluster swarm, torna-se necessária a definição de um serviço para um nó do tipo manager.
Quando um nó é definido como manager, este torna-se responsável por disparar unidades de trabalho denominadas tasks (tarefas).
Portanto, o sysadmin irá definir e solicitar novas tarefas a partir de um dos nós que atue como manager no cluster, e esta tarefas são disparadas para nós do tipo worker, mas dependendo da carga atual do Cluster, nós do tipo manager também executarão tarefas solicitadas.
Nós do tipo manager possuem a responsabilidade da orquestração e gerenciamento do cluster, onde caso exista mais de um manager no cluster, ocorrerá um processo de eleição que selecionará automaticamente o líder manager que conduzirá as orquestração das tarefas. Este processo é semelhante ao que temos em redes Microsoft baseadas nos protocolos SMB/CIFS, onde um local browser e master browser são eleitos para, por exemplo, armazenar a lista de hosts e recursos da rede para que esta seja utilizada no processo de resolução.
Definindo o nó manager no Cluster Swarm
Neste artigo teremos 3 nós, sendo 1 manager e dois workers, portanto, execute o comando a seguir no containerhost01
docker swarm init --advertise-addr 192.168.1.11:2377
Será exibida a informação na primeira linha que Docker Engine deste host passou a atuar no modo swarm, especificamente como modo manager.
Foi exibido o comando docker swarm join associado ao token gerado e o endereço IP do manager, por exemplo:
docker swarm join \
–token SWMTKN-1-41eozb4h6ucm58pwv6zxvllhggniv6kbo92hyyhja9z07whmtb-62q0t8pc373ff576xlbxp8bjj \
192.168.1.10:2377
Deste modo, copie e cole o comando exibido no shell do containerhost01 nos outros dois hosts (containerhost01 e containerhost02).
Após a execução do comando docker swarm join, em ambos os nós deverá ser exibida a frase:
This node joined a swarm as a worker.
Criando uma rede para utilização no serviço que será implementado no Cluster
A criação de uma rede para utilização no serviço que implantaremos no Cluster é importante, pois através desta rede conseguiremos disponibilizar a funcionalidade de balanceamento de carga, sem necessitar configurar um balanceador externo, por exemplo, baseado no NGINX ou HAProxy.
Através desta rede será interligado os serviços que irão operar no ambiente do Cluster.
Existem diversas possibilidade de criar redes no Docker e, no caso, utilizaremos um tipo de rede chamada overlay, onde é possível anexar vários serviços de contêineres na mesma rede.
Por padrão, o serviço de descoberta interno do swarm entrega um endereço IP (VIP) e uma entrada DNS (também por um serviço interno) para cada contêiner na mesma rede.
Para utilizar redes overlay no seu cluster é necessário que cada nó do swarm tenha regras no Firewall autorizando a conectividade nas portas 7946 e 4789 (ambas nos protocolos TCP e UDP).
Informe o comando abaixo no host containerhost01 para criar a rede com o nome de ClusterNet e em seguida listar as atuais redes criadas para confirma a existência da nova rede:
docker network create -d overlay --subnet 10.0.10.0/24 ClusterNet docker network ls
Execute o comando docker network ls nos outros dois hosts (containerhost02 e containerhost03) e veja que as redes não foram criadas nos nós, pois, somente no momento do criação do serviço do Cluster que utilizará esta rede para dispararmos as tarefas, é que a rede será criada. No caso, entende-se tarefa como a inicialização de um ou mais container nos hosts (nós) do Cluster.
Criando um serviço que utilizará uma imagem do Apache e operará sob o cluster Docker
As instâncias que iremos inicializar neste novo serviço são de um Web Server baseado no Apache. Entretanto, ao invés de realizarmos o pull de uma imagem oficial do projeto Apache, iremos utilizar uma imagem que foi adaptada para que a página padrão do Apache mostre o ID do container.
Deste modo, durante os testes de load balance poderemos ver que a página exibida no momento da conexão está em execução em um determinado container (alocado sobre um determinado nó do Cluster), isso graças a exposição do container ID na página padrão do Apache.
docker service create --name webservice1 --network ClusterNet --replicas 3 -p 5001:80 francois/apache-hostname docker service ls
O primeiro comando acima criou uma serviço no Cluster chamado webservice1 utilizando a rede do tipo overlay chamada ClusterNet (criada anteriormente). Este serviço inicializou três réplicas de um container baseado na imagem francois/apache-hostname, bem como, foi estabelecido um mapeamento da porta TCP 5001 (em listening em cada nó do Cluster) para a porta 80 (em listening em cada container alocado no Cluster, no caso, 3 réplicas).
Se realizarmos uma conexão na porta 5001 do nós 192.168.1.10, 192.168.1.11 e 192.168.1.12 o docker engine já realizou os redirecionamento para a rede overlay, o que permitirá que, independente de qual host (nós) seja estabelecida a conexão, a conexão com a porta 80 do Apache em execução em cada container será direcionada de forma balanceada.
O segundo comando (docker service ls) permitiu verificar que o serviço foi criado.
Execute nos três nós o comando:
docker ps
Será possível observar que no total existirão três containers (réplicas) em execução, o que bem provavelmente ocorrerá de forma balanceada, ou seja, um container em execução em cada nó.
Testando o mecanismo de load balance
Em um outro terminal Linux ou Mac OS X que não seja dos hosts (nós), iremos criar um BASH Shell Script muito simples que irá ficar testando o balanceamento de carga durante a conexão em nosso três nós, onde será possível observar que a medida que alternamos a conexão entre os três nós, o Container ID exibida na página do Apache indicará o container que atendendo nosso conexão no momento.
Caso não tenha a ferramenta curl instale ela pois será utilizada no script abaixo.
Crie um arquivo chamado testLB.sh da seguinte forma:
cat << EOF > testLB.sh #!/bin/sh hosts="192.168.1.10 192.168.1.11 192.168.1.12" nHosts=`echo $hosts |wc -w` i=1 clear while [ 1 ]; do if [ $i -eq 4 ]; then i=1 fi h=`echo $hosts |cut -f$i -d" "` echo "Web Server: $h" curl --connect-timeout 3 http://$h:5001 sleep 3 clear ((i++)) done EOF
Atribua a permissão de execução ao script e execute-o:
chmod +x testLB.sh ./testLB.sh
Poderemos ir acompanhando as conexões em cada Web Server em execução em cada container, bem como, verificar que o balanceamento de carga estará sendo realizado.
Realizando a escalação de instâncias do container do Apache
Este sem dúvidas é um dos recursos mais interessantes, pois, com ele, podemos escalar rapidamente mais ou menos instâncias em nosso Cluster com grande facilidade, inclusive, veremos que através da ferramenta Portainer poderemos realizar isso de forma totalmente gráfica.
Execute no nó manager do Cluster (containerhost01) os comandos:
docker service scale webservice1=10 docker service ps
O primeiro está realizando a mudança do número de réplicas para 10 no serviço chamado web (que representa dentro do Cluster Swarm os containers Apache em execução em cada nó). Lembrando que, quando criamos o serviço ele possuía 3 instâncias.
O segundo comando mostra o atual status dos serviços em execução, e também permite acompanhar quantas réplicas estão definidas para o serviço e quantas já estão em execução. Portanto, se o segundo comando mostrar 8/10, significa que o serviço possui definida 10 instâncias para entrar em execução de forma distribuída no Cluster, mas que até o momento somente 8 estão em execução, demonstrando assim que 2 tasks (tarefas) enviadas pelo manager para o nós do Cluster ainda estão em andamento.
Execute nos três nós o comando:
docker ps
Será possível observar que no total existirão dez containers (réplicas) em execução de forma balanceada, podendo por exemplo, ter 4 containers em um nó, 3 container em outro e mais 3 container em outro nó.
Execute no nó manager do Cluster (containerhost01) os comandos:
docker service scale webservice1=6 docker service ps
Assim estaremos escalando o serviço para 6 instâncias, tornando assim a distribuição possivelmente de 2 containers por nó.
É possível definir restrições (constraints) diversas nos serviços de Cluster, podendo por exemplo, iniciar uma instância em particular somente em hosts (nós) com discos SSD, tudo isso de forma automática.
Ativando um instância como serviço da solução de orquestração Portainer
Independente de estarmos inicializando uma instância (container) da imagem portainer como serviço, um aspecto importante para uso de container ou serviço de containers é a persistência de dados.
Iremos criar um diretório que irá armazenar os dados do container, de modo, que quando o container for finalizado e iniciar novamente, o orquestrador portainer possa ler os dados da configurações já realizadas.
Crie um diretório para persistência dos dados:
mkdir -p /data/orquestrador
Criando um serviço para utilizar o orquestrador container
A grande vantagem de inicializar um container utilizando o recurso serviço do Docker Swarm é que podemos contar com a alta disponibilidade do container, deste modo, supondo que tenhamos um Cluster de 6 nós, sendo que 3 deles atuam no modo manager. Deste modo, caso um nó que atua no modo manager fique indisponível, o Docker Swarm irá alocar outro container em um dos outros dois nós que estão em modo manager.
Para que o Docker Swarm possa alocar um container no Cluster, e tal alocação ocorra apenas em nós que são manager no Cluster, iremos utilizar um recurso de constraint, que permitirá definir tal regra.
Execute o comando abaixo no nó manager (containerhost01):
docker service create --name orquestrador -p 9000:9000 --constraint 'node.role == manager' \ --mount type=bind,src=/data/orquestrador,dst=/data --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ portainer/portainer --no-auth -H unix:///var/run/docker.sock
O serviço criado tem o nome orquestrador, bem como, o host manager na qual o container for iniciado irá publicar (expor a porta 9000), de modo que, toda conexão na porta 9000 do host será redirecionada para porta 9000 em listening no container.
O parâmetro –constraint informa que o container somente será iniciado caso o atributo role possua o valor manager, o que justamente faz com o que a instância do container inicie apenas em nós do tipo manager no Cluster.
O parâmetro –mount type=bind,src=/data/orquestrador,dst=/data fará com que o diretório local (existente no nó manager ond e o container será iniciado) seja montado (mapeado) no diretório /data dentro do container, permitindo assim que todos os dados gravados ou lidos no diretório /data do container na verdade são dados existentes no diretório /data/orquestrador. Deste modo, o container terá persistência dos dados no momento que ele for finalizado.
O parâmetro –no-auth é particular dos binários do container portainer, e faz com que o orquestrador não exija usuário e senha em uma primeira execução, o que é interessante para apenas começarmos a explorar um pouco a ferramenta, mas iremos no final do artigo configurar a autenticação no orquestrador.
O parâmetro -H faz com que o Orquestrador consiga estabelecer uma conexão não TCP/IP (via unix sockets) com o host manager que o container estiver sendo executado, pois assim, o Portainer conseguirá comunicar-se com a API do Docker sem utilizar conexões TCP/IP.
Entretanto é possível realizarmos conexões a outros hosts com Docker Engine, onde precisaremos configurar o serviço nestes hosts para expor a API serviço. Assim o Portainer poderá ter outros endpoints adicionados para que sejam gerenciados também, mas isso é um assunto para outro artigo.
Para verificar se o serviço iniciou, e para isso, o container deve ter entrado em execução, informe o comando a seguir:
docker service logs orquestrador
Deverá ser exibida a informação a seguir:
Starting Portainer on :9000
Verifique no nó manager que o serviço está em execução, bem como, que o container está em execução no mesmo nó:
docker service ls docker ps
Acesse o endereço a seguir:
http://192.168.1.11:9000
A imagem a seguir mostra um Dashboard com boa organização e informações muito úteis do nó manager.
Na seção container temos os dois containers referente ao Apache, que foram iniciados neste nó devido o serviço que foi criado (webservice1) ter sido escalado para 6 instâncias. Juntamente destes dois containers temos o container referente ao orquestrador container.
A imagem abaixo expõem de forma clara que o Cluster possui um nó com papel de Manager e outros dois com papel de worker.
Observação: esta imagem foi capturada de um dos labs que realizei, onde os nós estão com endereço IP diferente do apresentado no escopo inicial do artigo.
Realizando operações básicas sobre containers
Na seção containers, clique no nome de um dos dois containers como começam com web
O Portainer permite realizar diversas operações em sua interface web, operações como, por exemplo: Iniciar, Parar, Finalizar de maneira forçada, etc.
Além destas operações visíveis na imagem abaixo, podemos obter métricas de monitoramento referente ao consumo de CPU, Memória, etc e claro, abrir um console diretamente no container (equivalente ao comando: docker attach nome_do_container).
Verificando o consumo de CPU, Memória, Rede e Processos em Execução no Container
Clique na opção Stats. Esta opção apresenta um excelente dashboard com gráficos bem elaborados indicando informações referente ao consumo de CPU, Memória, Rede e os processos em execução no container.
Estabelecendo um terminal com o container em execução
Volte para a página anterior e clique na opção Console, e em seguida no botão Connect.
A imagem abaixo demonstra um recurso muito interessante que traz praticidade para estabelecer um terminal com o container.
No terminal estabelecido com o container podemos por exemplo, podemos verificar a comunicação entre os containers.
Como cada serviço recebe um nome próprio e cada container das réplicas recebe um endereço IP específico, por exemplo, criamos um serviço chamado webservice1, portanto, se eu executar o comando a seguir quatro vezes:
ping tasks.webservice1 -c 1
Teremos a resolução de nomes retornando em cada momento o endereço IP e FQDN de cada container dentro deste serviço.
A image que estamos utilizando em nosso serviço webservice1 não possui a ferramenta dig e nslookup, mas caso tivessem, poderíamos utilizar o comando para obter todos os endereços IP e FQDNS atrelados ao service, graças ao serviço interno de DNS que durante o estabelecimento do serviço sob a rede overlay (que torna transparente a comunicação de cada container independente do nó que esteja), o processo de mapeamento DNS no Docker Swarm é realizado automaticamente.
nslookup tasks.webservice1
Configurando autenticação para o portal do orquestrador Portainer
Iremos remover o serviço que inicializa o container do orquestrador no Cluster, e recriaremos ele com novo parâmetro (–admin-password).
Portanto, remova o serviço orquestrador:
docker service rm orquestrador
Isso obviamente fará com o que o container em execução no nó containerhost01 seja finalizado.
Como provavelmente os seus nós utilizam alguma distribuição minimalista, então certamente a ferramenta htpasswd não está instalada.
Para evitar de instalar pacotes adicionais no host, iremos executar o binário htpasswd proveniente de uma imagem que contém binários e bibliotecas do Apache. Esta sem dúvidas é uma abordagem interessante, pois depois de utilizada a ferramenta o container será removido (devido a opção –rm), e depois iremos remover a imagem.
docker run --rm httpd:2.4-alpine htpasswd -nbB admin LinusTorvalds | cut -d ":" -f 2 docker rmi httpd:2.4-alpine -f
Supondo que o hash gerado seja:
$2y$05$2AIY9Z//ED3aoyRM/aEy7O9q946bEuMe2tiJRTyciv7fKXGV8whbS
Copie o hash gerado pois será utilizado com valor de parâmetro a seguir:
docker service create --name orquestrador -p 9000:9000 --constraint 'node.role == manager' --mount type=bind,src=/data/orquestrador,dst=/data --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock portainer/portainer \ --admin-password '$2y$05$2AIY9Z//ED3aoyRM/aEy7O9q946bEuMe2tiJRTyciv7fKXGV8whbS' \ -H unix:///var/run/docker.sock
Acesse o endereço https://192.168.1.11:9000
Nos próximos artigos falaremos sobre outros orquestradores interessantes como UCP, Rancher, Tuntum, bem como, sobre Docker-machine e Docker-Compose.
6 Comentários
Parabéns, Waldemar!!
Excelente artigo, extremamente detalhado!!
Muito obrigado Jonathan. Em breve estarei publicando outros artigos relacionados a este assunto.
Waldemar,
Muito bom, estava precisando de um material assim, montei aqui usando Virtualbox perfeito.
Obrigado pelo esforço e dedicação.
Waldemar, excelente artigo parabéns.
Estou tendo problema ao executar o comando para criar o serviço do orquestrador portainer no meu cluster swarm
ao digitar o comando:
docker service create –name orquestrador -p 9000:9000 –constraint ‘node.role == manager’ \
–mount type=bind,src=/data/orquestrador,dst=/data –mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
portainer/portainer –no-auth -H unix:///var/run/docker.sock
o docker retorna o seguinte erro:
Error response from daemon: rpc error: code = InvalidArgument desc = ContainerSpec: ” –mount” is not a valid repository/tag
como posso resolvê-lo, obrigado
Parabéns pelo Artigo! Muito didático e funcional.
Parabéns, muito bem explicado.
Fiquei com a seguinte dúvida: ao acessar o ip do portainer, consigo gerenciar containers que ficam hospedados em servidores distintos e em redes distintas, tudo em um único lugar?