Saudações.
Criei esse tutorial para ser um guia prático de uso do Traefik em ambientes diversos, desde Docker em modo Standalone até o modo Swarm avançado.
Vou compartilhar com vocês todos os meus ajustes finos de Traefik!
Links:
- Site do Traefik: https://traefik.io/
- Projeto no Github: https://github.com/traefik/traefik
- Documentação: https://docs.traefik.io
Pré-requisitos:
- Servidor, VPS ou VM com Linux e com Internet;
- Docker;
É necessário configurar o email do administrador no provedor de certificados LetsEncrypt, vou usar “voce@dominio.com” nos exemplos e você deve alterar para um email funcional.
# Configurar email do administrador
[ -s /etc/email ] || echo "voce@dominio.com" > /etc/email;
# Ler email do administrador para a variavel de ambiente:
export EMAIL=$(head -1 /etc/email);
echo;
echo "Email do administrador: $EMAIL";
echo;
1 – Antes de HTTP vem o DNS
O DNS é a configuração que precede o uso do Traefik.
No tutorial usarei o domínio fictício dominio.com como exemplo, troque-o pelo seu domínio real e garanta a configuração correta antes de usar.
No DNS o nome completo do servidor – FQDN (Fully Qualified Domain Name) – é apontado para o IP do servidor. “www” é o nome de host, “dominio.com” é o domínio, www.dominio.com é o FQDN (nome completo).
Existem 3 formas de configurar, da mais simples para a mais profissional:
- FQDN para IPv4 – Registro A: O tipo mais comum, o servidor do Traefik tem seu IPv4 vinculado a um nome como www.servidor.com.br e servidor.com.br (com e sem www devem ter os mesmos valores).
- FQDN para IPv4 e IPv6 – Registros A AAAA (quad A): O serviço poderá abrir em IPv4 e IPv6, a escolha do cliente.O tipo mais comum, o servidor do Traefik tem seu IPv4 vinculado a um nome como www.servidor.com.br e servidor.com.br (com e sem www devem ter os mesmos valores), valor de exemplo: 45.255.128.2;
- CAA: Configuração rigorosa de quais provedores na Internet podem emitir certificados assinados na cadeia global de RootCA, opcional, mas recomendo estudo;
- DNS HTTPS: O DNS pode responder em um único registro os IPs e as capacidades de protocolo HTTP (como HTTP/3+QUIC), o que evita a latência de requisições adicionais no upgrade da conexão, de protocolo legado HTTP/1 e HTTP/2 para H3.
Exemplo de arquivo de zona ideal (mas não incluí DNS-SEC, não faz diferença aqui):
; Dominio que representa o '@' na zona atual
; nomes que nao terminam com "." recebem esse valor como sufixo
$ORIGIN meudominio.com.br.
; TTL padrao: de 1 hora,
; ao trocar de valor no registro,
; o valor antigo pode operar por mais 1h
$TTL 3600
; Start of Authority (SOA)
; Necessario para que o servidor tenha "Autoridade sobre o dominio"
@ IN SOA ns1.meudominio.com.br. admin.meudominio.com.br. (
2026061301 ; Serial number (YYYYMMDDNN format)
28800 ; Refresh period (8 hours)
7200 ; Retry interval (2 hours)
1209600 ; Expire time (2 weeks)
3600 ; Minimum/Negative caching TTL (1 hour)
) ; fim do valor do SOA
; Name Server (NS) records
; usa TTL padrao $TTL
@ IN NS ns1
@ IN NS ns2
; IPs dos NS (Glue)
ns1 IN A 45.255.130.131
ns2 IN A 45.255.130.132
; Mail Exchanger (MX) - Servidores de e-mail
; TTL personalizado para 2h (7200 segundos) no MX e 30min no IP
@ 7200 IN MX 10 mail
mail 1800 IN A 45.255.131.2
mail 1800 IN AAAA 2804:caf9:f131::2
@ IN TXT "v=spf1 mx -all"
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:admin@meudominio.com.br"
; Site principal com ipv4 e ipv6
@ IN A 45.255.128.2
@ IN AAAA 2804:caf9:a128::2
; Site com www. nos mesmo valores do dominio
www IN A 45.255.128.2
www IN AAAA 2804:caf9:a128::2
; HTTPS RR (RFC 9460): anuncia h2/h3 + dicas de IP
@ IN HTTPS 1 . alpn="h2,h3" ipv4hint="45.255.128.2" ipv6hint="2804:caf9:a128::2"
www IN HTTPS 1 . alpn="h2,h3" ipv4hint="45.255.128.2" ipv6hint="2804:caf9:a128::2"
; Alias (CNAME)
; web e' apelido que usa os mesmos valores do WWW
; herdam valores HTTPS, economiza repeticoes
web IN CNAME www
api IN CNAME www
; CAA - Certification Authority Authorization
; Obs: incluir os provedores que voce deseja
; usar como CloudFlare, GoDaddy, Comodo, Hostinger, Digicert, Sectigo, ...
; Restring quais provedores podem gerar certificados para o dominio
; só a Let's Encrypt pode emitir certificados
@ IN CAA 0 issue "letsencrypt.org"
@ IN CAA 0 issuewild "letsencrypt.org"
@ IN CAA 0 iodef "mailto:admin@meudominio.com.br"
Com o DNS perfeito você ja pode receber os visitantes no seu servidor WEB.
2 – Apresentando o Traefik
O Traefik é um software escrito em Go que atua como servidor nos seguintes protocolos:
- HTTP/1.1 – Roteamento web padrão;
- HTTP/2 – Multiplexing, server push, etc;
- H2C: HTTP/2 cleartext suportado a partir da v3.2;
- HTTP/3 – Baseado em QUIC, menor latência;
- WebSockets – Upgrade de conexões HTTP para WS/WSS;
- gRPC – Proxy nativo para chamadas gRPC;
- HTTPs e TLS/SSL – Realiza o handshake TLS/SSL e negocia certificados de maneira dinâmica em tempo de execução, sem precisar reiniciar o sistema, possui suporte a obtenção automática de certificados via ACME/Lets Encrypt;
- TLS Termination e TLS Offload – Realiza a negociação TLS completa e entrega a conexão TCP pura para o software no backend;
- TLSRoute/GRPCRoute: Tem CRDs específicos pra gRPC e TLS puro;
- TCP – Roteamento L4 puro, útil para bancos, MQTT, etc;
- UDP – Suporte L4 para DNS, syslog, VoIP, etc.
Sua principal função não é servir diretamente nesses protocolos mas atuar como proxy, tunnel ou midleware entre os usuários na Internet e as aplicações dentro de uma intranet (redes Docker no nosso caso).
Com isso ele é capaz de prover os seguintes recursos:
- Balance: Distribuir conexões de entrada em vários servidores internos;
- Fail-Over: Alternar para diferentes servidores quando um deles cair, pela ordem de preferência ou por balanceamento dinâmico;
- Retry e Recovery: Repetir tentativas de acesso a servidor interno sem impactar a conexão estabelecida com usuário na Internet;
- Rate Limit: Impor limite de requisições por segundo;
- Basic Auth: Impor autenticação simples;
- IP ACL: Discriminar conexões por IP de origem para bloquear ou permitir;
- Redirecionamento L7: Guiar conexão até servidor específico por características no payload;
- Compressão: Servir conteúdo comprimido;
- Circuit breaker: Reação a eventos que podem interromper o serviço;
A configuração é organizada em blocos simples e organizados:
- Entrypoints: Cada porta aberta é nomeada como um entrypoint a qual serviços podem se associar para receber conexões;
- Providers: São plugins que o Traefik usa para obter configuração em tempo de execução, como arquivos do usuários, API do Docker (Standalone e Swarm);
- Logs: Para onde o fluxo de logs será canalizado (arquivos, syslog, plugin) para fins de observabilidade e auditoria.
Sintaxe de configuração:
| Tipo | Formato | Quando usa |
|---|---|---|
| Estática (argumento) | YAML, TOML, CLI, ENV | Start do Traefik: portas, providers, log |
| Dinâmica via arquivo | YAML, TOML | Rotas, middlewares, services em arquivo |
| Dinâmica via provider | Labels, CRDs, KV, Tags | Docker, K8s, Consul, etc |
2.1 – Entrypoints – IPs e portas de entrada
Problema: Você só pode abrir a porta 80 e 443 uma única vez por IP, é por meio do LISTEN na camada 4 que a porta é mapeada ao aplicativo.

Se você precisa rodar vários softwares que servem via HTTP, a primeira solução seria colocar um IP exclusivo para cada aplicação, o que é inviável economicamente.

Ao usar o proxy-reverso HTTP, ele abre a porta 80, 443 (ou qualquer outra) em todos os IP do servidor (IPv4, IPv6, loopbacks, VPNs e túneis) e ao receber a requisição vinda da Internet, ele encaminha diretamente para o software final baseado nas regras de roteamento.

Fluxo de conexões de entrada vindas da Internet até o Traefik:

O Linux deve enviar as conexões para o IP e porta do Traefik. Isso não significa que o Traefik irá receber e processar tais conexões. É necessário configurar os entrypoints (pontos de entrada) dando a eles nomes e propriedades (com ou sem TLS por exemplo), diagrama de fluxo até o entrypoint:

