Containers sugiram no mercado de tecnologia como sendo “o próximo grande passo” na escala evolutiva dos servidores de aplicação. Entretanto, por um bom tempo, fazer algo mais sério (ambientes produtivos que precisavam escalar e tal) com containers era praticamente impossível, mesmo na nuvem pública. A razão para isso era clara: não havia naquele momento qualquer tecnologia associada a eles que nos possibilitasse realizar algumas operações clássicas de servidores de aplicações convencionais, como: escalar, distribuir carga, aumentar ou diminuir quantidade de instâncias, segmentar rede, etc. Ou seja, alguma tecnologia que nos possibilitasse orquestrar de maneira eficiente ambientes distribuídos baseados em containers.
Não demorou muito para que o mercado identificasse este gap e logo as primeiras iniciativas neste sentido começaram a aparecer. Apache surgiu com seu DC/OS, Docker com seu Swarm, dentre tantas outras iniciativas. Finalmente em uma iniciativa open source o Google surgiu com aquela tecnologia que, ao que tudo indica, se consolidará como sendo a principal em termos de orquestração de containers em clusters – o Kubernetes (ou K8s).
Iniciei uma série de posts falando sobre os fundamentos do Kubernetes. Se você quiser saber um pouco mais sobre como ele funciona, basta clicar neste link. Está nos planos evoluir a série com novos posts em breve.
Docker Swarm
Docker Swarm (ou simplesmente Swarm) é a proposta da Docker para que seja possível realizar orquestração de containers sobre um clusters de Docker hosts. Funciona de maneira integrada ao docker engine a partir da versão 1.12.0 e portanto, não requer qualquer instalação adicional se você possui Docker 1.12.0 ou superior.
O que você pode estar se perguntando a esta altura é: “Fabricio, se Kubernetes é a solução que o mercado – e a Microsoft inclusive – vem utilizando, por que então falar sobre Swarm a esta altura?”. Bom, existem algumas razões para isso. As principais destaco a seguir:
- O trabalho com Swarm é mais simples. O trabalho com K8s requer uma série de conhecimentos avançados de containers e também noções sobre clusterização dos mesmos. Dessa forma, como Swarm apresenta um subset de recursos mais simples, para fins de aprendizado, é bem interessante utilize-lo como entry point neste universo.
- Docker e Swarm se fundem na mesma tecnologia. Este é outro ponto positivo para o Swarm. Existem outras abordagens para containers, entretanto acredite, 98% dos projetos que você irá trabalhar que forem relacionados a containers será utilizando Docker, o que quer dizer que para se trabalhar com containers tem que se conhecer Docker muito bem. Se você aprofundar seus conhecimentos sobre Docker poderá também, muito facilmente, evoluir com seus conhecimentos sobre Swarm, já que uma coisa está totalmente amarrada a outra.
- Existem muitos ambientes baseados em Swarm no mercado. Swarm chegou antes do K8s no mercado e claro, por conta disso, muitos projetos foram estruturados sobre Swarm. Isso significa que seus conhecimentos sobre Swarm serão sim úteis a você no mercado de trabalho. Além disso, praticamente tudo do que você aprender sobre Swarm lhe será útil no ramp up para o aprendizado de K8s.
Tecnologicamente falando, Swarm apresenta algumas características funcionais (e de design) bem interessantes. As principais são:
- Um deles já mencionamos anteriormente – a integração nativa com Docker engine. Nenhum software adicional é necessário. O único requerimento existente é a versão do Docker 1.12.0 ou superior.
- Design descentralizado em relação aos nodes do cluster. Swarm não exige que os hosts adicionados ao cluster sejam do mesmo tipo ou dimensão. Além disso, Swarm abstrai as camadas inferiores (host) do tempo de deploy. Todo ajuste necessário é realizado automaticamente em tempo de execução e dessa maneira, é possível criar um cluster a partir de um único disco, por exemplo, e ir expandindo a capacidade conforme a necessidade em tempo de execução.
- Modelo de serviço declarativo. Você pode compor uma aplicação completa (frontend, backend, banco de dados e mensageria, por exemplo) de maneira declarativa, utilizando docker-compose para isso.
- Escalabilidade. Sim, com Docker Swarm você poderá escalar (para cima e para baixo) o número de containers executando cada instância da sua aplicação de uma maneira bem simples, como veremos mais a diante.
- Gerenciamento de estado. O host identificado como “gerenciador” no cluster terá como uma de suas responsabilidades monitorar o estado dos containers em execução no cluster. Imagine por exemplo que você solicitou 5 instâncias (containers) de determinado serviço. Se em determinado momento o gerenciador identificar que 2 destas 5 instâncias por alguma razão estão em estado de “exited”, ele automaticamente, subirá duas novas instâncias deste serviço para garantir a confiabilidade do cluster de acordo com a política de escalabilidade determinada.
- Multi-networking. Ao realizar o deployment de um novo serviço você poderá criar camadas de rede sobre a rede interna do cluster. De maneira automática o node”gerenciador” atribuirá IPs internos a estes novos containers entrantes no instante em que forem iniciados.
- Resolução automática de DNS. Quando um novo serviço é enviado para o cluster Swarm, o node gerenciador automaticamente atribui um DNS único para este. Ao fazer isso, uma camada de balanceamento é gerada para que qualquer requisição para o serviço seja automaticamente resolvida e a carga automaticamente distribuída entre os nodes do cluster.
- Balanceamento de cargas. Sim, é possível expor um serviço para a internet para que requisições externas sejam distribuídas diretamente entre os containers em execução no cluster. Você pode escolher o algoritmo de distribuição de cargas desejado.
- Atualização de serviços em execução (rolling updates). É relativamente simples fazer qualquer tipo de atualização em um dado serviço em execução. Faremos isso em profusão no segundo artigo sobre este assunto.
Como funciona (5000 pés)?
Dessa forma, neste e no próximo post (que virá em breve), quero falar um pouco sobre como criar (e fazer um gerenciamento em nível mais básico) do zero um cluster baseado em Docker hosts que utiliza Swarm como orquestrador e claro, ao final, orquestrar uma aplicação simples. Mas antes, precisamos entender como Docker Swarm funciona. Para isso, considere a Figura 1.
Figura 1. Estrutura básica de funcionamento de um cluster gerenciado por Swarm
Conforme já mencionamos anteriormente, Swarm é apenas uma camada de software orquestradora de containers Docker que estão sendo distribuídos através de (Docker) hosts físicos ou virtuais. Para que o gerenciamento possa ser executado, um cluster Swarm sempre precisará contar com dois tipos diferentes de docker host: “Managers” e “Workers”. Tenha em mente também que um Docker host dentro de um cluster Swarm será formalmente conhecido como “Node”. Mantendo estas taxonomias em mente, vamos as funções de cada uma delas.
- Managers: responsáveis pela gestão do cluster como um todo. Elas monitoram todos os nodes do cluster, cuidam das rotinas internas quando um novo serviço é publicado no cluster, recebem e aplicam atualizações impostas pelo administrador do cluster, dentre tantas outras operações. Se um cluster possuir apenas uma máquina manager, ela será também a leader (node através do qual o administrador do sistema se comunica com os demais nodes do cluster). Se um cluster Swarm tiver mais que um node manager, eles serão réplicas do leader e poderão substituí-lo caso ele sofra um outage inesperado. Um node manager também poderá receber containers para serem executados em seu contexto worker.
- Workers: como o próprio nome sugere, são nodes trabalhadores. Recebem as cargas de trabalho enviadas pelo manager leader. Não possuem qualquer missão administrativa.
Conforme é possível visualizar na Figura 1, ou profissional responsável por administrar o cluster ou algum processo de deployment automatizado comunica-se com o node “manager – leader”. Qualquer requisição de operação administrativa no cluster (como criação de um novo serviço, rollout update, etc.) é realizada através da máquina manager – leader. Ela então distribui o resultado destas operações para os nodes abaixo dela.
O cenário apresentado pela Figura 1 descreve a criação de um cluster apenas para fins de desenvolvimento, isto é, apenas uma máquina manager – leader e três nodes workers. Entretanto para ambientes produtivos, Docker recomenda pelo menos três nodes managers (para fins de redundância) e três nodes workers.
As demais nomenclaturas veremos ao longo do trabalho com o cluster, neste e no próximo post.
Criando um cluster manualmente no Azure
Você sabe – Microsoft Azure hoje é, sem sombra de dúvidas, a nuvem pública que entrega a maior quantidade (e com a melhor qualidade) de serviços especializados para containers dentre todas as clouds. Se não acredita em mim, faça uma pesquisa. Não vai demorar muito pra você comprovar minha afirmação. Dessa forma, poderíamos criar nosso cluster sobre ACS, por exemplo. ACS já entrega um VM Scale Set pronto com load balancer e orquestradores (dentre eles, Swarm) configurados. Bastaria apenas iniciarmos nossas brincadeiras de orquestração. Mas hoje iremos um pouco mais adiante, iremos criar o cluster manualmente e orquestrar uma aplicação simples.
Para isso, utilizaremos VMs tradicionais. Vamos criar um ambiente produtivo mínimo seguindo as recomendações da Docker, isto é:
- Assinatura do Microsoft Azure com recursos de IaaS disponíveis
- 3 máquinas virtuais com Ubuntu 17.10 – Managers
- 3 máquinas virtuais com Ubuntu 17.10 – Workers
- Utilizaremos Docker CE latest version (na ocasião em que este post foi escrito, estava na versão 18.03)
- Criaremos uma aplicação ASP.NET Core simples
- Criaremos duas imagens distintas baseadas na mesma aplicação
- A máquina cliente (através da qual me comunicarei com o cluster Swarm e farei todo este processo administrativo) é Windows. Já tenho Docker client para Windows instalado em minha máquina. Se você não possui o Docker instalado, por favor, clique neste link para encontrar o processo de instalação adequado ao seu ambiente.
- Utilizarei o PUTTY e o PUTTY Gen para me comunicar com as máquinas servidoras e para gerar as chaves SSH. Você também pode utilizar o Linux Subsystem se estiver no Windows.
Mãos a obra?
Passo 1: Criação da máquina Linux com imagem padrão
Vamos iniciar criando as 6 máquinas virtuais do cluster, certo? Podemos fazer isso de duas maneiras: (1) criar cada máquina individualmente e fazer a mesma configuração 6 vezes; ou (2) criar uma máquina com as configurações que precisaremos (instalação e configuração do Docker) e criar as demais apenas apontando para esta imagem já criada. Não sei você, mas vou de opção 2.
- Se você não conhece o processo de criar uma máquina virtual no Azure, clique aqui para ver um tutorial detalhado.
A Figura 2 apresenta a visão da máquina (docker-image) recém criada no Azure.
Figura 2. Máquina virtual “docker-image” criada
Agora vamos ao processo de configuração dessa máquina. Precisaremos nos conectar (no meu caso utilizando SSH) a ela e instalar o Docker CE. Após isso poderemos generalizar a imagem atual para que ela sirva de base para as demais máquinas virtuais do cluster (que ainda serão criadas).
- Como estou no Windows 10, optei por me conectar através do Putty. O processo de conexão através do Putty pode ser encontrado aqui. Como mencionei anteriormente, se você preferir, pode utilizar o Linux subsystem (através do serviço nativo de SSH) para esta operação.
- O processo de instalação e configuração do Docker CE em um servidor Ubuntu está bem documentado no portal oficial da Docker. Existem basicamente 3 caminhos para se realizar esta instalação. A que eu sempre procuro fazer sempre (e também recomendo que você a faça) é aquela baseada em um repositório centralizado. O processo para que isso ocorra pode ser encontrado clicando aqui.
Ao final deste processo de instalação/configuração, tenho minha máquina “docker-image” pronta, isto é, com Docker instalado e configurado, conforme pode ser visualizado pela Figura 3.
Figura 3. Docker corretamente instalado e configurado na máquina “docker-image”
Como é possível observar, temos agora o Docker engine instalado (versão mais recente 18.03.1-ce, portanto atendendo ao requisito de versão mínima do Swarm) e fizemos uma operação simples de executar uma image Docker chamada “Hello-World”. O sucesso deste procedimento nos assegura que nossa engine Docker está funcionando perfeitamente.
Nota:
Antes de iniciar o processo de generalização, salve as informações contidas no arquivo “/etc/resolv.conf”. Ele deverá ser semelhante aquele apresentado pela Listagem 1.
Já temos nossa máquina Linux base pronta para ser generalizada. Não vou descrever o processo de generalização aqui por uma razão muito simples: ele já está muito bem documentado no portal oficial do Azure. Para visualizar este tutorial, basta seguir este link. Para realizar o procedimento descrito neste tutorial, você precisará do Azure CLI 2.0.
Se tudo correu bem com este procedimento, ao final, você deverá estar visualizando na listagem de recursos dentro de seu Resource Group (RG) um recurso semelhante aquele apresentado pela Figura 4. Trata-se de uma imagem que, na verdade, é um espelho de nossa máquina já configurada (montada anteriormente). Temos o que precisamos agora para criar nossas máquinas do cluster.
Figura 4. Nova imagem generalizada criada
Duas observações importantes a esta altura:
- Ao generalizar a imagem, o arquivo “/etc/resolv.conf” foi removido (o utilitário do CLI lhe informou isso durante o processo). Assim que a nova VM criada a partir da imagem generalizada voltar a vida, você deverá recriar este arquivo com o conteúdo apresentado pela Listagem 1.
- Se você executou o comando “sudo waagent -deprovision+user” para desprovisionar “docker-image” você precisará recriar um novo usuário + SSH key. Se você não optou por isso (isto é, executou apenas “sudo waagent -deprovision”), os dados de acesso da máquina docker-image ainda estarão válidos.
Listagem 1. Modelo do arquivo resolv.conf
Passo 2: Criando storage base + rede virtual
Antes de avançarmos com o processo de criação das VMs, precisamos cuidar dos seguintes ítens:
- Criar uma rede virtual (ou VNet) no Azure através da qual, utilizando IPs locais (internos), os nodes do cluster conseguirão enxergar uns aos outros. Para visualizar o processo de criação de uma VNet no Azure, por favor, siga este link. No meu caso, criei uma rede chamada “swarm”.
- Criar uma storage account que hospedará tanto os arquivos de logs dos nodes quanto os discos virtuais de cada um deles (nodes). Para visualizar um tutorial sobre como criar uma storage account, por favor, siga este link. No meu caso, criei um storage account chamada “swarmstg”.
Se tudo correu bem, você deverá estar visualizando estes dois novos recursos em seu resource group, assim como apresenta a Figura 5.
Figura 5. Visualizando os novos recursos criados
Passo 3: Criando as máquinas do cluster Swarm
Agora que possuímos nossa imagem base, a rede de comunicação e o storage, podemos então criar nossos nodes do cluster Swarm. No meu caso, criei primeiro os 3 managers (os quais chamei de “node-manager-1”, “node-manager-2” e “node-manager-3”) e na sequência, os workers (os quais nomeei como “node-worker-1”, “node-worker-2” e “node-worker-3”). Não se esqueça de adicionar cada uma dessas novas VMs à rede que acabamos de criar. Do mesmo modo, não se esqueça de apontar o storage recém criado como padrão para cada uma das VMs no momento da criação das mesmas.
- Para visualizar um tutorial sobre como criar uma máquina virtual a partir de uma imagem generalizada, por favor, siga este link.
Done! Como Docker já está previamente configurado nesta imagem padrão, estamos prontos então para iniciar os trabalhos com nosso cluster Swarm. A Figura 6 apresenta as máquinas do cluster já operando normalmente.
Figura 6. Criando os nodes do cluster Swarm
Configurando o cluster Swarm
Já possuímos todos os assets de que precisamos para operar nosso Swarm cluster. Agora precisamos apenas configurá-lo. Iniciaremos o trabalho com a máquina “node-manager-1”. Utilize suas informações de autenticação para se conectar a VM. Reforço uma vez mais que, no meu caso, estou usando o Putty para este fim.
Passo 1: Inicializando o cluster Swarm
Uma vez dentro da VM, vamos iniciar o trabalho de configuração ativando o cluster. Para fazer isso, utilizamos a linha de comando apresentada a seguir.
docker swarm init --advertise-addr 10.0.0.4:2377 --listen-addr 10.0.0.4:2377
O que estamos fazendo:
- Iniciando o modo “swarm” junto ao docker engine;
- Informando que a máquina manager e também leader utilizará o IP interno 10.0.0.4 como padrão de comunicação;
- Também estamos informando que a porta padrão para essa comunicação entre os hosts será a 2377 (padrão do Swarm);
- Por padrão, a máquina que inicia o cluster será nomeada “manager – leader”, como é possível visualizar ao se executar o comando “docker info”.
Se tudo correu bem, uma imagem informando que o cluster foi criado e está ativo. Logo abaixo será apresentado uma linha de comando dizendo que, se você quiser adicionar um novo worker ao cluster, você deve executar exatamente aquilo que está sendo apresentado. No meu caso o que foi apresentado foi:
docker swarm join --token \ SWMTKN-1-1ejii7xwf44spkd6sdq6s3lb8etac74d93x077tsiak8vjloe0-514ml7z2p8q46igwbel55tici 10.0.0.4:2377
Imediatamente outra mensagem é exibida:
To add a manager to this swarm, run "docker swarm join-token manager" and follow the instructions
Seguindo as instruções e executando “docker swarm join-token manager” (uma vez que temos mais dois manager para adicionar ao cluster), recebemos outro código de join.
docker swarm join --token \ SWMTKN-1-1ejii7xwf44spkd6sdq6s3lb8etac74d93x077tsiak8vjloe0-f3i98wykkgenpr3xn68eid9z6 10.0.0.4:2377
Essas linhas nos trazem dois conceitos bem importantes relacionados aos clusters Swarm. São eles:
- Quando falamos sobre alguns aspectos técnicos do Swarm no início do post, mencionei o aspecto de que ele abstrai o processo de dimensionamento do recursos, possibilitando assim a adição de novos recursos computacionais (leia-se nodes) em tempo de execução, certo? Pois bem. Clusters Swarm aceitam novos recursos através do modelo de “join” acompanhado de um token de autenticação.
- Para que isso seja possível (adicionar tanto novos nodes managers quanto workers ao cluster), o docker engine gera um “token” de acesso para cada tipo. Obviamente, estes tokens são diferentes entre nodes “manager” e “worker”. Portanto, você deve ficar atento no momento de adicionar um novo node ao cluster, isto é, utilize o token correto.
Pronto. Cluster inicializado e máquina “node-manager-1” devidamente definida como manager e leader. Vamos agora adicionar os demais elementos do cluster.
Passo 2: Adicionando novos managers ao cluster
Já sabemos o que precisamos fazer para dar o join das demais máquinas manager ao cluster, certo? Precisamos nos conectar, respectivamente, a cada uma dessas máquinas que desejamos adicionar (i. e., “node-manager-2” e “node-manager-3”) e executar o comando informado pelo docker engine quando iniciamos nosso cluster no passo 1. A única coisa que faremos diferente da instrução impressa pelo docker, é adicionar a referência ao IP local do servidor que estamos adicionando. No meu caso, “10.0.0.5 – node-manager-2” e “10.0.0.6 – node-manager-3”. Dessa maneira meu comando ficou configurado da seguinte forma:
docker swarm join \ --token SWMTKN-1-1ejii7xwf44spkd6sdq6s3lb8etac74d93x077tsiak8vjloe0-f3i98wykkgenpr3xn68eid9z6 10.0.0.4:2377 \ --advertise-addr {ip-local-manager}:2377 --listen-addr {ip-local-manager}:2377
Ao fazer isso, se tudo correu bem, você deverá visualizar a seguinte mensagem (indicando que o cluster agora conta com mais manager para ajudar no trabalho) ao findar o processo de adição de cada um dos managers.
This node joined a Swarm as a manager.
Trabalho concluído para os managers. Agora vamos para os workers.
Passo 3: Adicionando novos workers ao cluster
Já temos nossos manager configurados. Resta agora adicionar os trabalhadores. O processo é exatamente o mesmo em relação aquele descrito no passo 2 anterior, entretanto, como tanto o token para adicionar workers quanto os IPs locais de cada máquina a ser adicionada são diferentes, precisaremos alterar ligeiramente o comando que os adiciona. No meu caso, ficou da seguinte forma:
docker swarm join \ --token SWMTKN-1-1ejii7xwf44spkd6sdq6s3lb8etac74d93x077tsiak8vjloe0-514ml7z2p8q46igwbel55tici 10.0.0.4:2377 \ --advertise-addr {ip-local-worker}:2377 --listen-addr {ip-local-worker}:2377
Apenas para que você guarde as referências, meus IPs para os workers são “10.0.0.7 – node-worker-1”, “10.0.0.8 – node-worker-2” e “10.0.0.9 – node-worker-3”.
Novamente, se tudo correu bem, você deve ter visualizado uma mensagem de sucesso semelhante aquela apresentada a seguir para cada um dos servidores workers adicionados.
This node joined a Swarm as a worker.
Trabalho finalizado com os workers. Nosso cluster está pronto para ser utilizado. Resta agora verificar de maneira global se tudo foi adicionado corretamente. Para verificarmos isso, nos conectaremos de volta na máquina “node-manager-1” e executaremos o comando a seguir. Por que não podemos fazer isso de uma máquina worker? Boa pergunta! Porque apenas managers tem permissão a ter acesso a dados do cluster. Estamos nos conectando a máquina leader, mas qualquer um de nossos três managers estariam aptos a nos retornar tais informações administrativas.
Se tudo correu bem, dentre o universo de informações retornadas pela execução deste comando, você deverá estar visualizando uma porção de dados referente ao cluster Swarm, conforme apresenta a Figura 7.
Figura 7. Informações sobre o cluster recém configurado
Como é possível visualizar, nosso cluster agora possui 3 managers e 6 workers. Ops! Como assim 6 workers se adicionamos apenas 3? Outra boa pergunta. Isso ocorre porque cada manager também possui um contexto de worker e portanto, pode receber tarefas (leia-se containers) para serem executadas. Portanto, também eles (managers) são contados como workers. Bacana, não?
Pronto. Nosso cluster está pronto. Como já temos muita informação pra digerir até aqui, vou fazer um hard stop no assunto e deixar o restante do processo de deploy para o próximo artigo. Nele vou detalhar o processo de distribuição de uma aplicação .NET Core (mas poderia ser qualquer outra plataforma) sobre este cluster.
Até lá!
Pingback: Distribuindo containers com Swarm – Parte 2 – Fabrício Sanchez