À medida que nossa aplicação ganha usuários e cresce, surge naturalmente a necessidade de expandir a infraestrutura. Isso significa provisionar novos servidores capazes de suportar o aumento de demanda e garantir que tudo continue funcionando normalmente.
Para provisionar mais recursos, podemos escolher dentre várias estratégias, como provisionamento manual de instâncias, ASG da AWS ou orquestradores como Kubernetes.
Porém, cada uma dessas abordagens adiciona uma camada de complexidade ao projeto. Lidar com configurações delicadas, gerenciar clusters, orquestração de containers, estamos inevitavelmente aumentando a superfície de coisas que podem dar errado e que precisam ser monitoradas e mantidas. Para projetos menores ou aplicações que não exigem alta disponibilidade constante, todo esse overhead pode ser excessivo e desnecessário.
É justamente nesses cenários que o Docker Swarm se encaixa como uma solução equilibrada. Por ser nativo do Docker, ele te dá a orquestração de containers de um jeito muito mais simples, além de possuir um overhead baixo (favorecendo máquinas menos potentes) e uma curva de aprendizado leve.
Agora que vimos que o Docker Swarm pode fazer sentido vamos explorar como ele funciona na prática.
Nota: Ambiente com duas VPS Ubuntu 22.04 LTS, UFW e Docker instalados. Manual de instalação Docker.
1. Definindo as metas
Ao fim deste tutorial devemos ter ambas as VPS's funcionando juntas para disponibilizar os nossos serviços, o load balance deverá estar sendo gerenciado automaticamente e devemos ser capazes de acessar esse recurso utilizando sempre apenas um IP.
2. Configurando nosso Docker Compose
O Docker Compose define toda a arquitetura dos serviços: interações, volumes, variáveis de ambiente, comportamento e outras configurações. Neste tutorial, usarei como exemplo esse projeto que desenvolvi na faculdade.
Nota: Existem considerações sobre rodar postgres dentro de um container, mas isso está fora do escopo desse post.
services:
db:
image: docker.io/library/postgres:14
restart: always
container_name: treinomax-database
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- ./postgres:/var/lib/postgresql/data
ports:
- "5435:5432"
backend:
build:
context: ../backend
dockerfile: Dockerfile
container_name: treinomax-backend
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/${POSTGRES_DB}
- SPRING_DATASOURCE_USERNAME=${POSTGRES_USER}
- SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
- JWT_SECRET=${JWT_SECRET}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USERNAME=${SMTP_USERNAME}
- SMTP_PASSWORD=${SMTP_PASSWORD}
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}
- LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_BOOT_AUTOCONFIGURE=DEBUG
ports:
- "8080:8080"
depends_on:
- db
restart: always
volumes:
- ../backend/src:/app/src
- ../backend/target:/app/target
- ../backend/target_test-classes:/app/target_test-classes
- ~/.m2:/root/.
frontend:
build:g
context: ../front
dockerfile: Dockerfile
container_name: treinomax-frontend
ports:
- "4200:4200"
restart: always
volumes:
- ../front:/app
- /app/.dart_tool
- /app/build
3. Entendendo Managers e Workers
Dentro de um cluster de Docker Swarm temos 2 tipos principais de nodes, um Docker pode ser um Manager e/ou um Worker. Um node Manager tem o papel de distribuir as tarefas entre os Workers, receber comandos e por padrão também são um Worker. Já os Workers tem o papel de rodar os containers e processar as requisições.
4. Configurando o firewall
Workers e Managers tem requisitos diferentes quando se trata de firewall. A seguir temos uma tabela definindo melhor quais portas são essenciais para o funcionamento.
Nota: O ideal em um cenário de produção seria isolar essa rede com uma VPN como Wireguard
| Porta | Protocolo | Manager | Worker | Propósito |
|---|---|---|---|---|
| 2377 | TCP | ✅ Obrigatório | ❌ Não necessário | Gerenciamento do cluster |
| 7946 | TCP | ✅ Obrigatório | ✅ Obrigatório | Comunicação entre nós |
| 7946 | UDP | ✅ Obrigatório | ✅ Obrigatório | Comunicação entre nós |
| 4789 | UDP | ✅ Obrigatório | ✅ Obrigatório | Rede overlay (VXLAN) |
Então para um Manager liberaríamos essas portas da seguinte forma:
sudo ufw allow 2377/tcp && \
sudo ufw allow 7946/tcp && \
sudo ufw allow 7946/udp && \
sudo ufw allow 4789/udp && \
sudo ufw enableE para um Worker seria assim:
sudo ufw allow 7946/tcp && \
sudo ufw allow 7946/udp && \
sudo ufw allow 4789/udp && \
sudo ufw enable4. Criando nosso Manager
Em uma VPS que já está com o firewall configurado podemos criar nosso primeiro Manager com o seguinte comando:
Nota: para conseguir seu ip você pode consultar esta api no terminal da sua VPS
$ curl api.ipify.org
$ docker swarm init --advertise-addr <MEU-IP>Você terá como resposta algo como:
Swarm initialized: current node (<ID-GERADO>) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token <TOKEN-GERADO> <MEU-IP>:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.Nota: Voce sempre pode rodar
docker node lspara ver sua rede
Pronto, guarde o token gerado e estamos pronto para começarmos a cadastrar nossos Workers.
4. Conectando um Worker
Nota: Todas as instâncias devem ter o horário sincronizado antes de prosseguir para essa etapa
$ sudo docker swarm join --token <TOKEN-GERADO> <IP-DO-MANAGER>:2377
This node joined a swarm as a workeragora ao rodar docker node ls você deve ser capaz de ver ambos os nodes.