Os pacotes das conexões são redirecionadas pelo firewall do Linux para o IP do container do Traefik, que por sua vez deve explicitamente possuir um LISTEN configurado no entrypoints da configuração principal (SYN -> SYN/ACK -> ACK). Portas não declaradas são recusadas pelo kernel Linux dentro do namespace do container (SYN -> RST).
2.2 – Config Providers e Routes
O Traefik possui dois níveis de configurações:
- Estática: É a configuração principal e fixa do serviço, para alterar é necessário reiniciar o traefik, no nosso caso, o container dele (traefik-app), inclui:
- Logs: Registro de atividades e erros;
- API: Estatísticas e Dashboard;
- Ping: Serviço de status e keep-alive para healph-check;
- Entrypoints: Portas abertas (LISTEN) para recebimento de pacotes e conexões;
- Providers: Sub-sistema de coleta de configurações dinâmicas;
- Cert Resolvers: Sub-sistema de obtenção de chaves e certificados x.509;
- Dinâmica: São as configurações fornecidas em tempo de execução, sofrem alterações de acordo com o ambiente, essas configurações vem de fora do traefik, você pode não usar nenhum ou usar quantos achar necessário:
- Arquivos (YAML, TOML): Arquivos gerados por outros programas em tempo real, se o arquivo é alterado o Traefik faz a sincronia, remove as linhas que sumiram ou mudaram e sobe as novas linhas;
- Docker (SOCKET, TCP): O Traefik se conecta via TCP ou Unix Socket do Docker que está rodando no HOST (/var/run/docker.sock) e acompanhar os eventos em tempo real:
- Container caiu: Retira as configurações aprendidas via labels;
- Container subiu: Faz a leitura dos labels para obter configs;
- Servidores: Permitem puxar configurações de sistemas paralelos ou remotos, opções:
consul;etcd;redis;boltdb;
As rotas (routes) que são os principais mecanismos que orienta o Traefik a entregar conexões no destino final são aprendidas dinamicamente.

2.3 – Docker como Provider
O Docker é o melhor provedor de configurações para o Traefik:
- A criação de um container dispara eventos no docker-engine (processo dockerd) que se propaga pela API (TCP, HTTP e Socket), onde o Traefik está conectado;
- O Traefik analisa o container:
- Se o container possui labels “traefik.*” em suas propriedades, se “traefik.enable=true” for encontrado o container é elegível para importação de configurações;
- Se o container está rodando e pronto para receber conexões:
- Se houver health-check, aguardar entrar em modo “healthy“;
- Sem health-check basta que ele esteja no modo “running“;
- Os labels serão analisados e se não houver erros ou colisões a configuração é importada para os provides e as rotas serão instaladas no fluxo principal;
- Se houver alguma rota ela é entregue;
- A ausência de rota, geral ou específica, resulta em erro 404 (not found).
Ao final, teremos:

Na imagem acima, temos:
- O container do RabbitMQ foi criado com os labels reconhecidos, declarando mq.dominio.com como nome de host e 9000 como porta do container;
- Docker comunicou o evento que foi lido pelo Traefik que as transformou em rota;
- Qualquer requisição HTTP para “https://mq.dominio.com” que chegar na porta HTTPs TCP/443 do Traefik será entregue ao container do RabbitMQ na porta tcp/9000 (sem HTTPs).
2.4 – Conexões e IP de origem
Agora as coisas ficam um pouco complicadas. Quem se conecta ao container final é o Traefik, não o cliente original na Internet.
Observe o fluxo direto do cliente na Interent até o container do serviço RabbitMQ:

O cliente com IP de origem “70.14.28.56” se conectou ao IP do servidor “45.255.128.2” que terminou no entrypoint websecure, ele por sua vez fez uma segunda conexão usando como IP de origem o endereço do Traefik na rede Docker, “10.249.255.254” nesse caso.
Como o container RabbitMQ saberá o IP real do cliente que originou a conexão?
Para isso o protocolo HTTP prevê a adição dos cabeçalhos:
: Contém a cadeia de IPs pela qual a requisição passou, o IP do cliente original é sempre o primeiro IP à esquerda (o primeiro), exemplos:X-Forwarded-For, cliente original em:X-Forwarded-For70.14.28.5670.14.28.56, cliente original em 70.14.28.56, primeiro IP da lista;:X-Forwarded-For70.14.28.56, 45.255.128.2
: Contem a versão simples contendo exclusivamente o IP original do cliente, nesse nosso exemplo:X-Real-IpX-Real-Ip: 70.14.28.56
: Protocolo usado pelo cliente para chegar ao Traefik, exemplo:X-Forwarded-Proto: httpsX-Forwarded-Proto
: O hostname original requisitado pelo cliente, exemplo:X-Forwarded-Host: mq.dominio.comX-Forwarded-Host
: A porta do Entrypoint que recebeu a conexão, exemplo:X-Forwarded-Port: 443X-Forwarded-Host
: Formato moderno completo, nem sempre presente, informa tudo num único cabeçalho, raramente presente.Forwarded
Assim, o software que recebe a conexão deve obter o IP de origem do cliente na seguinte ordem:
- IP em
, se ausente,X-Real-Ip - Primeiro IP em
, se ausente,X-Forwarded-For - IP de origem da conexão TCP.
Para identificar o VirtualHost:
- Nome em
, se ausente,X-Forwarded-Host - Nome em
.Host
2.5 – Porta de origem
Agora temos um problema: A porta TCP ou UDP de origem do cliente na Internet não é repassada para o serviço final, mas é retida pelo log de acesso do Traefik (accesslog).
No Brasil, essa porta efêmera é especialmente importante para a Lei de Marco Civil da Internet para ajudar a identificar um usuário em um provedor que faz uso de CGNAT , onde um mesmo IP é usado em vários usuários, distinguindo-os pela “porta virtual“, que nesse caso é a porta de origem alocada pelo CGNAT e registrado no log do provedor.

2.6 – TLS Offload sem IP e porta de origem
Agora vem a parte ruim: Conexões TCP+TLS com offload no traefik não possuem essa capacidade de repassar a identificação do cliente original.
Exemplo:
- O Redis usa o protocolo RESP (Redis Serialization Protocol), baseado em TCP;
- Ao utilizar Redis + TLS, é necessário usar um certificado, o Traefik pode usar um certificado obtido do Acme/LetsEntrypt para isso;
- A conexão TCP+TLS será recebida da Internet e o Traefik fará a comunicação criptografada com o cliente;
- O Traefik se conectará ao Redis usando uma conexão TCP simples, com IP de origem no próprio Traefik;
- O Redis jamais saberá o IP real do cliente, para ele o Traefik é o cliente.

Para esses casos é vital que o Traefik seja configurado com ACLs para controlar o acesso e logs para registrar os acessos.
O uso de VPNs que preservam o IP do cliente até o Redis seria mais adequado.
2.7 – Rede única do Traefik
O Traefik faz o aprendizado de containers em apenas 1 rede Docker. Em ambientes simples você nem percebem que existe essa limitação.

Se o container Traefik for ligado a várias redes sem a configuração explicita da rede em que ele deve atuar, ele pode alternar para uma rede em detrimento de outra e os serviços podem ficar indisponíveis.

Para evitar isso, manifeste explicitamente nos argumentos qual a rede (docker network) estão os containers dos serviços (providers.docker.network).
Chegamos à conclusão de que conectar o Traefik em todas as redes das stacks não é a solução, a solução é cada stack se conectar à rede do Traefik.

É comum você encontrar por ai redes como “network_public“, “traefik“, “network_cloud“, “proxy“.
2.8 – Provedor de configuração próprio
O fato do Traefik não participar de várias redes pode ser contornado com o “file provider” construído por software próprio.
Nesse cenário você desenvolve um gerador de configurações do Traefik sempre que seu ambiente sofre mutação, caberá a você preencher as rotas e entregar ao Traefik para que ele obedeça cegamente.

