Docker Swarm é uma ferramenta de orquestração de containers, que possibilita o usuário a gerenciar multiplos containers distribuidos em diversas máquinas hosts. Habilitando ferramentas para dimensionamento, rede, proteção e manutenção dos containers.
Cluster é um conjunto de computadores que trabalham em grupo de modo a ser visto como um sistema único.
Utilizamos clusters para ganhar mais poder computacional e maior confiabilidade orquestrando um número de máquinas de "baixo custo" para obter um maior poder de processamento.
Utilizamos o nome Node, ou nó, quando nos referimos a qualquer elemento computacional que faz parte de um cluster, seja ele um nó do tipo primário (master) ou um do tipo secundário (follower).
Iremos nos referir aos nossos "computadores¨ como Master/Node
Em um passado não tão distante, os termos Master/Slave eram utilizados para se referir aos nós e as ações na qual eles desempenhavam. Em 2020 um forte movimento aconteceu para acabar com essa terminologia, uma vez que ela traz referências a escravidão. Não é sobre aguardar alguem ser ofendido. É sobre remover estes termos com base em coisas terríveis e desumanas.
Algumas alternativas a serem utilizadas para se referir aos nodes:
- Main / Secondary
- Conductor/ Follower
- Leader / Follower
- Host / Client
- Sender / Reciever
- Producer / Consumer
- Primary / Replica
- Primary / Secondary
- Manager / Worker
Quando trabalhamos com sistemas distribuidos, precisamos de algum algorítmo para tratar da confiabilidade do cluster. Um dos algoritmos mais utilizados para este meio é o Raft Consensus.
O Docker Swarm implementa o algorítmo Raft Consensus para gerencia do estado global do cluster.
O nome Raft é uma palavra em inglês que quer dizer Jangada, a referênca é porque para montarmos uma jangada, necessitamos de, ao menos, três toras de madeira.
Consensus é uma palavra em ingles que quer dizer Consenso. É também um dos problemas fundamentais em sistemas distribuidos com tolerância a falhas. Involve multiplos servidores aceitando valores.
O site The Secret Lives of Data possúi um ótimo guia sobre como ocorre o raft consensus.
Temos também uma página no github dedicado ao raft
Quando temos um sistema composto por um único nó, podemos dizer que este nó armazena um valor único, temos também o cliente que envia este valor para o servidor, neste ponto o cliente e o servidor entram em um "consensus", um valor é simples com um único nó.
Quando temos diversos servidores, como podemos chegar em um consenso ? esse é o problema de sistemas distribuidos.
Como chegamos em um concenso em multiplos nodes? Utilizando algoritmos como o Raft Consensus.
Pensando de uma maneira mais macro, o raft define os nós em três estados básicos:
- Follower
- Candidate
- Leader
Todos os nós começam em um estado de seguidor, se os seguidores não encontrarem um líder, eles se tornam candidatos.
O candidato requisita votos de outros nós e os nós respondem com o voto, o candidato vira um lider se ele tiver os votos de uma maioria dos nodes. Esse processo é chamado de Eleição do Líder
O problema básico evitado ao se utilizar o raft consensus é chamado de split-brain
ou cérebro dividido, e isto acontece da seguinte maneira:
Estamos falando apenas de nodes do tipo Master, ou primário.
O Raft Consensus tolera até (N-1)/2
falhas, e precisa de um quórum de (N/2)+1
para ser funcional.
Tratamos apenas numeros inteiros.
Imagine que temos 2 nodes. Caso um dos nodes perca a comunicação (rede), como ele irá saber se:
- Ele está sem conectividade?
- O outro nó está sem conectividade?
Esta ação é chamada de split-brain
, e o sistema se torna inoperante.
Vamos para o mesmo cenário com 3 nós, onde um node perde a comunicação (rede). O que ocorre neste momento é:
- Os nodes começam a se perguntar (rede) se os outros nós estão operantes.
- Os 2 nodes que estão operantes se comunicam e verificam que são a maioria
(N/2)+1
ou seja(3/2)+1 = 2
e continuam se comunicando. - O nó que está inoperante verifica que ele não é a maioria, e para de responder.
Qual seria o problema que poderiamos encontrar caso o nó que ficou sem conectividade continue a operar? Teriamos duas versões das aplicações rodando, uma em cada lado dos servidores, com conteudos diferentes.
Nós | Tolerância |
---|---|
1 | 0 |
3 | 1 |
5 | 2 |
7 | 3 |
9 | 4 |
Não é comum termos mais de 5 nodes, uma vez que a comunicação entre os nós precisa ser feita de maneira rápida e eficiente, e ao aumentar o número de nodes fazemos com que o volume de replicação de dados aumente, tornando comum a utilização de 3 ou 5
nodes
Mais informações na Documentação Oficial
Trabalharemos com as máquinas master
node-01
e node-02
Ligue as máquinas
$ vagrant up master node01 node02
Acesse a máquina master
$ vagrant ssh master
Para criar o cluster do swarm, precisamos apenas de que o docker esteja instalado na máquina, podemos em seguida executar o comando para verificar seu endereço IP e iniciar o cluster swarm
$ ip -c -br a show enp0s8
$ docker swarm init --advertise-addr 10.20.20.100
Será exibida uma mensagem informando que o swarm foi iniciado e o node é um manager, bem como o token para adicionar mais nós ao swarm.
Swarm initialized: current node (sqtbdppo34ez4i4wsngbdeply) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-4n7u9o6cmhoizpx57umbp1g8nkh5zetuimx06k20nme64syy0t-1x0yu5ogxdlu2b0fs7fcnsc91 10.20.20.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Podemos ver novamente o token para adicionar novos nós ao cluster através do comando docker swarm join-token <manager/worker>
$ docker swarm join-token manager
$ docker swarm join-token worker
Na máquina master, copie o token de worker
$ docker swarm join-token worker
Em um novo terminal, acesse a máquina node01
e execute o comando copiado anteriormente
$ vagrant ssh node01
$ docker swarm join --token SWMTKN-1-4n7u9o6cmhoizpx57umbp1g8nkh5zetuimx06k20nme64syy0t-1x0yu5ogxdlu2b0fs7fcnsc91 10.20.20.100:2377
Repita o passo para a máquina node02
$ vagrant ssh node02
$ docker swarm join --token SWMTKN-1-4n7u9o6cmhoizpx57umbp1g8nkh5zetuimx06k20nme64syy0t-1x0yu5ogxdlu2b0fs7fcnsc91 10.20.20.100:2377
De volta a máquina master, vamos verificar se os nós foram adicionados corretamente.
$ docker node ls
Verifique que o node que estamos atualmente conectados é informado com um *
e podemos ver também quais nodes são manager
ou lider
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
sqtbdppo34ez4i4wsngbdeply * master Ready Active Leader 20.10.7
o122d88a3dwxdkw6nyopgmqu7 node01 Ready Active 20.10.7
tj57th3ri0zouv1f8ui90zv0n node02.docker-dca.example Ready Active 20.10.7
os comandos
docker node
só podem ser executados em nodes do tipomanager
Podemos verificar também que uma rede com o driver overlay
foi criada para comunicação do swarm
$ docker network ls
Podemos promover um node a manager ou rebaixa-lo através do comando docker node promote
e docker node demote
Na máquina master, vamos promover o node01 a manager.
$ docker node ls
$ docker node promote node01
$ docker node ls
Note que seu Manager Status
se tornou Reachable
, ou seja, ele está disponível, caso a máquina esteja sem comunicação, ela ficará no estado de Unreachable
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
sqtbdppo34ez4i4wsngbdeply * master Ready Active Leader 20.10.7
o122d88a3dwxdkw6nyopgmqu7 node01 Ready Active Reachable 20.10.7
tj57th3ri0zouv1f8ui90zv0n node02.docker-dca.example Ready Active 20.10.7
Caso a máquina fique offline veremos o nosso cluster entrar em estado de split-brain
exibindo a seguinte mensagem ao executar o comando docker node ls
Error response from daemon: rpc error: code = Unknown desc = The swarm does not have a leader. It's possible that too few managers are online. Make sure more than half of the managers are online.
Vamos agora rebaixar nosso node a worker.
$ docker node ls
$ docker node demote node01
$ docker node ls
Trabalharemos com apenas um manager pois como falamos na etapa do raft consensus, ter dois managers é pior do que ter apenas um.
Quando trabalhamos com clusters como o docker swarm, precisamos configurar um private registry, porque fazer isto? temos alguns motivos:
- Docker Hub Rate Limiting
- Segurança
- Velocidade
Docker Hub Rate Limiting
Quando utilizamos o serviço do dockerhub, temos um limite de requisição de 100 imagens de containers a cada seis horas por utilização anônima que podemos aumentar para 200 caso utilizemos uma conta gratuita.
Segurança
Muitas das vezes não queremos que nossas imagens sejam enviadas para um registry publico por conter algum tipo de informação/tecnologia sensível.
Velocidade
Quando temos um ambiente complexo, com diversos nodes fazendo o download de imagens diariamente, não queremos depender da nossa banda de internet. Um registry privado ganha velocidade neste quesito, uma vez que ele se comunica com o dockerhub
Primeiramente precisamos conectar via SSH e adicionar a máquina registry ao nosso cluster.
Caso precise, execute docker swarm join-token worker
na máquina manager para copiar o comando de join
$ vagrant ssh registry
$ docker swarm join --token
SWMTKN-1-0cfecsrgcdvuncxosbwpgu8yg674b4lur8643tizkeilnplhax-4wow83y7dw51vvy5d281347z1 10.20.20.100:2377
O próximo passo é adicionar o bloco de insecure registry a todas máquinas do nosso cluster.
{
"insecure-registries" : ["registry.docker-dca.example:5000"]
}
Podemos fazer isto executando o seguinte comando em todos os nodes
$ echo '{ "insecure-registries" : ["registry.docker-dca.example:5000"] }' | sudo tee /etc/docker/daemon.json ; sudo systemctl restart docker
Em ambientes de produção, configuramos nosso registry como seguro e adicionamos a ele um certificado SSL, mais informações podem ser encontradas na Documentação Oficial
Agora que ja configuramos os nodes podemos efetuar o deploy do nosso registry.
Execute o comando na máquina registry
$ docker container run -dit --name registry -p 5000:5000 registry:2
Utilizamos a tag
registry:2
porque utilizamos o deploy versãov2
do registry, que é a mais atual.
Para enviar imagens para nosso registry precisamos criar uma tag da imagem e efetuar o push. Podemos fazer isto a partir de qualquer máquina que tenha acesso a nosso registry.
$ docker image pull alpine
$ docker image tag alpine registry.docker-dca.example:5000/alpine
$ docker image push registry.docker-dca.example:5000/alpine
O Docker Registry por padrão não possui nenhuma interface para visualizar as imagens, mas podemos através de um simples curl
verificar os repositórios existentes.
$ curl http://registry.docker-dca.example:5000/v2/_catalog
Teremos como resposta um json.
{
"repositories": ["alpine"]
}
Podemos tambem listar as tags acessando o endpoint v2/<image_name>/tags/list
$ curl http://registry.docker-dca.example:5000/v2/alpine/tags/list
{
"name":"alpine","tags":["latest"]
}
Agora que ja aprendemos como enviar images para nosso private registry, iremos fazer o download das imagens que utilizaremos nos próximos laboratórios
nginx
mysql:5.7
wordpress
caiodelgadonew/docker-supermario
traefik:v2.4
Para facilitar nosso processo, iremos criar um pequeno script em bash para automatizar este processo.
$ vim images.sh
for image in 'nginx' 'mysql:5.7' 'wordpress' 'caiodelgadonew/docker-supermario' 'traefik:v2.4'
do
docker image pull $image
docker tag $image registry.docker-dca.example:5000/$image
docker push registry.docker-dca.example:5000/$image
done
Vamos executar o script
$ chmod +x images.sh
$ ./images.sh
Após a finalização do script podemos visualizar nosso catalogo e verificar as imagens disponíveis
$ curl http://registry.docker-dca.example:5000/v2/_catalog
Caso você tenha o
jq
instalado em sua máquina podemos visualizar de maneira mais agradável
$ curl http://registry.docker-dca.example:5000/v2/_catalog | jq
{
"repositories": [
"alpine",
"caiodelgadonew/docker-supermario",
"mysql",
"nginx",
"traefik",
"wordpress"
]
}
Podemos verificar as tags dos repositórios que não utilizaremos a tag latest como por exemplo o mysql
e o traefik
$ curl http://registry.docker-dca.example:5000/v2/mysql/tags/list
$ curl http://registry.docker-dca.example:5000/v2/traefik/tags/list
Quando falamos de Swarm, precisamos entender dois recursos importantes, os serviços e as tasks
Services -> É a definição de um estado desejado. Tasks -> É criada a partir de um serviço que da origem aos containers a serem executados nos nodes.
Quando utilizamos os serviços descrevemos o estado desejado, como por exemplo:
Com isto o swarm manager irá criar as tasks e dividi-las entre os nodes disponíveis, de forma a atender, se possível, o estado desejado.
Uma task é uma unidade atomica agendada em um swarm. Quando declaramos um estado desejado de um serviço criando ou atualizando o serviço, o orquestrador identifica o estado desejado através do agendamento de tasks.
Uma task é um mecanismo uni-direcional, que navega entre uma séria de estados: assigned
, prepared
, running
, etc.. Se uma task falha, o orquestrador remove a task e o container e em seguida cria uma nova task para a substituir de acordo com o estado desejado especificado pelo serviço
Estado | Descrição |
---|---|
NEW |
Task foi inicializada . |
PENDING |
Recursos estão sendo alocados |
ASIGNED |
Task foi atribuida a um nó |
ACCEPTED |
Task aceita por um nó worker |
PREPARING |
Docker está preparando a task |
STARTING |
Docker esta iniciando a task |
RUNNING |
Task em execução |
COMPLETE |
Task finalizou sem error code |
FAILED |
Task finalizou com error code |
SHUTDOWN |
Docker requisitou o desligamento da task |
REJECTED |
O nó worker rejeitou a task |
ORPHANED |
O nó esteve down por muito tempo |
REMOVE |
A task não terminou mas o recurso associado foi removido ou reduzido |
Existem dois tipos de deployment de serviço, o replicated e o global.
Um serviço replicated, é o que informamos a quantidade de tasks identicas que iremos executar, por exemplo quando decidimos fazer um deploy de um webserver com três replicas, servindo o mesmo conteúdo.
O serviço gobal, é o serviço que vai executar em todos os nós, por isso o nome global. Não existe um número de tasks pré-especificadas. Sempre que um novo nó for adicionado ao swarm o orquestrador criará uma task e o agendador (scheduler) atribui a task ao novo nó. Este tipo de serviço é amplamente utilizado quando falamos de agents de monitoramento, scanners anti-virus ou algum serviço que precisa ser executado em cada nó do swarm.
O diagrama abaixo mostra um serviço com três replicas em amarelo e um serviço global em cinza
Para gerenciar os serviços utilizamos o comando docker service
$ docker service --help
Vamos criar um serviço para executar o nginx
$ docker service create --name webserver registry.docker-dca.example:5000/nginx
Podemos listar os serviços através do subcomando ls
e listar as tasks através do comando ps
$ docker service ls
$ docker service ps webserver
Através do
docker service ps
podemos verificar o estado da task, o id da task e em qual nó a task está sendo executada, bem como se houveram erros e quais portas estão publicadas.
Para publicar alguma porta, podemos executar um update
no service com o parametro --publish-add
$ docker service update --publish-add 80 webserver
$ docker service ls
Por padrão uma porta aleatória é adicionada ao service (30000 +)
Vemos também que o serviço tem uma nova task e sua task anterior entra em estado de shutdown
$ docker service ps webserver
No nosso caso o serviço está rodando no node01
podemos acessar por um navegador ou através do curl e verificar que o serviço está sendo executado.
$ curl http://node01.docker-dca.example:30000
Também é possivel inspecionar o serviço para identificarmos alguns detalhes interessantes
$ docker service inspect webserver
$ docker service inspect --pretty webserver
A opção
--pretty
exibe a informação de uma maneira mais agradável para humanos.
Endpoint Mode: vip
Ports:
PublishedPort = 30000
Protocol = tcp
TargetPort = 80
PublishMode = ingress
Podemos ver que:
- O endpoint está sendo executado como
vip
ou seja,virtual ip
. - A porta de destino é a
80
,TargetPort
- A porta publicada é a porta
30000
,PublishedPort
- O modo de publicação é o
ingress
ou seja, a rede que gerencia e controla os dados relacionados ao serviço de swarm.
Vamos remover nosso serviço
$ docker service rm webserver
Assim como fazemos com containers, podemos alterar o comando que o container deve executar, basta adicionar o comando ao final.
$ docker service create --name pingtest registry.docker-dca.example:5000/alpine ping google.com
Também podemos verificar os logs do serviço.
$ docker service logs pingtest
Agora que temos nosso serviço sendo executado, podemos fazer jus ao benefício da elasticidade e escala-lo, ou seja, aumentar a quantidade de replicas em execução.
$ docker service scale pingtest=3
$ docker service ls
$ docker service ps pingtest
também podemos executar o comando
$ docker service update --replicas 3 pingtest
Agora que temos nosso serviço executando um container em cada node, podemos verificar os logs de todos os containers de maneira agregada
$ docker service logs -f pingtest
Podemos também verificar os services que estão rodando em cada container
$ docker node ps master.docker-dca.example
$ docker node ps node01.docker-dca.example
$ docker node ps node02.docker-dca.example
Quando precisamos efetuar algum tipo de manutenção em algum node do cluster, precisamos garantir que a manutenção seja feita de maneira correta.
Para este tipo de situação precisamos fazer o processo de drain
ou drenagem dos nós.
Isto é possível da seguinte maneira:
Primeiramente vamos aumentar o numero de replicas de nosso pingtest
para 7
$ docker service update --replicas 7 pingtest
$ docker service ps pingtest
Podemos ver que nosso serviço está sendo executado em diversos nodes. Vamos colocar nosso node01
em estado de drenagem através do comando:
$ docker node update node01.docker-dca.example --availability drain
Podemos verificar agora como nosso serviço e nosso node estão trabalhando
$ docker service ps pingtest
$ docker node ls
$ docker node inspect --pretty node01.docker-dca.example
Vemos que nosso node esta em estado de Drain
e as tasks foram direcionadas para outros nodes.
Para voltar nosso node para um estado de disponível podemos mudar sua avaliability
para active
$ docker node update node01.docker-dca.example --availability active
Note que as tasks não serão redistribuidas uma vez que estão sendo executadas com sucesso.
$ docker service ps pingtest
Caso novas tasks sejam requistadas, o nosso node agora é um candidato para recebe-las
Remova o serviço
$ docker service rm pingtest
$ docker service ls
Em termos de Serviços Swarm, um secret é um blob data
como senha, chave privada ssh, certificado SSL ou qualquer outro dado que NÃO deve ser transmitido pela rede ou armazenado sem criptografia no código fonte da aplicação.
Um blob (do inglês: Binary Large OBject, basic large object, BLOB ou BLOb, que significa objeto grande binário ou objeto grande básico na tradução literal), é uma coleção de dados binários armazenados como uma única entidade.
Para gerenciar os secrets no swarm, utilizamos o comando docker secret
$ docker secret --help
$ docker secret ls
Para criar um secret precisamos passa-lo através do STDIN, ou através de um arquivo.
$ echo "caiodelgadonew123" | docker secret create senha_db -
$ docker secret inspect --pretty senha_db
O secret criado é armazenado no arquivo
/run/secrets/<secret_name>
no container em execução
Vamos executar um container mysql
passando a senha como um secret
$ docker service create --name mysql_database \
--publish 3306:3306/tcp \
--secret senha_db \
-e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/senha_db \
registry.docker-dca.example:5000/mysql:5.7
Vamos instalar o client do mariadb, verificar em qual servidor está sendo executada a task com o container e vamos conectar passando a senha que configuramos no arquivo.
$ sudo apt-get install mariadb-client -y
$ docker service ps mysql_database
$ mysql -h node01.docker-dca.example -u root -pcaiodelgadonew123
> CREATE DATABASE caiodelgadonew;
> SHOW DATABASES:
> EXIT
Destrua o serviço
$ docker service rm mysql_database
Vamos criar agora uma rede overlay para suportar um serviço do nginx
$ docker network create -d overlay dca-overlay
$ docker network inspect dca-overlay
$ docker service create --name webserver --publish target=80,published=80 --network dca-overlay registry.docker-dca.example:5000/nginx
$ docker node ps
Verifique na máquina node01
que não existe a rede dca-overlay
que criamos
$ vagrant ssh node01
$ docker network ls
Na máquina master
vamos efetuar o scaling para 3 replicas do webserver
$ docker service scale webserver=3
$ docker service ps webserver
Na máquina node01
verifique que agora que o serviço está em execução, a rede foi criada com o mesmo ID da máquina master
$ docker network ls
Acesse os endereços dos servidores pelo navegador e veja a página do nginx sendo executada
http://master.docker-dca.example/ http://node01.docker-dca.example/ http://node02.docker-dca.example/
Remova o serviço
$ docker service rm webserver
Para esta etapa, precisamos que o plugin trajano/nfs-volume
esteja instalado em todas as máquinas que fazem parte do swarm, caso não esteja instalado, verifique a aula 03-volumes
na seção NFS Volume
Verifique se o plugin esta instalado e se o exports
é reconhecido em todas as máquinas
$ docker plugin ls
$ showmount -e master.docker-dca.example
Na máquina master, crie o volume
$ docker volume create -d trajano/nfs-volume-plugin \
--opt device=master.docker-dca.example:/home/vagrant/storage \
--opt nfsopts=hard,proto=tcp,nfsvers=3,intr,nolock volume_nfs
$ docker volume inspect volume_nfs | jq
Crie um serviço do nginx com 3 replicas apontando para o volume
$ docker service create --name webserver \
--replicas 3 \
--publish 80:80 \
--network dca-overlay \
--mount source=volume_nfs,target=/usr/share/nginx/html/ \
registry.docker-dca.example:5000/nginx
$ docker service ps webserver
Verifique o conteudo do nginx
$ curl master.docker-dca.example
$ curl node01.docker-dca.example
$ curl node02.docker-dca.example
Altere o conteúdo da pagina e verifique o conteúdo do nginx
$ echo "<marquee> VOLUME NFS DOCKER SWARM</marquee>" | tee -a /home/vagrant/storage/index.html
$ curl master.docker-dca.example
$ curl node01.docker-dca.example
$ curl node02.docker-dca.example
Remova o serviço
docker service rm webserver
Quando executamos o Docker em modo swarm, podemos utilizar o comando docker stack deploy
para fazer o deploy de uma aplicação completa no swarm. O comando deploy
aceita um descritivo de stack no formato de um arquivo compose.
Para trabalharmos com stacks, precisamos utilizar o arquivo compose com sua versão 3 ou superior.
Para entender o funcionamento do stack, iremos efetuar o deploy do webserver que anteriormente subimos com o docker-compose.
Vamos começar com o básico.
Primeiramente crie uma pasta para armazenarmos arquivos do stack e o arquivo webserver.yml onde iremos definir nosso compose
$ mkdir -p ~/stack
$ cd ~/stack
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
Para fazer o deploy podemos utilizar a opção --compose-file
ou -c
$ docker stack deploy --compose-file webserver.yml nginx-webserver
$ docker stack ls
Verifique os status do stack através do comando docker stack services
$ docker stack services nginx-webserver
Veja que o serviço foi iniciado com o mode replicated
com uma replica apenas.
Podemos ver os status das tasks do service através do comando docker stack ps
$ docker stack ps nginx-webserver
Agora que sabemos onde nossa task está sendo executada, podemos acessar a mesma através do endereço do servidor .
http://master.docker-dca.example
vamos alterar nosso serviço agora para executar 2 replicas adicionando opções de deploy a mesma.
$ vim webserver.yml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
replicas: 2
restart_policy:
condition: on-failure
Atualize a stack ae verifique os status:
$ docker stack deploy -c webserver.yml nginx-webserver
$ docker stack services nginx-webserver
$ docker stack ps nginx-webserver
Podemos ver que nossa stack está sendo executada agora nos nós node01 e node02.
Vamos explorar agora o deploy do tipo global, para isto vamos alterar nosso arquivo compose
$ vim webserver.yml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
ports:
- 80:80
deploy:
mode: global
restart_policy:
condition: on-failure
Para alterar nosso serviço para global, precisamos primeiramente destruir o mesmo e subi-lo novamente.
$ docker stack rm nginx-webserver
$ docker stack deploy -c webserver.yml nginx-webserver
$ docker stack services nginx-webserver
$ docker stack ps nginx-webserver
Note que agora ao utilizar o mode como global, temos nossa task executando exatamente 1 container em cada servidor, este tipo de utilização é muito util quando precisamos garantir que todos os containers possuam por exemplo um determinado container de monitoramento.
Vamos remover nosso serviço e alterar alguns outros parâmetros:
$ docker stack rm nginx-webserver
$ vim webserver.yml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
deploy:
mode: replicated
replicas: 4
placement:
constraints:
- node.role==manager
restart_policy:
condition: on-failure
Utilizamos as placements constraints para indicar as preferencias de onde queremos executar nossos containers, desta maneira podemos atrelar eles para serem executados apenas em nós do tipo manager, ou até mesmo em nós que estão executando uma determinada versão de sistema operacional
$ docker stack deploy -c webserver.yml nginx-webserver
$ docker stack services nginx-webserver
$ docker stack ps nginx-webserver
Vamos alterar agora para executarmos 5 replicas apenas nos nós que sejam workers e estejam rodando o ubuntu 18.04
Para isto iremos adicionar uma label no node01
$ docker node update --label-add os=ubuntu18.04 node01.docker-dca.example
$ vim webserver.yml
version: '3.9'
services:
webserver:
image: registry.docker-dca.example:5000/nginx
hostname: webserver
deploy:
mode: replicated
replicas: 4
placement:
constraints:
- node.role==worker
- node.labels.os==ubuntu18.04
restart_policy:
condition: on-failure
$ docker stack deploy -c webserver.yml nginx-webserver
$ docker stack services nginx-webserver
$ docker stack ps nginx-webserver
Existem diversas outras propriedades que podemos utilizar com o compose, podemos verificar todas no Docker Compose Reference File - Deploy
Vamos agora derrubar nosso webserver e criar um compose para subir o wordpress.
$ docker stack rm webserver
$ vim wordpress.yml
version: "3.9"
volumes:
mysql_db:
driver: trajano/nfs-volume-plugin
driver_opts:
device: master.docker-dca.example:/home/vagrant/mysql_db
nfs_opts: hard,proto=tcp,nfsvers=3,intr,nolock
networks:
wp_overlay:
services:
wordpress:
image: registry.docker-dca.example:5000/wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: caiodelgadonew@youtube
WORDPRESS_DB_NAME: wordpress
networks:
- wp_overlay
deploy:
mode: replicated
replicas: 2
restart_policy:
condition: on-failure
db:
image: registry.docker-dca.example:5000/mysql:5.7
volumes:
- mysql_db:/var/lib/mysql
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: caiodelgadonew@youtube
MYSQL_RANDOM_ROOT_PASSWORD: '1'
networks:
- wp_overlay
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
Antes de executar os containers, vamos criar o volume_nfs na máquina master
$ mkdir ~/mysql_db
$ echo "/home/vagrant/mysql_db/ 10.20.20.0/24(no_root_squash,rw)" | sudo tee -a /etc/exports
$ sudo systemctl restart nfs-server
Vamos fazer o deploy da stack:
$ docker stack deploy -c wordpress.yml wordpress-stack
$ docker stack services wordpress-stack
$ docker stack ps wordpress-stack
Acesse o navegador e configure a webpage
Note que podemos acessar o site de qualquer endereço do cluster, uma vez que temos um vip configurado.
http://master.docker-dca.example:8080/
Titulo do Site: Wordpress - Stack
Nome de Usuário: caiodelgadonew
Senha: caiodelgadonew@youtube
email: caiodelgadonew@docker-dca.example
Podemos também gerenciar um limite/reserva de recursos para o container através do parâmetro resources
no compose.
$ vim wordpress.yml
version: "3.9"
volumes:
mysql_db:
driver: trajano/nfs-volume-plugin
driver_opts:
device: master.docker-dca.example:/home/vagrant/mysql_db
nfs_opts: hard,proto=tcp,nfsvers=3,intr,nolock
networks:
wp_overlay:
services:
wordpress:
image: registry.docker-dca.example:5000/wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: caiodelgadonew@youtube
WORDPRESS_DB_NAME: wordpress
networks:
- wp_overlay
deploy:
mode: replicated
replicas: 2
restart_policy:
condition: on-failure
resources:
limits:
cpus: "1"
memory: 60M
reservations:
cpus: "0.5"
memory: 30M
db:
image: registry.docker-dca.example:5000/mysql:5.7
volumes:
- mysql_db:/var/lib/mysql
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: caiodelgadonew@youtube
MYSQL_RANDOM_ROOT_PASSWORD: '1'
networks:
- wp_overlay
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
Vamos fazer o deploy da stack:
$ docker stack deploy -c wordpress.yml wordpress-stack
$ docker stack services wordpress-stack
$ docker stack ps wordpress-stack
$ docker service inspect wordpress-stack_wordpress --pretty
Para verificar o uso de memoria/cpu do container podemos utilizar o comando docker stats
Execute em algum nó que esteja rodando o wordpress
$ docker stats
Vamos instalar na máquina master o apache benchmark para fazer um stress test no container.
$ sudo apt-get install apache2-utils -y
Execute o apache benchmark e acompanhe o uso de cpu/memoria do container nos node01 e 02
$ ab -n 10000 -c 100 http://master.docker-dca.example:8080/
Veja que o container não passa do limite de memória que especificamos. Vamos alterar o numero de replicas para 6 e executar novamente o apache benchmark
$ docker service scale wordpress-stack_wordpress=6
$ ab -n 10000 -c 100 http://master.docker-dca.example:8080/
Remova a stack
$ docker stack rm wordpress-stack
Para finalizar, vamos fazer um deploy de uma stack do jogo "dockersupermario" com o traefik fazendo proxy reverso.
Primeiramente vamos criar a rede para o traefik.
$ docker network create -d overlay proxy
Vamos criar os arquivos compose para o container do supermario.
$ vim supermario.yml
version: "3.9"
networks:
proxy:
external: true
services:
supermario:
image: registry.docker-dca.example:5000/caiodelgadonew/docker-supermario
networks:
- proxy
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
resources:
limits:
cpus: "1"
memory: 100M
reservations:
cpus: "0.5"
memory: 60M
labels:
- "traefik.enable=true"
- "traefik.http.routers.game.rule=Host(`supermario.docker-dca.example`)"
- "traefik.http.services.game.loadbalancer.server.port=8080"
Vamos criar agora a stack do Traefik
$ vim traefik.yml
version: '3.9'
networks:
proxy:
external: true
services:
traefik:
image: "registry.docker-dca.example:5000/traefik:v2.4"
command:
- --entrypoints.web.address=:80
- --providers.docker.swarmMode=true
- --providers.docker.exposedByDefault=false
- --api
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy
deploy:
mode: global
placement:
constraints:
- node.role==manager
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`dashboard.docker-dca.example`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.services.traefik.loadbalancer.server.port=80"
Efetue o deploy das stacks
$ docker stack deploy -c supermario.yml supermario
$ docker stack deploy -c traefik.yml traefik
$ docker stack ls
$ docker stack services supermario
$ docker stack services traefik
Adicione a entrada dns em seu arquivo /etc/hosts
$ vim /etc/hosts
10.20.20.100 master.docker-dca.example supermario.docker-dca.example dashboard.docker-dca.example
Podemos também escalar nosso serviço
$ docker service scale supermario_supermario=6
$ docker stack services supermario
$ docker stack ps supermario
No dashboard do traefik conseguimos ver a quantidade de nós e as informações do serviço
http://dashboard.docker-dca.example/
Após o termino, remova as stacks
$ docker stack rm traefik
$ docker stack rm supermario