Acelerando a implantação de software - um tutorial do Docker Swarm
Publicados: 2022-03-11A menos que você viva dentro de um contêiner, provavelmente já ouviu falar sobre contêineres. A indústria tem feito uma mudança distinta da infraestrutura persistente para a efêmera, e os contêineres estão no meio desse movimento. O motivo é bem simples: embora os contêineres certamente ajudem as equipes de desenvolvimento a começar a trabalhar rapidamente, eles têm ainda mais potencial para mudar completamente a face das operações.
Mas como exatamente isso se parece? O que acontece quando você está pronto para dar o salto para executar contêineres localmente ou manualmente em alguns servidores? Em um mundo ideal, você quer apenas lançar seu aplicativo em um cluster de servidores e dizer “execute!”
Bem, felizmente, é onde estamos hoje.
Neste artigo, exploraremos o que é o Docker Swarm, juntamente com alguns dos ótimos recursos que ele oferece. Em seguida, veremos como é realmente usar o modo Swarm e implantar em um enxame, e terminaremos com alguns exemplos de como são as operações diárias com um enxame implantado. Um conhecimento básico de Docker e contêineres é definitivamente recomendado, mas você pode conferir esta excelente postagem de blog primeiro se você for novo em contêineres.
O que é Docker Swarm?
Antes de mergulharmos na criação e implantação de nosso primeiro enxame, é útil ter uma ideia do que é o Docker Swarm. O próprio Docker existe há anos, e a maioria das pessoas hoje pensa nele como um tempo de execução de contêiner. Na verdade, porém, o Docker é composto de muitas peças diferentes, todas trabalhando juntas. Por exemplo, essa parte do tempo de execução do contêiner é tratada por dois componentes menores, chamados runC e containerd. À medida que o Docker evoluiu e retribuiu à comunidade, eles descobriram que criar esses componentes menores é a melhor maneira de crescer e adicionar recursos rapidamente. Como tal, agora temos o SwarmKit e o modo Swarm, que é embutido diretamente no Docker.
O Docker Swarm é um mecanismo de orquestração de contêineres. Em alto nível, são necessários vários Docker Engines em execução em diferentes hosts e permitem que você os use juntos. O uso é simples: declare seus aplicativos como pilhas de serviços e deixe o Docker cuidar do resto. Os serviços podem ser desde instâncias de aplicativos até bancos de dados ou utilitários como Redis ou RabbitMQ. Isso deve soar familiar se você já trabalhou com docker-compose em desenvolvimento, pois é exatamente o mesmo conceito. Na verdade, uma declaração de pilha é literalmente apenas um arquivo docker-compose.yml
com a sintaxe da versão 3.1. Isso significa que você pode usar uma configuração de composição semelhante (e em muitos casos idêntica) para desenvolvimento e implantação de enxame, mas estou me adiantando um pouco aqui. O que acontece quando você tem instâncias do Docker no modo Swarm?
Não caia da balsa
Temos dois tipos de nós (servidores) no mundo Swarm: gerentes e trabalhadores. É importante ter em mente que os gerentes também são trabalhadores, eles apenas têm a responsabilidade adicional de manter as coisas funcionando. Cada enxame começa com um nó gerenciador designado como líder. A partir daí, é apenas uma questão de executar um comando para adicionar nós com segurança ao enxame.
O Swarm está altamente disponível graças à implementação do algoritmo Raft. Não vou entrar em muitos detalhes sobre o Raft porque já existe um ótimo tutorial sobre como ele funciona, mas aqui está a ideia geral: o nó líder está constantemente verificando com seus colegas nós gerenciadores e sincronizando seus estados. Para que uma mudança de estado seja “aceita”, os nós gerenciadores chegam a um consenso, o que acontece quando a maioria dos nós reconhece a mudança de estado.
A beleza disso é que os nós gerenciadores podem cair esporadicamente sem comprometer o consenso do enxame. Se uma mudança de estado chegar a um consenso, sabemos que ela existirá na maioria dos nós gerenciadores e persistirá mesmo se o líder atual falhar.
Digamos que temos três nós gerenciadores chamados A, B e C. É claro que A é nosso líder destemido. Bem, um dia um erro de rede transitório deixa A offline, deixando B e C sozinhos. Não tendo notícias de A há muito tempo (algumas centenas de milissegundos), B e C esperam um período de tempo gerado aleatoriamente antes de se candidatarem e notificarem o outro. Claro que o primeiro a se candidatar, neste caso, será eleito. Neste exemplo, B se torna o novo líder e o quórum é restaurado. Mas então, reviravolta na história: o que acontece quando A volta a ficar online? Ele vai pensar que ainda é o líder, certo? Cada eleição tem um mandato associado a ela, então A foi eleito no mandato 1. Assim que A voltar a ficar online e começar a ordenar B e C, eles gentilmente informarão que B é o líder do mandato 2 e A vai descer.
Esse mesmo processo funciona em uma escala muito maior, é claro. Você pode ter muito mais de três nós gerenciadores. Vou adicionar uma outra nota rápida embora. Cada enxame só pode receber um número específico de perdas de gerente. Um enxame de n nós gerenciadores pode perder (n-1)/2
gerenciadores sem perder quorum. Isso significa que para um enxame de três gerentes você pode perder um, para cinco você pode perder dois, etc. A razão subjacente para isso remonta à ideia sobre o consenso da maioria, e é definitivamente algo a ter em mente quando você vai para a produção.
Agendamento e reconciliação de tarefas
Até agora, estabelecemos que nossos gerentes são realmente bons em manter a sincronia. Excelente! Mas o que eles estão realmente fazendo? Lembra como eu disse que você implanta uma pilha de serviços no Swarm? Quando você declara seus serviços, você fornece ao Swarm informações importantes sobre como você realmente deseja que seus serviços sejam executados. Isso inclui parâmetros como quantas réplicas você deseja de cada serviço, como as réplicas devem ser distribuídas, se devem ser executadas apenas em determinados nós e muito mais.
Depois que um serviço é implantado, é trabalho dos gerentes garantir que todos os requisitos de implantação definidos continuem a ser atendidos. Digamos que você implante um serviço Nginx e especifique que deve haver três réplicas. Os gerentes verão que nenhum contêiner está em execução e distribuirão uniformemente os três contêineres pelos nós disponíveis.
O que é ainda mais legal, porém, é que se um contêiner falhar (ou um nó inteiro ficar offline), o Swarm criará automaticamente contêineres nos nós restantes para compensar a diferença. Se você disser que deseja três contêineres em execução, terá três contêineres em execução, enquanto o Swarm lida com todos os detalhes minuciosos. Além disso – e isso é uma grande vantagem – aumentar ou diminuir a escala é tão fácil quanto dar ao Swarm uma nova configuração de replicação.
Descoberta de serviço e balanceamento de carga
Quero destacar um detalhe importante, mas sutil, desse último exemplo: se o Swarm estiver iniciando contêineres de maneira inteligente em nós de sua escolha, não saberemos necessariamente onde esses contêineres serão executados. Isso pode parecer assustador no começo, mas na verdade é um dos recursos mais poderosos do Swarm.
Continuando o mesmo exemplo do Nginx, imagine que dissemos ao Docker que esses contêineres deveriam expor a porta 80. Se você apontar seu navegador para um nó executando esse contêiner na porta 80, verá o conteúdo desse contêiner. Não há nenhuma surpresa aí. O que pode ser surpreendente, porém, é que, se você enviar sua solicitação para um nó que não está executando esse contêiner, ainda verá o mesmo conteúdo! O que está acontecendo aqui?
Na verdade, o Swarm está usando uma rede de entrada para enviar sua solicitação a um nó disponível que executa esse contêiner e está fazendo o balanceamento de carga ao mesmo tempo. Portanto, se você fizer três solicitações para o mesmo nó, provavelmente atingirá os três contêineres diferentes. Contanto que você conheça o IP de um único nó no enxame, você pode acessar qualquer coisa em execução nele. Por outro lado, isso permite que você aponte um balanceador de carga (como um ELB) para todos os nós do swarm sem precisar se preocupar com o que está sendo executado e onde.
Ele não para em conexões externas. Os serviços executados na mesma pilha têm uma rede de sobreposição que permite que eles se comuniquem entre si. Em vez de codificar endereços IP em seu código, você pode simplesmente usar o nome do serviço como o nome do host ao qual deseja se conectar. Por exemplo, se seu aplicativo precisa se comunicar com um serviço Redis chamado “redis”, ele pode simplesmente usar “redis” como o nome do host e o Swarm encaminhará sua solicitação para o contêiner apropriado. E como isso funciona perfeitamente no desenvolvimento com o docker-compose e na produção com o Docker Swarm, é menos uma coisa para se preocupar ao implantar seu aplicativo.
Atualizações contínuas
Se você está em operações, provavelmente experimentou um ataque de pânico quando uma atualização de produção deu terrivelmente errado. Pode ser uma atualização de código ruim ou até mesmo um erro de configuração, mas de repente a produção caiu! As probabilidades são de que o chefe não vai se importar de qualquer maneira. Eles só vão saber que a culpa é sua. Bem, não se preocupe, Swarm está de costas para isso também.
Ao atualizar um serviço, você pode definir quantos contêineres devem ser atualizados por vez e o que deve acontecer se os novos contêineres começarem a falhar. Após um determinado limite, o Swarm pode interromper a atualização ou (a partir do Docker 17.04) reverter os contêineres para a imagem e as configurações anteriores. Não se preocupe em ter que trazer um café para seu chefe amanhã de manhã.
Segurança
Por último, mas nem de longe menos importante, o Docker Swarm vem com ótimos recursos de segurança prontos para uso. Quando um nó se junta ao enxame, ele usa um token que não apenas verifica a si mesmo, mas também verifica se está se juntando ao enxame que você acha que está. A partir desse momento, toda a comunicação entre os nós ocorre usando criptografia TLS mútua. Essa criptografia é toda provisionada e gerenciada automaticamente pelo Swarm, para que você nunca precise se preocupar com a renovação de certificados e outros problemas típicos de segurança. E, claro, se você quiser forçar uma rotação de chave, há um comando para isso.
A versão mais recente do Docker Swarm também vem com gerenciamento de segredos integrado. Isso permite implantar segredos com segurança, como chaves e senhas, para os serviços que precisam deles e apenas para os serviços que precisam deles. Quando você fornece um serviço com um segredo, os contêineres desse serviço terão um arquivo especial montado em seu sistema de arquivos que inclui o valor do segredo. Escusado será dizer, mas isso é muito mais seguro do que usar variáveis de ambiente, que eram a abordagem tradicional.
Mergulhando no enxame
Se você é como eu, está ansioso para entrar e aproveitar todos esses recursos! Então, sem mais delongas, vamos mergulhar!
Aplicativo de exemplo do Docker Swarm
Eu criei um aplicativo Flask muito rudimentar para demonstrar o poder e a facilidade de usar o Docker Swarm. O aplicativo da web simplesmente exibe uma página que informa qual contêiner atendeu à sua solicitação, quantas solicitações totais foram atendidas e qual é a senha “secreta” do banco de dados.
Ele é dividido em três serviços: o aplicativo Flask real, um proxy reverso Nginx e um keystore Redis. Em cada solicitação, o aplicativo incrementa a chave num_requests
no Redis, portanto, independentemente de qual instância do Flask você está acessando, você verá o número correto de solicitações refletidas.
Todo o código-fonte está disponível no GitHub se você quiser “verificar” o que está acontecendo.
Jogue com o Docker!
Sinta-se à vontade para usar seus próprios servidores ao longo deste tutorial, mas eu recomendo usar play-with-docker.com se você quiser apenas entrar. É um site executado por alguns desenvolvedores do Docker que permite ativar vários nós que têm o Docker pré-instalado. Eles serão desligados após quatro horas, mas isso é suficiente para este exemplo!
Criando um Enxame
Pronto, vamos lá! Vá em frente e crie três instâncias no PWD (play-with-docker) ou ative três servidores em seu serviço VPS (virtual private server) favorito e instale o mecanismo Docker em todos eles. Lembre-se de que você sempre pode criar uma imagem e reutilizá-la ao adicionar nós no futuro. Não há diferença em termos de software entre um nó do gerenciador e um nó do trabalhador, portanto, você não precisa manter duas imagens diferentes.