Você não está limitado apenas à containers como endpoints das rotas, qualquer IP ou URL pode ser um alvo da rota.
2.9 – Provedores de certificados
O “Cert Resolver”, normalmente usando protocolo ACME (Automated Certificate Management Environment), são sub-sistemas responsáveis por obter certificados assinados na cadeia de PKI global (reconhecidos até as Root CA dos navegadores).
Simplificando, quando você coloca o label “mq.dominio.com” em um container, o Traefik realiza os seguintes procedimentos:
- Cria uma chave privada sua respectiva chave pública para esse FQDN;
- Emite um certificado auto-assinado temporariamente;
- Se você abrir o endereço no navegador nesse momento o HTTPs estará disponível mas exibirá alerta de não confiável;
- Entra em contato com o provedor ACME, por padrão Let’s Encrypt (LE) solicitando o inicio do desafio para provar a propriedade do FQDN e do site:
- O LE ordena o armazenamento de arquivos de desafio na porta HTTP 80 (entrypoint web);
- O LE acessa o arquivo de desafio (exemplo: http://mq.dominio.com/…/arquivo);
- Se o acesso funcionar, isso significa que o solicitente possui do DNS e o HTTP para esse site funcionando;
- O LE fornecerá o certificado (chave pública assinada);
- O Traefik alterna do certificado auto-assinado para o certificado válido;
- O site abrirá normalmente sem alerta (obs: o cache do navegador pode não atualizar o certificado, reinicie-o para conferir).
2 – Usando Traefik no Docker Standalone
Vou apresentar nesse capitulo o uso do Traefik em um ambiente com Docker onde não há swarm instalado. Esse caso se aplica a ambientes simples com aplicações básicas do dia-a-dia e envolve usuários leigos que não precisam se embolar com muita tecnologia avançada sem necessidade (swarm, overlay vxlan, ingress e egress, etc).
2.1 – Rede Docker – Bridge
Vamos criar uma rede onde o traefik poderá enxergar os containers vizinhos pelo DNS interno do Docker.
Escolha a rede com ou sem IPv6, recomendo configurar IPv6 no HOST e usar o modelo com IPv6, caso você tenha apenas IPv4 use o primeiro exemplo.
Por padrão o MTU de 1500 é funcional para todos os casos, se você tem VPNs e túneis antes do servidor recomendo reduzir para o PTMU funcional, nunca inferior a 1330.
2.2 – Rede somente IPv4
Essa é a rede mais conservadora, para ambientes onde o usuário não domina IPv6 ou o IPv6 não foi disponibilizado para a VPS/VM ou provedor.
# Rede de containers
docker network create network_public \
-d bridge \
-o com.docker.network.bridge.name=br-net-public \
-o com.docker.network.driver.mtu=1500 \
-o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \
--subnet 10.249.0.0/16 \
--gateway 10.249.255.254;
2.3 – Rede dual-stack IPv4+IPv6 (recomendado)
Essa é a rede perfeita! IPv4 e IPv6 juntos:
docker network create \
-d bridge \
\
-o "com.docker.network.bridge.name"="br-net-public" \
-o "com.docker.network.bridge.enable_icc"="true" \
-o "com.docker.network.driver.mtu"="65495" \
\
--subnet 10.249.0.0/16 --gateway 10.249.255.254 \
--ipv6 \
--subnet=2001:db8:10:249::/64 \
--gateway=2001:db8:10:249::ffff \
\
network_public;
2.4 – Container Traefik básico
Antes de avançar para configurações completas, apresento abaixo a configuração mais simples e conservadora do Traefik, para ambientes apenas HTTP sem muitos mistérios e pronto para trabalho.
Criando container simples com nome traefik-app:
# Obter email do administrador
EMAIL=$(head -1 /etc/email);
# Nome oficial do servidor
SERVER=$(hostname -f);
# Pasta par reter dados no volume
mkdir -p /storage/traefik-app/letsencrypt; # certificados em acme.json
mkdir -p /storage/traefik-app/logs; # logs de acessos e erros
mkdir -p /storage/traefik-app/config; # configs manuais
mkdir -p /storage/traefik-app/tmp; # core-dump debug
# Obter imagem atualizada
docker image pull traefik:latest;
# Renovar/criar/rodar
docker container rm -f traefik-app 2>/dev/null;
docker container run \
--name traefik-app \
--hostname traefik-app.intranet.br \
--restart unless-stopped \
\
--detach \
\
--network network_public \
\
--publish 80:80 \
--publish 443:443 \
\
-v /var/run/docker.sock:/docker.sock:ro \
\
-v /storage/traefik-app/letsencrypt:/letsencrypt \
-v /storage/traefik-app/config:/config \
-v /storage/traefik-app/logs:/logs \
-v /storage/traefik-app/tmp:/tmp \
\
\
traefik:latest \
\
--global.checkNewVersion=false \
--global.sendAnonymousUsage=false \
\
--api.insecure=true \
\
--log=true \
--log.level=INFO \
--log.filePath=/logs/error.log \
--log.noColor=false \
\
--accessLog=true \
--accessLog.filePath=/logs/access.log \
\
--entrypoints.web.address=:80 \
--entrypoints.web.http.tls=false \
--entrypoints.web.http.redirections.entryPoint.to=websecure \
--entrypoints.web.http.redirections.entryPoint.scheme=https \
--entrypoints.web.http.redirections.entryPoint.permanent=true \
\
--entrypoints.websecure.address=:443 \
\
--providers.docker=true \
--providers.docker.watch=true \
--providers.docker.endpoint=unix:///docker.sock \
--providers.docker.exposedbydefault=false \
--providers.docker.network=network_public \
\
--providers.file.directory=/config \
--providers.file.watch=true \
\
--certificatesresolvers.letsencrypt.acme.email=$EMAIL \
--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json \
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web;
# Conferir argumentos do container
docker inspect traefik-app;
docker inspect traefik-app --format '{{.Args}}' 2>/dev/null;
docker inspect traefik-app --format '{{range .Config.Cmd}}{{.}} {{end}}';
2.5 – Container Traefik completo
Agora é hora de fazer direito. Vou explicar após o script o que cada argumento está fazendo.
# Obter email do administrador
EMAIL=$(head -1 /etc/email);
# Nome oficial do servidor
SERVER=$(hostname -f);
# Pasta par reter dados no volume
mkdir -p /storage/traefik-app/letsencrypt; # certificados em acme.json
mkdir -p /storage/traefik-app/logs; # logs de acessos e erros
mkdir -p /storage/traefik-app/config; # configs manuais
mkdir -p /storage/traefik-app/tmp; # core-dump debug
# Obter imagem atualizada
docker image pull traefik:latest;
# Renovar/criar/rodar
docker container rm -f traefik-app 2>/dev/null;
docker container run \
--name traefik-app \
--hostname traefik-app.intranet.br \
--restart unless-stopped \
\
--detach \
--read-only \
\
--cpus 1 \
--memory 512M \
--memory-swap 512M \
--memory-reservation 512M \
--shm-size 512M \
\
--network network_public \
--ip 10.249.255.253 \
--ip6 2001:db8:10:249::255:253 \
--mac-address 00:25:31:25:52:53 \
\
--publish 80:80/tcp \
--publish 443:443/tcp \
--publish 443:443/udp \
--publish 8081:8081/tcp \
\
--publish 5001-5009:5001-5009/tcp \
--publish 5001-5009:5001-5009/udp \
\
-v /var/run/docker.sock:/var/run/docker.sock:ro \
\
-v /storage/traefik-app/letsencrypt:/letsencrypt \
-v /storage/traefik-app/config:/config:ro \
-v /storage/traefik-app/logs:/logs \
-v /storage/traefik-app/tmp:/tmp \
\
--health-cmd="traefik healthcheck --ping --pingEntryPoint=ping" \
--health-start-period=6s \
--health-start-interval=1s \
--health-interval=10s \
--health-timeout=3s \
--health-retries=3 \
\
--label "traefik.enable=true" \
--label "traefik.http.routers.dashboard.rule=Host(\`${SERVER}\`)" \
--label "traefik.http.routers.dashboard.entrypoints=dashboard" \
--label "traefik.http.routers.dashboard.service=api@internal" \
--label "traefik.http.routers.dashboard.tls=true" \
--label "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" \
\
\
traefik:latest \
\
--global.checkNewVersion=false \
--global.sendAnonymousUsage=false \
\
--api.insecure=true \
--api.dashboard=true \
\
--ping=true \
\
--log=true \
--log.level=INFO \
--log.filePath=/logs/error.json \
--log.format=json \
--log.noColor=true \
\
--accessLog=true \
--accessLog.filePath=/logs/access.json \
--accessLog.format=json \
--accessLog.fields.defaultMode=keep \
--accessLog.fields.headers.defaultMode=drop \
--accessLog.fields.headers.names.X-Forwarded-For=keep \
--accessLog.fields.headers.names.X-Real-IP=keep \
\
--entrypoints.ping.address=127.0.0.1:8082 \
\
--entrypoints.dashboard.address=:8081 \
\
--entrypoints.web.address=:80 \
--entrypoints.web.http.tls=false \
--entrypoints.web.http.redirections.entryPoint.to=websecure \
--entrypoints.web.http.redirections.entryPoint.scheme=https \
--entrypoints.web.http.redirections.entryPoint.permanent=true \
--entrypoints.web.http.redirections.entrypoint.priority=10000000 \
\
--entrypoints.websecure.address=:443 \
--entryPoints.websecure.forwardedHeaders.insecure=true \
--entrypoints.websecure.http3=true \
\
--entrypoints.port5001.address=:5001 \
--entrypoints.port5002.address=:5002 \
--entrypoints.port5003.address=:5003 \
--entrypoints.port5004.address=:5004 \
--entrypoints.port5005.address=:5005 \
--entrypoints.port5006.address=:5006 \
--entrypoints.port5007.address=:5007 \
--entrypoints.port5008.address=:5008 \
--entrypoints.port5009.address=:5009 \
\
--providers.docker=true \
--providers.docker.watch=true \
--providers.docker.endpoint=unix:///var/run/docker.sock \
--providers.docker.exposedbydefault=false \
--providers.docker.network=network_public \
--providers.docker.defaultRule="Host(\`{{ .Name }}.dominio.com\`)" \
\
--providers.file.directory=/config \
--providers.file.watch=true \
\
--certificatesresolvers.letsencrypt.acme.email=$EMAIL \
--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json \
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web;
# Conferir argumentos do container
docker inspect traefik-app;
docker inspect traefik-app --format '{{.Args}}' 2>/dev/null;
docker inspect traefik-app --format '{{range .Config.Cmd}}{{.}} {{end}}';
2.6 – Containers de teste HTTPs
O Traefik tem um container auxiliar na imagem “traefik/whoami” muito bom para testes de URLs.
Vou criar o FQDN no DNS chamado who01.dominio.com para esse teste.
# Nome de DNS do contaienr (deve apontar para o IP do servidor Traefik)
#FQDN="who01.$(hostname -f)";
FQDN="who01.dominio.com";
# Obter imagem atualizada
docker image pull traefik/whoami:latest;
# Renovar/criar/rodar
docker container rm -f whoami001 2>/dev/null;
docker container run \
--name whoami001 \
--hostname whoami001.intranet.br \
--restart unless-stopped \
--network network_public \
--detach --read-only \
\
--label "traefik.enable=true" \
\
--label "traefik.http.routers.whoami001.rule=Host(\`${FQDN}\`)" \
--label "traefik.http.routers.whoami001.entrypoints=web,websecure" \
--label "traefik.http.routers.whoami001.service=whoami001" \
--label "traefik.http.routers.whoami001.tls=true" \
--label "traefik.http.routers.whoami001.tls.certresolver=letsencrypt" \
\
--label "traefik.http.services.whoami001.loadbalancer.server.port=80" \
\
traefik/whoami;
# Listar labels do container pronto:
docker container inspect \
whoami001 \
--format '{{range $k,$v := .Config.Labels}}{{$k}}={{$v}}{{"\n"}}{{end}}' \
| sort;
Ao acessar o endereço https://who01.dominio.com você verá o seguinte texto:
Hostname: whoami001.intranet.br
IP: 127.0.0.1
IP: ::1
IP: 10.249.0.1
IP: 2001:db8:10:249::1
IP: fe80::6489:cbff:fe9b:2d4f
RemoteAddr: 10.249.255.253:57466
GET / HTTP/1.1
Host: who01.cloud01.tmsoft.com.br
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Priority: u=0, i
Sec-Ch-Ua: "Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 45.255.130.219
X-Forwarded-Host: who01.dominio.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: traefik-app.intranet.br
X-Real-Ip: 45.255.130.219
2.7 – Containers de teste TLS-Offload
Neste exemplos vamos usar a técnica de proxy-TCP com TLS na entrada e TCP simples até o container. O Traefik não fará processamento do payload HTTP, somente repasse do payload TCP puro.
O que muda nos labels:
- de Host para HostSNI;
- de HTTP para TCP (uma camada abaixo).
Vou criar o FQDN no DNS chamado redis001.dominio.com para esse teste.
# Nome de DNS do contaienr (deve apontar para o IP do servidor Traefik)
#FQDN="redis001.$(hostname -f)";
FQDN="redis001.dominio.com";
# Obter imagem atualizada
docker image pull redis:latest;
# Renovar/criar/rodar
docker container rm -f redis001 2>/dev/null;
docker container run \
--name redis001 \
--hostname redis001.intranet.br \
--restart unless-stopped \
--network network_public \
--detach --read-only \
\
--label "traefik.enable=true" \
\
--label "traefik.tcp.routers.redis001.entrypoints=port5001" \
--label "traefik.tcp.routers.redis001.rule=HostSNI(\`$FQDN\`)" \
--label "traefik.tcp.routers.redis001.service=redis001" \
--label "traefik.tcp.routers.redis001.tls=true" \
--label "traefik.tcp.routers.redis001.tls.certresolver=letsencrypt" \
--label "traefik.tcp.services.redis001.loadbalancer.server.port=6379" \
\
redis:latest \
redis-server --save "" --appendonly no;
Testando o redis001.dominio.com no redis-cli:
# Testando Redis com TLS
FQDN="redis001.dominio.com";
# Deve mostrar o certificado Let's Encrypt emitido para who02.dominio.com
openssl s_client -connect $FQDN:5001 -servername $FQDN;
# Conectar usando TLS — o redis-cli negocia TLS com o Traefik,
# mas o Redis interno recebe TCP puro e nem sabe disso
redis-cli -h $FQDN -p 5001 --tls --sni $FQDN PING;
# PONG
Essa técnica é pouco explorada, a vantagem dela é poder abrir qualquer porta no entrypoints com TLS e entregar a um container apenas o TCP simples.
O Traefik obterá o certificado assinado usando Acme e te entregará uma porta TLS perfeita para seu aplicativo/protocolo.
Onde usar:
- RabbitMQ;
- Postgres;
- MariaDB
- Redis;
- NATS;
3 – Usando Traefik no Docker Swarm
Agora vem a parte boa: Distribuir containers em vários servidores e ainda acessá-los por uma única URL HTTPS, independe de onde eles estão.
Vou considerar que você possui domínio total de Docker Swarm.
3.1 – Rede Docker – Overlay
Voltando ao problema do Traefik em só atender em uma única rede, teremos que converter essa rede de bridge para overlay.
Redes:
- network_cloud: Nome fixo e global.
- Não terá acesso à Internet, será do tipo internal;
- O Traefik será instruído a escutar e alcançar containers na rede “network_cloud“;
- O argumento “attachable” permite que outros containers sejam anexados a essa rede, que é exatamente o propósito;
- IPv6 recomendado sempre, não há necessidade de saída para Internet;
- O MTU deve ser compatível com o PMTU para alcançar todos os demais nós do cluster com o overhead VXLAN, ideal jumbo-frame 9000.
- traefik_wan: Nome particular da stack traefik.
- O Traefik precisa de Internet para obter DNS e resolver certificados via ACME;
- Rede particular do Traefik, nenhum outro container irá participar dela;
- IPv6 deve ser ativado somente se todos os nós do cluster estiverem homologados em IPv6 com acesso global;
- O MTU dela deve ser compatível com o PMTU até a Internet geral, ideal IPoE 1500.
Detalhe: Redes overlay não podem ter o MTU inferior a 1330.
Visualização:

No diagrama acima observamos:
- Ingress: A porta 80 e 443 é aberta em todos os nós do cluster, as conexões são recebidas e encaminhadas pela rede overlay ingress até o nó onde está o container (task) traefik, toda conexão estabelecida por ela retorna por ela;
- Egress: O traefik é o único que pode usar a rede traefik_wan para navegar, os demais containers devem possuir uma rede própria para navegar;
- Rede network_cloud: Todos os containers ligados a ela podem se comunicar com o Traefik para receber e responder requisições, nenhuma conexão vinda da Internet alcança esses containers sem passar pelo Traefik.
3.2 – Criando rede Overlay
Criando as duas redes:
# Rede de ingress (internet => traefik)
#-----------------------------------------------------------------------
# obs: --ipv6 opcional;
# docker network rm traefik_wan;
docker network create traefik_wan \
-d overlay \
--opt encrypted=false \
--opt com.docker.network.driver.mtu=1330 \
--label type=private \
--label internet=true \
--label type=proxy-ingress;
# Rede de comunicacao com containers atendidos pelo Traefik
#-----------------------------------------------------------------------
# obs: --ipv6 recomendado, somente se houver suporte total no cluster
# docker network rm network_cloud;
docker network create network_cloud \
-d overlay \
--scope global \
--opt encrypted=false \
--opt com.docker.network.driver.mtu=1330 \
--label type=public \
--label internet=false \
--label traefik=true \
--attachable \
--internal;
# Rede de containers standalone (locais, fora do stack/service)
#-----------------------------------------------------------------------
docker network create network_public \
-d bridge \
-o com.docker.network.bridge.name=br-net-public \
-o com.docker.network.driver.mtu=1500 \
-o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \
--subnet 10.249.0.0/16 \
--gateway 10.249.255.254;
Visualizar detalhes das redes:
# Detalhes das redes
# - rede do traefik
docker network inspect traefik_wan;
docker network inspect traefik_wan \
--format '{{.Name}} ipv6={{.EnableIPv6}} {{json .IPAM.Config}}';
# - rede do containers swarm
docker network inspect network_cloud;
docker network inspect network_cloud \
--format '{{.Name}} ipv6={{.EnableIPv6}} {{json .IPAM.Config}}';
# - rede do containers standalone/locais
docker network inspect network_public;
docker network inspect network_public \
--format '{{.Name}} ipv6={{.EnableIPv6}} {{json .IPAM.Config}}';
3.3 – Conexões de entrada
O Docker possui duas formas da conexão que vem da internet chegar até o container. Modo host e ingress.
No modo host, padrão do Docker Standalone (sem Swarm):
- A porta é aberta somente no servidor onde o container está;
- Se o Traefik estiver no servidor 1 num cluster com 20 nós, os outros 19 não abrirão essa porta;
- Permite que cada servidor tenha seu próprio proxy-reverso independente.
O idea para o Traefik é o modo host para implementações normais e simples.

Já o modo ingress, recurso do Docker Swarm:
- A porta é aberta em todos os servidores do cluster;
- Se o Traefik estiver no servidor 1 e a conexão chegar no IP do servidor 20, a conexão é transportada (Routing Mesh IPVS Overlay) até o servidor do Traefik;

3.4 – Traefik em Ingress único
A primeira técnica é a mais simples.
Basta implementar o Traefik no Swarm e piná-lo no servidor que terá seu IP público (IPv4, IPv6) vinculado no DNS.
O ideal é que o Traefik rode somente nesse servidor para evitar utilizar a rede overlay duas vezes, a primeira para chegar no Traefik em outro nó, a segunda para o Traefik chegar ao container endpoint.

Implementando a Stack acima, o primeiro servidor se chama “cloud01.dominio.com” e o serviço ficará restrito a ele.
Script do serviço:
# Criar serviço do Traefik
# Obter email do administrador
EMAIL=$(head -1 /etc/email);
# Apagar servico atual (modo de limpeza nuclear)
docker service rm traefik-app 2>/dev/null;
# Criar o servico
docker service create \
--detach \
--name traefik-app \
--replicas 1 \
--replicas-max-per-node 1 \
--update-parallelism 1 \
--rollback-parallelism 0 \
--rollback-delay 0s \
--stop-grace-period 3s \
--update-failure-action rollback \
--restart-condition any \
\
--constraint 'node.hostname == cloud01.dominio.com' \
--constraint 'node.role == manager' \
--hostname 'traefik-app.intranet.br' \
--container-label 'com.docker.swarm.service.name=traefik-app' \
\
--read-only \
\
--limit-cpu 1 \
--limit-memory 512M \
--reserve-cpu 1 \
--reserve-memory 512M \
\
--network traefik_wan \
--network network_cloud \
\
--publish mode=host,published=80,target=80,protocol=tcp \
--publish mode=host,published=443,target=443,protocol=tcp \
--publish mode=host,published=443,target=443,protocol=udp \
\
--mount type=volume,source=traefik-letsencrypt,target=/letsencrypt \
--mount type=volume,source=traefik-config,target=/config,readonly \
--mount type=volume,source=traefik-logs,target=/logs \
--mount type=volume,source=traefik-tmp,target=/tmp \
--mount type=bind,source=/var/run/docker.sock,target=/docker.sock,readonly \
\
--health-cmd "traefik healthcheck --ping --pingEntryPoint=ping" \
--health-start-period 6s \
--health-interval 10s \
--health-timeout 3s \
--health-retries 3 \
\
\
traefik:latest \
\
--global.checkNewVersion=false \
--global.sendAnonymousUsage=false \
\
--api.insecure=true \
--api.dashboard=false \
\
--ping=true \
\
--log=true \
--log.level=INFO \
--log.filePath=/logs/error.json \
--log.format=json \
--log.noColor=true \
\
--accessLog=true \
--accessLog.filePath=/logs/access.json \
--accessLog.format=json \
--accessLog.fields.defaultMode=keep \
--accessLog.fields.headers.defaultMode=drop \
--accessLog.fields.headers.names.X-Forwarded-For=keep \
--accessLog.fields.headers.names.X-Real-IP=keep \
\
--entrypoints.ping.address=127.0.0.1:8082 \
\
--entrypoints.web.address=:80 \
--entrypoints.web.http.tls=false \
--entrypoints.web.http.redirections.entryPoint.to=websecure \
--entrypoints.web.http.redirections.entryPoint.scheme=https \
--entrypoints.web.http.redirections.entryPoint.permanent=true \
--entrypoints.web.http.redirections.entrypoint.priority=10000000 \
\
--entrypoints.websecure.address=:443 \
--entryPoints.websecure.forwardedHeaders.insecure=true \
--entrypoints.websecure.http3=true \
\
--providers.swarm=true \
--providers.swarm.endpoint=unix:///docker.sock \
--providers.swarm.watch=true \
--providers.swarm.exposedbydefault=false \
--providers.swarm.network=network_cloud \
\
--providers.file.directory=/config \
--providers.file.watch=true \
\
--certificatesresolvers.letsencrypt.acme.email=$EMAIL \
--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json \
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web;
Visualizando detalhes do serviço Traefik:
# Conferir o log do Docker
journalctl -u docker --no-pager -n 200;
# journalctl --rotate;
# journalctl --vacuum-time=1s;
# journalctl --vacuum-time=7d;
# Conferir o serviço
docker service ls;
docker service ps traefik-app --no-trunc;
docker service inspect traefik-app;
docker service inspect traefik-app --pretty;
Stack (docker stack deploy):
# Considerando que as redes ja foram criadas previamente:
networks:
traefik_wan:
external: true
network_cloud:
external: true
# Para criar as duas redes nessa stack, com ipv6
#networks:
# traefik_wan:
# driver: overlay
# enable_ipv6: true
# network_cloud:
# driver: overlay
# enable_ipv6: true
volumes:
traefik-letsencrypt:
external: true
traefik-config:
external: true
traefik-logs:
external: true
traefik-tmp:
external: true
services:
traefik-app:
image: traefik:latest
hostname: traefik-app.intranet.br
read_only: true
stop_grace_period: 3s
networks:
- traefik_wan
- network_cloud
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 443
published: 443
protocol: udp
mode: host
volumes:
- type: volume
source: traefik-letsencrypt
target: /letsencrypt
- type: volume
source: traefik-config
target: /config
read_only: true
- type: volume
source: traefik-logs
target: /logs
- type: volume
source: traefik-tmp
target: /tmp
- type: bind
source: /var/run/docker.sock
target: /docker.sock
read_only: true
labels:
- "com.docker.swarm.service.name=traefik-app"
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping", "--pingEntryPoint=ping"]
start_period: 6s
interval: 10s
timeout: 3s
retries: 3
deploy:
mode: replicated
replicas: 1
placement:
max_replicas_per_node: 1
constraints:
- "node.hostname == cloud01.dominio.com"
- "node.role == manager"
resources:
limits:
cpus: "1"
memory: 512M
reservations:
cpus: "1"
memory: 512M
restart_policy:
condition: any
update_config:
parallelism: 1
failure_action: rollback
rollback_config:
parallelism: 0
delay: 0s
command:
- "--global.checkNewVersion=false"
- "--global.sendAnonymousUsage=false"
- "--api.insecure=true"
- "--api.dashboard=false"
- "--ping=true"
- "--log=true"
- "--log.level=INFO"
- "--log.filePath=/logs/error.json"
- "--log.format=json"
- "--log.noColor=true"
- "--accessLog=true"
- "--accessLog.filePath=/logs/access.json"
- "--accessLog.format=json"
- "--accessLog.fields.defaultMode=keep"
- "--accessLog.fields.headers.defaultMode=drop"
- "--accessLog.fields.headers.names.X-Forwarded-For=keep"
- "--accessLog.fields.headers.names.X-Real-IP=keep"
- "--entrypoints.ping.address=127.0.0.1:8082"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.tls=false"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.web.http.redirections.entryPoint.permanent=true"
- "--entrypoints.web.http.redirections.entrypoint.priority=10000000"
- "--entrypoints.websecure.address=:443"
- "--entryPoints.websecure.forwardedHeaders.insecure=true"
- "--entrypoints.websecure.http3=true"
- "--providers.swarm=true"
- "--providers.swarm.endpoint=unix:///docker.sock"
- "--providers.swarm.watch=true"
- "--providers.swarm.exposedbydefault=false"
- "--providers.swarm.network=network_cloud"
- "--providers.file.directory=/config"
- "--providers.file.watch=true"
- "--certificatesresolvers.letsencrypt.acme.email=voce@dominio.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
3.5 – Testando serviços distribuidos no Swam
Com o Traefik conectado à rede overlay network_cloud, vamos subir serviços que se espalhem pela nuvem Swarm e ver como o acesso é balanceado entre eles.
Os serviços serão acessíveis pelo Traefik, mas cada servição não terá acesso à Internet (network_cloud é internal).
# Nome de DNS do contaienr (deve apontar para o IP do servidor Traefik)
#FQDN="who01.$(hostname -f)";
FQDN="who01.dominio.com";
# Remover serviço anterior (se existir)
docker service rm whoami001 2>/dev/null;
# Criar/rodar o serviço em modo global (1 task por nó)
docker service create \
--name whoami001 \
--hostname whoami001.intranet.br \
--mode global \
--restart-condition any \
--network network_cloud \
--read-only \
--with-registry-auth \
\
--label "traefik.enable=true" \
\
--label "traefik.http.routers.whoami001.rule=Host(\`${FQDN}\`)" \
--label "traefik.http.routers.whoami001.entrypoints=web,websecure" \
--label "traefik.http.routers.whoami001.service=whoami001" \
--label "traefik.http.routers.whoami001.tls=true" \
--label "traefik.http.routers.whoami001.tls.certresolver=letsencrypt" \
\
--label "traefik.http.services.whoami001.loadbalancer.server.port=80" \
\
traefik/whoami:latest;
Visualizando detalhes do serviço WhoamI:
# Conferir 1 task por nó:
docker service ps whoami001 \
--format 'table {{.Node}}\t{{.Name}}\t{{.CurrentState}}';
# Listar labels do serviço:
docker service inspect \
whoami001 \
--format '{{range $k,$v := .Spec.Labels}}{{$k}}={{$v}}{{"\n"}}{{end}}' \
| sort;Cada acesso ao “who01.dominio.com” mostrará informações diferentes do container, o motivo disso é o balanceamento IPVS do Docker Swarm, não há balanceamento feito pelo Traefik, observe o diagrama:

4 – Traefik Cluster
O capítulo anterior apresentou o Traefik com um serviço de uma task só (apenas 1 container).
Agora vou explorar os detalhes técnicos para atingir resiliência máxima e escala horizontal.
Se a porta de entrada estiver em modo Ingress o container pode estar em qualquer nó do cluster que o serviço funcionará mas ainda há o problema de existir apenas um ponto de contato (SPoF – Single Point of Failure), que é exatamente esse container singular.
Para escalar e aguentar carga paralela, o Traefik precisa ser um serviço replicado em vários servidores com vários pontos de entrada, vamos abordar os problemas e soluções dessa técnica.
4.1 – Balanceamento no DNS
É possível balancer as conexões ainda no DNS, registros tipo [A, AAAA, MX, SRV, TXT, PTR, CAA] podem ter o mesmo recurso (nome) vinculado a vários valores.
Quando isso ocorre, o servidor DNS fornece todos os valores do recurso em uma única resposta, mas a cada resposta os valores mudam de ordem no pacote (round-robin ou randomicamente).
Exemplo:
- Registro
www(FQDN:www.dominio.com.) possuir os valores:www IN A [11.1.1.1,22.2.2.2,33.3.3.3]
- Primeira resposta:
www IN A [11.1.1.1,22.2.2.2,33.3.3.3] - Segunda resposta:
www IN A [,22.2.2.2,33.3.3.3]11.1.1.1 - Terceira resposta:
www IN A [,33.3.3.3,11.1.1.1]22.2.2.2
Com isso, cada cliente que se conectar ao servidor HTTP escolherá o primeiro IP da lista (o cliente também pode randomizar a lista por conta própria).
Diagrama:

Esse balanceamento não é muito previsível e pode levar os servidores a terem cargas de trabalho diferentes.
Alguns provedores profissionais (Akamai, CloudFlare, DNS Made Easy, Amazon Route53, COnstellix) e softwares de DNS inteligente integráveis (PowerDNS, NSD, Bind9) permitem tais implementações.
Isso se chama DNS Health Check, nele o DNS assume uma posição ativa no cluster e monitora os alvos de seus registros:
- Testando a disponibilidade, se o IP parar de responder a PING, HTTP-STATUS-CHECK, começar a retornar erros HTTP (status 500 pra cima), ele retira o IP da lista automaticamente;
- Monitorar a capacidade de cada servidor em tempo real para corrigir desequilibrios;
- Alternar IPs propositalmente para evitar bloqueios misteriosos no caminho:
- Servidor A possui os IPs:
,11.1.1.2,11.1.1.211.1.1.2 - Servidor B possui os IPs:
,22.2.2.4,22.2.2.522.2.2.6 - Servidor C possui os IPs:
,33.3.3.7,33.3.3.833.3.3.9 - Na primeira hora, o registro
wwwpossuirá o primeiro valor de cada servidor; - Na segunda hora, passamos a usar o segundo IP de cada servidor, e assim em diante;
- Servidor A possui os IPs:
Outro recurso importante é o Split Horizon (horizonte dividido), nele o DNS possui tabelas de valores baseados no IP de origem da solicitação (IP do servidor DNS dos usuários):
- IP de origem do Brasil:
- Enviar para o datacenter Equinix em São Paulo/Brasil;
-
www IN A [11.1.1.1,,11.1.1.2]11.1.1.3
- IP de origem no Canadá:
- Enviar para o datacenter Digital Realty em Vaughan/Canadá;
www IN A [,22.2.2.14,22.2.2.25]22.2.2.36
- IP de origem Europa, Africa e Asia:
- Enviar para o datacenter Start Campus em Sienes/Portugal;
www IN A [,33.3.3.91,33.3.3.92]33.3.3.93
Se você está no Brasil e usar um DNS no Canadá, você induzirá erroneamente o DNS do domínio a te conduzir para o IP também no Canadá, mesmo que seu IP de cliente HTTP (navegador) esteja em Minas Gerais.
4.3 – Balanceamento DNS em VPS/VM
Acredito que esse seja o caso mais desejado por ser barato. Quem monta um sistema online (SaaS) e deseja redundância e contingência automática irá optar por um DNS guiando o balanceamento inicial com Round-Robin e SplitHorizon.
Para evitar uma latência ainda maior no Swarm, o Traefik deve estar presente em todos os nós sem Ingress pois o balanceamento na entrada poderá fazer com que a conexão que chega no servidor A seja entregue ao Traefik em B, que precisará entregar a um container em C (ingress A=> traefik B => task C). O trajeto A=>C é mais eficiente se a porta do Traefik estiver em modo host.

Nesse modo você deve garantir o Traefik como um serviço modo global (presente em todos os nós do cluster) e terá que configurar todos os IPs no DNS.
Alternativamente, se você deseja apenas 2 servidores como pontos de entrada, coloque restrição no serviço para apenas esses e faça o mesmo no DNS.
4.4 – Topologia de entrada com WAF
O WAF (Web Application Firewall) é um equipamento anterior a nosso cluster que receberá as conexões, principais recursos:
- Anti-DDoS: Protege contra ataques DDoS, tentativas de invasão, bugs de clientes em loop, entre outros problemas a filtrar;
- QoS e QoE: Para priorizar conexões importantes em detrimento do lixo;
- GeoIP Split-Horizon: Implementa o tratamento de requisições baseadas no IP de origem, reserva por zona (exemplo: 90% para Brasil, 10% para o resto do mundo);
- KeepAlive e fail-over: Monitora os servidores internos para contornar automaticamente servidores indisponíveis/sobrecarregados ou marcados para manutenção;
- Balanceamento TCP: Distribui as conexões TCP já estabelecidas com o proxy-reverso, Traefik no nosso caso, mas tambem pode entregar diretamente aos serviços (tasks/containers).
Envolvendo o Traefik, ele entregará conexões mais confiáveis e limpas de todo lixo nos IPs de cada servidor do cluster Swarm, que por sua vez entreguará ao serviço Traefik, que por meio do IPVS distribuirá às tasks replicadas (containers Traefik).
A escala horizontal envolve adicionar mais servidores executando, mais tasks do Traefik (scale) e mais equipamentos WAF em paralelo.

4.5 – Configuração em KDB provider
O Traefik suporta armazenamento centralizado em seviços de Chave/Valor (KeyValue Databases), sistemas de Chave/Valor suportados:
consuletcdredis ouvalkeyboltdb
Vamos utilizar o Valkey (clone melhorado do Redis) para provar o conceito, obviamente ele também deve estar em seu próprio cluster para não virar um SPoF.
Criando serviço do Valkey na rede network_cloud:
#!/bin/sh
# Remove serviço anterior se existir
docker service rm valkey-kdb 2>/dev/null;
# Volume para persistência AOF
docker volume create valkey-kdb-data 2>/dev/null;
# Cria o serviço Valkey 8 com AOF
docker service create \
--name valkey-kdb \
--hostname valkey-kdb.intranet.br \
\
--constraint 'node.hostname == cloud01.dominio.com' \
--constraint 'node.role == manager' \
--container-label 'com.docker.swarm.service.name=valkey-kdb' \
\
--mode replicated \
--replicas 1 \
--restart-condition any \
--restart-max-attempts 5 \
--with-registry-auth \
\
--network network_cloud \
\
--mount type=volume,source=valkey-kdb-data,target=/data \
\
--label "traefik.enable=true" \
\
--label "traefik.tcp.routers.valkey-kdb.rule=HostSNI(\`${FQDN}\`)" \
--label "traefik.tcp.routers.valkey-kdb.entrypoints=valkey" \
--label "traefik.tcp.routers.valkey-kdb.service=valkey-kdb" \
--label "traefik.tcp.routers.valkey-kdb.tls=true" \
--label "traefik.tcp.routers.valkey-kdb.tls.certresolver=letsencrypt" \
\
--label "traefik.tcp.services.valkey-kdb.loadbalancer.server.port=6379" \
\
valkey/valkey:8-alpine \
valkey-server \
--appendonly yes \
--appendfsync everysec \
--dir /data;
Visualizando detalhes do serviço valkey-kdb:
# Conferir detalhes do servico valkey-kdb:
docker service ps valkey-kdb;
docker service ps valkey-kdb \
--format 'table {{.Node}}\t{{.Name}}\t{{.CurrentState}}';
# Detalhes completos:
docker service inspect valkey-kdb;
Agora vamos conectar o Traefik no Valkey (ou Redis, a palavra chave é “redis“).
Quando você seta providers.redis, o Traefik se conecta a ele e fica dando WATCH em chaves específicas, toda vez que a chave é alterada (por você, outro software ou outras tasks do Traefik) ele reconfigura imediatamente.
Ele resolve nossos problemas:
- Volumeless: O Traefik deixa de usar volume montado para configurações geradas por software externo, ele agora opera 100% na memória e usa o Redis como banco de dados em tempo real;
- Rotas: Passam a ser comum a todos os Traefiks do cluster, o que um descobrir os demais saberão.
Isso substitui a técnica de reconfiguração dinâmica em arquivo apresentada antes. Uma configuração que pode ser feita de qualquer lugar diretamente no Redis por um software SDN.
Argumentos para configurar o provider em Redis:
# Argumentos CMD:
... \
--providers.redis.endpoints=valkey-kdb:6379 \
--providers.redis.password=suaSenhaForte \
--providers.redis.rootKey=traefik-app \
... \
# Argumentos na stack:
- "--providers.redis.endpoints=valkey:6379"
- "--providers.redis.password=suaSenhaForte"
- "--providers.redis.rootKey=traefik-app"
O valor “rootKey” informa o prefixo da configuração, onde cada nível é separado por “/“.
Comandos no Valkey para configurar uma rota:
# Rota para https://www1.dominio.com no container chamado "site1" porta 8080
SET traefik-app/http/routers/www1/rule "Host(`www1.dominio.com`)"
SET traefik-app/http/routers/www1/service "www1-svc"
SET traefik-app/http/routers/www1/entrypoints "websecure"
SET traefik-app/http/routers/www1/tls "true"
SET traefik-app/http/services/www1-svc/loadbalancer/servers/0/url "http://site1:8080"
Pegadinhas importantes:
- Cluster Redis/Valkey: A alta disponibilidade usando sharding não é suportada, use 1 instância só ou Redis Sentinel com 1 master (1 replica + volume persistente). Um proxy Redis resolve isso;
- Senha: Se usar –requirepass no Valkey, tem que passar –providers.redis.password no Traefik.
- Rede: O Redis não pode depender do Traefik para ser alcançado (TLS Offload);
- Prefixo: rootKey default é “traefik“, eu prefiro usar “traefik-app“;
- Watch: O Traefik faz PSUBSCRIBE keyspace@0:traefik-app*, garanta que seu Valkey tem notify-keyspace-events habilitado (padrão no Valkey 7+);
- Certificados: Certificados ACME e chaves privadas não suportam armazenamento em KV na versão gratuita (somente na Enterprise).
4.6 – Certificados TLS/HTTPs no Cluster
Os certificados ACME ficam armazenados no volume (arquivo acme.json), ao replicar o serviço pelo cluster cada task terá sua versão particular desse arquivos (problemão).
Os servidores não estarão em acordo sobre isso, a consequência será vários servidores tentando gerar certificados ao mesmo tempo (a API ACME pode até bloquear o domínio se detectar repetições em menos de 2 minutos).
O Traefik gratuito não suporta armazenar as chaves e certificados em um servidor Key-Value pairs centralizado, esse recursos existe apenas no Traefik Enterprise ($).
Algumas dicas de como contornar isso:
- A – Certificado Wildcard: Não usar Acme/LetsEncrypt, em vez disso, comprar anualmente o
*.dominio.come fixar chave e certificado na configuração de TLS; - B – Volume compartilhado: Usar NFS/Ceph/Gluster para compartilhar o volume com todos os containers (tasks) do Traefik;
- C – Plugin: Utilizar um plugin que resolva esse problema, até mesmo usando o Valkey;
- D – Tunnel: PRO – Terceirizar o WAF na nuvem, usando por exemplo o CloudFlared Zero Trust;
- E – WAF TLS Offload: PRO – Fazer o TLS Offload antes do Traefik em sistema WAF que suporte cluster com compartilhamento de certificados ACME em tempo real, o Traefik passará a receber na porta 80 (sem TLS) e para que os serviços continuem compatíveis, use a porta 80 no entrypoint websecure.
Eu utilizo a primeira opção A e recomendo a opção D, se você é grande e especial sobra apenas E!
4.7 – Rodando Traefik em modo Global
Aqui temos a configuração para rodar o serviço Traefik em todos os nós “managers” do Cluster (comente para rodar em todos indiscriminadamente), também inclui o provedor de configurações via Valkey (sem senha).
Vou deixar os volumes nomeados “traefik-config” (/config) e “traefik-certs” (/certs) pronto para o próximo capítulo.
Script do serviço:
# Criar serviço do Traefik
# Obter email do administrador
EMAIL=$(head -1 /etc/email);
# Apagar servico atual (modo de limpeza nuclear)
docker service rm traefik-app 2>/dev/null;
# Criar o servico
docker service create \
--detach \
--name traefik-app \
--mode global \
--update-parallelism 1 \
--rollback-parallelism 0 \
--rollback-delay 0s \
--stop-grace-period 3s \
--update-failure-action rollback \
--restart-condition any \
\
--constraint 'node.role == manager' \
--hostname 'traefik-app.intranet.br' \
--container-label 'com.docker.swarm.service.name=traefik-app' \
\
--read-only \
\
--limit-cpu 1 \
--limit-memory 512M \
--reserve-cpu 1 \
--reserve-memory 512M \
\
--network traefik_wan \
--network network_cloud \
\
--publish mode=host,published=80,target=80,protocol=tcp \
--publish mode=host,published=443,target=443,protocol=tcp \
--publish mode=host,published=443,target=443,protocol=udp \
\
--mount type=volume,source=traefik-letsencrypt,target=/letsencrypt \
--mount type=volume,source=traefik-certs,target=/certs,readonly \
--mount type=volume,source=traefik-config,target=/config,readonly \
--mount type=volume,source=traefik-logs,target=/logs \
--mount type=volume,source=traefik-tmp,target=/tmp \
--mount type=bind,source=/var/run/docker.sock,target=/docker.sock,readonly \
\
--health-cmd "traefik healthcheck --ping --pingEntryPoint=ping" \
--health-start-period 6s \
--health-interval 10s \
--health-timeout 3s \
--health-retries 3 \
\
\
traefik:latest \
\
--global.checkNewVersion=false \
--global.sendAnonymousUsage=false \
\
--api.insecure=true \
--api.dashboard=false \
\
--ping=true \
\
--log=true \
--log.level=INFO \
--log.filePath=/logs/error.json \
--log.format=json \
--log.noColor=true \
\
--accessLog=true \
--accessLog.filePath=/logs/access.json \
--accessLog.format=json \
--accessLog.fields.defaultMode=keep \
--accessLog.fields.headers.defaultMode=drop \
--accessLog.fields.headers.names.X-Forwarded-For=keep \
--accessLog.fields.headers.names.X-Real-IP=keep \
\
--entrypoints.ping.address=127.0.0.1:8082 \
\
--entrypoints.web.address=:80 \
--entrypoints.web.http.tls=false \
--entrypoints.web.http.redirections.entryPoint.to=websecure \
--entrypoints.web.http.redirections.entryPoint.scheme=https \
--entrypoints.web.http.redirections.entryPoint.permanent=true \
--entrypoints.web.http.redirections.entrypoint.priority=10000000 \
\
--entrypoints.websecure.address=:443 \
--entryPoints.websecure.forwardedHeaders.insecure=true \
--entrypoints.websecure.http3=true \
\
--providers.swarm=true \
--providers.swarm.endpoint=unix:///docker.sock \
--providers.swarm.watch=true \
--providers.swarm.exposedbydefault=false \
--providers.swarm.network=network_cloud \
\
--providers.redis.endpoints=valkey-kdb:6379 \
--providers.redis.rootKey=traefik-app \
\
--providers.file.directory=/config \
--providers.file.watch=true \
\
--certificatesresolvers.letsencrypt.acme.email=$EMAIL \
--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json \
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web;
Visualizando detalhes do serviço Traefik:
# Conferir o log do Docker
journalctl -u docker --no-pager -n 200;
# journalctl --rotate;
# journalctl --vacuum-time=1s;
# journalctl --vacuum-time=7d;
# Conferir o serviço
docker service ls;
docker service ps traefik-app --no-trunc;
docker service inspect traefik-app;
docker service inspect traefik-app --pretty;
Stack (docker stack deploy):
# Considerando que as redes ja foram criadas previamente:
networks:
traefik_wan:
external: true
network_cloud:
external: true
# Para criar as duas redes nessa stack, com ipv6
#networks:
# traefik_wan:
# driver: overlay
# enable_ipv6: true
# network_cloud:
# driver: overlay
# enable_ipv6: true
volumes:
traefik-letsencrypt:
external: true
traefik-certs:
external: true
traefik-config:
external: true
traefik-logs:
external: true
traefik-tmp:
external: true
services:
traefik-app:
image: traefik:latest
hostname: traefik-app.intranet.br
read_only: true
stop_grace_period: 3s
networks:
- traefik_wan
- network_cloud
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 443
published: 443
protocol: udp
mode: host
volumes:
- type: volume
source: traefik-letsencrypt
target: /letsencrypt
- type: volume
source: traefik-config
target: /config
read_only: true
- type: volume
source: traefik-logs
target: /logs
- type: volume
source: traefik-tmp
target: /tmp
- type: bind
source: /var/run/docker.sock
target: /docker.sock
read_only: true
labels:
- "com.docker.swarm.service.name=traefik-app"
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping", "--pingEntryPoint=ping"]
start_period: 6s
interval: 10s
timeout: 3s
retries: 3
deploy:
mode: global
placement:
constraints:
- "node.role == manager"
resources:
limits:
cpus: "1"
memory: 512M
reservations:
cpus: "1"
memory: 512M
restart_policy:
condition: any
update_config:
parallelism: 1
failure_action: rollback
rollback_config:
parallelism: 0
delay: 0s
command:
- "--global.checkNewVersion=false"
- "--global.sendAnonymousUsage=false"
- "--api.insecure=true"
- "--api.dashboard=false"
- "--ping=true"
- "--log=true"
- "--log.level=INFO"
- "--log.filePath=/logs/error.json"
- "--log.format=json"
- "--log.noColor=true"
- "--accessLog=true"
- "--accessLog.filePath=/logs/access.json"
- "--accessLog.format=json"
- "--accessLog.fields.defaultMode=keep"
- "--accessLog.fields.headers.defaultMode=drop"
- "--accessLog.fields.headers.names.X-Forwarded-For=keep"
- "--accessLog.fields.headers.names.X-Real-IP=keep"
- "--entrypoints.ping.address=127.0.0.1:8082"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.tls=false"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.web.http.redirections.entryPoint.permanent=true"
- "--entrypoints.web.http.redirections.entrypoint.priority=10000000"
- "--entrypoints.websecure.address=:443"
- "--entryPoints.websecure.forwardedHeaders.insecure=true"
- "--entrypoints.websecure.http3=true"
- "--providers.swarm=true"
- "--providers.swarm.endpoint=unix:///docker.sock"
- "--providers.swarm.watch=true"
- "--providers.swarm.exposedbydefault=false"
- "--providers.swarm.network=network_cloud"
- "--providers.redis.endpoints=valkey:6379"
- "--providers.redis.rootKey=traefik-app"
- "--providers.file.directory=/config"
- "--providers.file.watch=true"
- "--certificatesresolvers.letsencrypt.acme.email=voce@dominio.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
5 – Certificado Próprio no Traefik
Usar o ACME é legal pois dá autonomia ao Traefik de resolver o problema de HTTPs sem que você tenha que se importar.
Por outro lado, não é a melhor estratégia de segurança nem pontua alto nos requisitos de compliance.
5.1 – Gerar chave e requisição de certificado
O primeiro passo é gerar uma chave privada, que NÃO DEVE SER ENVIADA A NINGUÉM fora do servidor.
Escolha um algoritmo, vou usar o RSA co 4096 bits por ser o padrão da industria, alternativamente você pode usar o ED25519 sem problemas.
# Diretorio para armazenar arquivos originais
mkdir /root/certs/dominio-com;
cd /root/certs/dominio-com;
# 4096 bits — mais margem, handshake um pouco mais caro
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out privkey.pem;
# Analisar chave gerada:
openssl pkey -in privkey.pem -noout -text;
# Outros tipos de chaves:
# 2048 bits — mínimo exigido pelo CA/Browser Forum Baseline Requirements
#openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out privkey.pem;
# P-256 (prime256v1 / secp256r1) — o sweet spot
#openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out privkey.pem;
# P-384 (secp384r1) — margem maior
#openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out privkey.pem;
# P-521 (secp521r1) — raro, nem todo cliente prioriza
#openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -out privkey.pem;
# EDCSA
#openssl genpkey -algorithm ED25519 -out privkey.pem;
#openssl genpkey -algorithm ED448 -out privkey.pem;
Agora gere o CSR (Certificate Signing Request), nesse arquivo constará:
- Chave pública;
- Atributos desejados (nome de DNS e Wildcard);
- Assinatura (verificável com a chave pública).
# Entrar na pasta em que esta a chave privada
cd /root/certs/dominio-com;
# Gerar arquivo CSR
openssl req -new \
-key privkey.pem \
-out dominio-com.csr \
-subj "/CN=*.dominio.com/O=Sua Empresa/C=BR" \
-addext "subjectAltName=DNS:*.dominio.com,DNS:dominio.com";
O arquivo “” será gerado após o comando acima, ele é o arquivo que você deverá enviar para o site onde o certificado será adquirido ($).dominio-com.csr
Após confirmar a autoridade sobre o domínio e pagar, em algumas horas ou dias você receberá o pacote para download ou e-mail.
5.2 – Arquivos do certificado
Após comprar o certificado para seu domínio, você deve possuir os seguintes arquivos (extensões pem, key e cert possuem o mesmo tipo de conteúdo):
privkey.pem– Chave privada, garantir que somente o root pode ter acesso;– Ignorar, foi gerado contendo a chave pública e os requisitos desejados, arquivo enviado para a unidade certificadora gerar o certificado assinado;dominio-com.csr– Certificado emitido (assinado) pela unidade certificadora (foi baseado no arquivo CSR);dominio-com.pemchain.pem– Possui os certificados da unidade certificadora, normalmente o certificado da sub-CA que assinou o seu certificado e a sub-CA acima dela, essa por sua vez é assinada por uma CA que consta os certificados globais (RootCAs);fullchain.pem– Junção dos arquivos(primeiro, deve constar no inicio do arquivo) edominio-com.pemchain.pem. Informa num único arquivo a cadeia de certificados até a RootCA.
O Certificado Wildcard possuirá os atributos:
Subject: CN=*.dominio.comX509v3 extensions:X509v3 Subject Alternative Name:DNS:*.dominio.com, DNS:dominio.com
A data de validade (range entre Not Before e Not After) deve estar dentro do tempo presente:
ValidityNot Before: Jan 25 18:14:18 2026 GMTNot After : Jan 23 15:02:43 2027 GMT
Para conferir os atributos e data de validade:
# Conferir atributos do certificado wildcard:
openssl x509 -in dominio-com.crt -noout -text;
# Conferindo data de validade (range):
openssl x509 -in dominio-com.crt -noout -dates;
openssl x509 -in dominio-com.crt -noout -enddate;
# Verificador de validade para script
# Expira nos próximos 30 dias (2.592.000 segundos)?
openssl x509 -in dominio-com.crt -noout -checkend 2592000 \
&& echo "OK por mais 30 dias" \
|| echo "EXPIRA em menos de 30 dias (ou ja expirou, RENOVE IMEDIATAMENTE)";
5.3 – Arquivos do certificado
Coloque no volume “traefik-certs” (montado em /certs dentro do container) na sub-pasta ./dominio.com/:
privkey.pem– Chave privada;fullchain.pem– Certificado do domínio e certificados intermediários.
Caminho dos arquivos dentro do container:
/certs/dominio.com/privkey.pem/certs/dominio.com/fullchain.pem
5.4 – Configurando no Traefik
Vamos criar um arquivo de configuração dinâmico declarando o certificado no volume “traefik-config” (montado em /config dentro do container).
Arquivo: /config/dominio-com-cert.yml
tls:
certificates:
- certFile: /certs/dominio.com/fullchain.pem
keyFile: /certs/dominio.com/privkey.pem
# (opcional, recomendado) torna o wildcard o certificado padrão,
# servido quando o SNI não casa com nada OU o cliente não envia SNI
stores:
default:
defaultCertificate:
certFile: /certs/dominio.com/fullchain.pem
keyFile: /certs/dominio.com/privkey.pem
Ao criar o arquivo o Traefik fará o carregamento automático do certificado.
Agora basta testar em um serviço que fará uso de qualquer nome no dominio.com (observe que não vamos usar mais o tls.certresolver=letsencrypt), o Traefik confere a entrada “Host()” e procura qual certificado no “stores” possui capacidade de atender.
Exemplo:
# Nome de DNS do container
#FQDN="www.$(hostname -f)";
FQDN="www.dominio.com";
# Remover serviço anterior (se existir)
docker service rm www 2>/dev/null;
# Criar/rodar o serviço em modo global (1 task por nó)
docker service create \
--name www \
--hostname www.intranet.br \
--mode global \
--restart-condition any \
--network network_cloud \
--read-only \
--with-registry-auth \
\
--label "traefik.enable=true" \
\
--label "traefik.http.routers.www.rule=Host(\`${FQDN}\`)" \
--label "traefik.http.routers.www.entrypoints=web,websecure" \
--label "traefik.http.routers.www.service=www" \
--label "traefik.http.routers.www.tls=true" \
--label "traefik.http.services.www.loadbalancer.server.port=80" \
\
traefik/whoami:latest;
5.5 – Certificado via Config e Secret
O exemplo anterior foi didático. Na prática, para funcionar, você precisaria criar o volume em todos os servidores com os mesmos arquivos, algo chato de manter.
Existe um truque mais prático usando o Docker Swarm Secrets e Configs, que automaticamente distribui os arquivos para todo o cluster.
Considerando os arquivos originais em /root/certs/dominio-com/ de qualquer servidor manager no Swarm:
# Diretorio com certificados
cd /root/certs/dominio-com/;
# Versionamento das chaves no config/secrets
#-----------------------------------------------------------------------------
# - Recomendavel.: data de vencimento do certificado
# - > openssl x509 -in dominio-com.crt -noout -enddate;
# - > openssl x509 -in dominio-com.crt -noout -enddate | \
# sed 's/notAfter=//' | xargs -I {} date -d "{}" +"%Y%m%d";
KEYNAME="dominio-com";
VERSION="20270123";
# SECRETS
#-----------------------------------------------------------------------------
# Criar secrets
# -- chave privada
cat privkey.pem | docker secret create "${KEYNAME}_privkey_${VERSION}" "-";
# -- full chain
cat fullchain.pem | docker secret create "${KEYNAME}_pubkey_${VERSION}" "-";
# Listar secrets
docker secret ls;
docker secret ls --format "{{.ID}}: {{.Name}}";
# zrp36xg03qqt5yefcni4p8ta0: dominio-com_privkey_20270123
# b25qo5jzi4zva80kd2jwruij4: dominio-com_pubkey_20270123
# CONFIGS
#-----------------------------------------------------------------------------
(
echo;
echo 'tls:';
echo ' certificates:';
echo ' - certFile: /run/secrets/dominio-com-fc.pem';
echo ' keyFile: /run/secrets/dominio-com-pk.pem';
echo;
) | docker config create "${KEYNAME}_traefik_${VERSION}" "-";
# Criar servico usando config e secrets
#-----------------------------------------------------------------------------
docker service create \
... \
... \
--secret source=${KEYNAME}_privkey_${VERSION},target=dominio-com-fc.pem \
--secret source=${KEYNAME}_privkey_${VERSION},target=dominio-com-pk.pem \
... \
... \
--config source=${KEYNAME}_traefik_${VERSION},target=/config/dominio-com.yml \
... \
...
Bom… com os exemplos acima você ja tem imaginação para integrar a gestão de chaves e certificados com o Traefik distribuído no cluster Swarm.
Terminamos por hoje!
Patrick Brandão, patrickbrandao@gmail.com
“É a direção da vela,
e não o sopro da tempestade,
que determina o seu curso na vida.“
Randy Davis