Ainda girando? Não se preocupe, eu vou esperar. Ok, agora vamos criar nosso primeiro nó de gerente e líder. Em sua primeira instância, inicialize um enxame:
docker swarm init --advertise-addr <node ip here>
Substitua <node_ip_here>
pelo endereço IP do seu nó. No PWD, o endereço IP é exibido na parte superior e, se você estiver usando seu próprio VPS, sinta-se à vontade para usar o endereço IP privado do seu servidor, desde que seja acessível a partir de outros nós em sua rede.
Agora você tem um enxame! É um enxame muito chato, porém, já que tem apenas um nó. Vamos em frente e adicionar os outros nós. Você notará que quando você executou o init
, ele exibiu uma longa mensagem explicando como usar o token de junção. Não vamos usar esse porque faria os outros nós funcionarem, e queremos que eles sejam gerentes. Vamos obter o token de junção para um gerente executando isso no primeiro nó:
docker swarm join-token manager
Copie o comando resultante e execute-o no segundo e terceiro nós. Eis um enxame de três nós! Vamos verificar se todos os nossos nós realmente existem. O comando docker node ls
listará todos os nodes em nosso swarm. Você deve ver algo assim:
$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable
Observe como nosso primeiro nó tem um asterisco ao lado do ID. Isso está simplesmente nos dizendo que é o nó ao qual estamos conectados no momento. Também podemos ver que este nó é atualmente o Líder, e os outros nós são alcançáveis se algo acontecer com ele.
Reserve um momento para apreciar como isso foi fácil e vamos implantar nosso primeiro aplicativo!
Enviá-lo!
Assim, a equipe de desenvolvimento de negócios prometeu a um cliente que seu novo aplicativo seria implantado e estaria pronto em uma hora! Típico, eu sei. Mas não tenha medo, não precisaremos de muito tempo, já que foi construído usando o Docker! Os desenvolvedores tiveram a gentileza de nos emprestar seu arquivo docker-compose
:
version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true
Vamos detalhar em um momento, mas ainda não há tempo para isso. Vamos implantá-lo! Vá em frente e crie um arquivo em seu primeiro nó chamado docker-compose.yml
e preencha-o com a configuração acima. Você pode fazer isso facilmente com echo "<pasted contents here>" > docker-compose.yml
.
Normalmente, poderíamos apenas implantar isso, mas nossa configuração menciona que usamos um segredo chamado db_password
, então vamos criar rapidamente esse segredo:
echo "supersecretpassword" | docker secret create db_password -
Excelente! Agora tudo o que precisamos fazer é dizer ao Docker para usar nossa configuração:
docker stack deploy -c docker-compose.yml demo
Ao executar esse comando, você verá o Docker criando os três serviços que definimos: web
, nginx
e redis
. No entanto, como nomeamos nossa pilha de demonstração, nossos serviços são, na verdade, denominados demo_web
, demo_nginx
e demo_redis
. Podemos observar nossos serviços em execução executando o comando docker service ls
, que deve mostrar algo assim:
$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest
Voilá! O Docker baixou nossas imagens para os nós apropriados e criou contêineres para nossos serviços. Se suas réplicas ainda não estiverem com capacidade total, aguarde um momento e verifique novamente. O Docker provavelmente ainda está baixando as imagens.
Ver é crer
Não acredite na minha palavra (ou na palavra do Docker) para isso. Vamos tentar conectar ao nosso aplicativo. Nossa configuração de serviço diz ao Docker para expor o NGINX na porta 8000. Se você estiver no PWD, deve haver um link azul na parte superior da página que diz “8000”. O PWD detectou automaticamente que temos um serviço em execução nessa porta! Clique nele e ele o encaminhará para o nó selecionado na porta 8000. Se você rodou seus próprios servidores, basta navegar até um dos IPs de seus servidores na porta 8000.
Você será saudado com uma tela lindamente estilizada, fornecendo algumas informações básicas:
Anote qual contêiner atendeu à sua solicitação e atualize a página. As chances são de que mudou. Mas por que? Bem, dissemos ao Docker para criar duas réplicas do nosso aplicativo Flask e ele está distribuindo solicitações para ambas as instâncias. Você acabou de acertar o outro contêiner pela segunda vez. Você também notará que o número de solicitações aumentou porque os dois contêineres do Flask estão se comunicando com a única instância do Redis que especificamos.
Sinta-se à vontade para tentar acessar a porta 8000 de qualquer nó. Você ainda será roteado corretamente para o aplicativo.
Desmistificando a Magia
Neste ponto, tudo funciona e, com sorte, você achou o processo indolor! Vamos dar uma olhada nesse arquivo docker-compose.yml
e ver o que realmente dissemos ao Docker. Em um nível alto, vemos que definimos três serviços: web
, nginx
e redis
. Assim como um arquivo de composição normal, fornecemos ao Docker uma imagem a ser usada para cada serviço, bem como um comando a ser executado. No caso de nginx
, também especificamos que a porta 8000 no host deve ser mapeada para a porta 80 no contêiner. Tudo isso é sintaxe de composição padrão até agora.
O que há de novo aqui são as chaves de implantação e segredos. Essas chaves são ignoradas pelo docker-compose
, portanto, não afetarão seu ambiente de desenvolvimento, mas são usadas pelo docker stack
. Vamos dar uma olhada no serviço da web. Bastante simples, estamos dizendo ao Docker que gostaríamos de executar duas réplicas do nosso aplicativo Flask. Também estamos informando ao Docker que o serviço da Web requer o segredo db_password
. Isso é o que garante que o contêiner terá um arquivo chamado /run/secrets/db_password
contendo o valor do segredo.
Descendo para o Nginx, podemos ver que o modo de implantação está definido como global
. O valor padrão (que é usado implicitamente na web) é replicated
, o que significa que especificaremos quantas réplicas queremos. Quando especificamos global
, ele informa ao Docker que cada nó do swarm deve executar exatamente uma instância do serviço. Execute docker service ls
novamente, você notará que o nginx
possui três réplicas, uma para cada nó em nosso swarm.
Por fim, instruímos o Docker a executar uma única instância do Redis em algum lugar do enxame. Não importa para onde, pois nossos contêineres da Web são roteados automaticamente para ele quando solicitam o host Redis.
Usando o Enxame no Dia a Dia
Parabéns por implantar seu primeiro aplicativo em um Docker Swarm! Vamos analisar alguns comandos comuns que você usará.
Inspecionando seu enxame
Precisa verificar seus serviços? Tente docker service ls
e docker service ps <service name>
. O primeiro mostra uma visão geral de alto nível de cada serviço e o último fornece informações sobre cada contêiner em execução para o serviço especificado. Esse é particularmente útil quando você deseja ver quais nós estão executando um serviço.
Atualizações contínuas
E quando você estiver pronto para atualizar um aplicativo? Bem, o legal da docker stack deploy
é que ela também aplicará atualizações a uma pilha existente. Digamos que você tenha enviado uma nova imagem do Docker para seu repositório. Na verdade, você pode simplesmente executar o mesmo comando de implantação que usou na primeira vez e seu enxame fará o download e implantará a nova imagem.
É claro que nem sempre você deseja atualizar todos os serviços em sua pilha. Também podemos realizar atualizações no nível de serviço. Vamos supor que eu atualizei recentemente a imagem para o meu serviço web. Posso emitir este comando para atualizar todos os meus contêineres da web:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web
Um benefício adicional para esse comando é que ele aplicará uma atualização sem interrupção se você especificou que deveria em sua configuração original. E mesmo que você não tenha, você pode passar sinalizadores para atualizar que o instruirão a fazer uma atualização contínua da seguinte forma:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web
Isso atualizará um contêiner por vez, aguardando 30 segundos entre as atualizações.
Escalando serviços para cima ou para baixo
Ter dois web containers é ótimo, mas sabe o que é melhor? Tendo dez! Escalar serviços para cima e para baixo em um enxame é tão fácil quanto:
docker service scale demo_web=10
Execute esse comando e verifique a saída do docker service ps demo_web
. Você verá que agora temos dez contêineres, e oito deles foram iniciados há pouco. Se estiver interessado, você também pode voltar ao aplicativo da Web e atualizar a página algumas vezes para ver que agora está obtendo mais do que os dois IDs de contêiner originais.
Removendo pilhas e serviços
Suas pilhas e serviços são implantados e dimensionados, incrível! Mas agora você quer levá-los offline. Isso pode ser feito com o respectivo comando rm
. Para remover nossa pilha de demonstração, execute o comando:
docker stack rm demo
Ou, se preferir apenas remover um único serviço, basta usar:
docker service rm demo_web
Nós de drenagem
Lembra quando executamos o docker node ls
anteriormente para verificar os nós em nosso enxame? Ele forneceu várias informações sobre cada nó, incluindo sua Disponibilidade . Por padrão, os nós são ativos , o que significa que eles são um jogo justo para executar contêineres. No entanto, há momentos em que pode ser necessário colocar um nó temporariamente offline para realizar a manutenção. Claro, você poderia simplesmente desligá-lo e o enxame se recuperaria, mas é bom avisar Moby (a baleia Docker) um pouco.
É aqui que entra a drenagem de nós. Quando você marca um nó como Drain , o Docker Swarm delega todos os contêineres executados nele para outros nós e não iniciará nenhum contêiner no nó até que você altere sua disponibilidade de volta para Active .
Digamos que queremos drenar node1
. Podemos executar:
docker node update --availability drain node1
Fácil! Quando estiver pronto para voltar ao trabalho:
docker node update --availability active node1
Empacotando
Como vimos, o Docker juntamente com o modo Swarm nos permite implantar aplicativos com mais eficiência e confiabilidade do que nunca. Vale a pena mencionar que o Docker Swarm não é de forma alguma o único mecanismo de orquestração de contêineres disponível. Na verdade, é um dos mais jovens. O Kubernetes existe há mais tempo e é definitivamente usado em mais aplicativos de produção. Dito isto, o Swarm é o desenvolvido oficialmente pelo Docker, e eles estão trabalhando para adicionar ainda mais recursos todos os dias. Independentemente de qual você escolher usar, mantenha o recipiente!