Inventariando uma frota Docker heterogênea antes de padronizar produção
Como inventariar hosts Docker em modo read-only, descobrir padrões reais de proxy, Compose, usuários, rotas e automações antes de tentar padronizar produção.
“Vamos padronizar os Docker hosts” parece uma tarefa de configuração.
Na prática, antes disso existe outra bem mais importante:
descobrir o que já está rodando de verdade.
Em uma frota real que precisava receber novos serviços, o primeiro inventário consolidou nove hosts ativos. Dois hosts antigos foram tratados como descontinuados e ficaram fora do conjunto operacional.
A expectativa inicial era encontrar algo razoavelmente uniforme.
Não foi o que apareceu.
Entre os hosts ativos havia:
- Traefik em seis;
- Nginx local em um;
- dois sem publicador HTTP local identificado na coleta;
deploycomo usuário operacional provável na maioria;- uma exceção usando outro usuário operacional;
- diferentes versões de Docker/Compose;
- múltiplas formas de organizar Compose;
- pelo menos um container iniciado fora de Compose;
- crons com responsabilidades diferentes entre hosts.
Ou seja: a frota tinha um padrão histórico, mas não um padrão único.
E isso muda completamente a ordem da padronização.
A regra da coleta: primeiro ler, depois decidir #
A auditoria inicial foi feita por SSH em modo somente leitura.
A coleta não deveria:
- reiniciar container;
- editar Compose;
- alterar cron;
- habilitar serviço;
- mudar proxy;
- trocar imagem;
- normalizar nome;
- “corrigir” host durante o inventário.
O objetivo era obter um mapa confiável do estado atual antes de propor qualquer estado futuro.
Essa separação é importante porque, se você corrige enquanto inventaria, perde a capacidade de responder depois:
Como esse ambiente estava antes da mudança?
Comecei pelo que o Docker realmente sabia #
Em cada host, os comandos básicos davam uma primeira visão:
| |
Depois, detalhes via docker inspect.
O nome visível do container nem sempre era a melhor referência para entender a aplicação.
Quando o serviço vinha de Compose, uma fonte mais confiável era a label:
| |
Isso ajudava a separar:
| |
Três coisas que parecem iguais até o dia em que não são.
docker compose config --services ajudava — quando havia Compose reconhecível #
Em hosts com projeto Compose organizado, um comando útil era:
| |
Também:
| |
Mas a própria coleta mostrou um limite importante: em alguns hosts o comando não retornava uma visão útil.
Isso podia acontecer porque o diretório atual não era o projeto certo, porque havia outra organização de arquivos ou porque o container em questão nem tinha sido criado por Compose.
Então o inventário não podia depender de um único comando.
Quando compose config não explicava o host, o docker inspect e os arquivos encontrados no filesystem viravam as próximas fontes.
A organização dos arquivos variava bastante #
Na frota apareceram padrões diferentes:
| |
Isso é exatamente o tipo de informação que uma padronização precisa conhecer antes de começar.
Se eu simplesmente decidir que “a partir de hoje tudo vai para /opt/compose”, ainda falta responder:
- quais serviços já dependem do caminho atual?
- há scripts apontando para ele?
- crons executam
docker composeali? - existe volume relativo?
- certificado ou configuração usa bind mount relativo?
- há mais de um projeto no mesmo diretório?
Padronizar path sem entender dependência é só mover o problema.
Para descobrir publicação, as labels do Traefik eram ouro #
Nos hosts com Traefik, as rotas podiam ser reconstruídas pelas labels.
As regras mais úteis eram as que continham algo como:
| |
A partir daí era possível montar uma tabela conceitual:
| |
Isso ajuda muito em migração porque a pergunta deixa de ser apenas:
Qual container está rodando?
E passa a ser:
Qual URL pública depende desse container e por qual regra ela chega nele?
Nem todo host usava Traefik #
Esse foi um dos achados mais importantes do inventário.
A maioria tinha Traefik, mas não todos.
Havia host com Nginx local e TLS tratado de outra forma. Em outros, nenhum publicador HTTP local foi identificado na coleta.
Então um template único de migração baseado em labels Traefik teria aplicado o padrão majoritário em hosts onde ele simplesmente não existia.
É por isso que eu gosto de classificar o host antes de falar em mudança:
| |
Padronização boa começa reconhecendo exceções reais, não apagando elas da planilha.
Usuário operacional também não era uniforme #
Outro detalhe fácil de ignorar: o usuário usado para operação não era igual em toda a frota.
Na maioria, o padrão provável era um usuário de deploy.
Havia pelo menos uma exceção com outro usuário operacional.
Isso afeta diretamente:
- caminho de home;
- ownership dos arquivos;
- chave SSH;
- localização do Compose;
- crontab do usuário;
- permissões Docker;
- scripts auxiliares.
Um playbook que presume /home/deploy/... sem inventário pode funcionar em oito hosts e falhar justamente no nono.
Cron faz parte da arquitetura, mesmo quando ninguém desenhou #
A coleta de cron mostrou rotinas como:
- login periódico em registry;
docker pullseguido dedocker run;- sincronização de tempo;
- script diário ligado à aplicação;
- host sem cron operacional específico encontrado.
Isso é infraestrutura.
Se um container depende de login ECR renovado por cron, o cron faz parte do fluxo de atualização daquela aplicação.
Se um job recria container com docker run, migrar o serviço para Compose sem remover ou adaptar o cron pode criar dois mecanismos concorrentes de operação.
Por isso eu incluo no inventário:
| |
quando o acesso e a política permitem leitura.
Também vale verificar timers systemd e scripts chamados pelos crons.
O que eu registraria por host #
Depois dessa experiência, um inventário útil de Docker não é uma lista de docker ps.
Eu registraria pelo menos:
| |
A coluna mais importante costuma ser a última.
É nela que aparece tudo que impede o “template universal”.
Inventário e migração começaram a se encontrar #
Depois de mapear os hosts, a estratégia de migração pôde ser incremental.
Em homologação, o padrão validado foi:
- manter aplicações existentes;
- adicionar o serviço novo ao host adequado;
- validar o Compose;
- subir somente o novo serviço;
- testar container e health;
- validar proxy/certificado;
- validar endpoint externo;
- adiar a troca do domínio/tráfego de produção.
Em alguns hosts, isso significava Traefik.
Em outro cenário, foi necessário considerar mensageria local com TLS, proxy e emissão ACME.
Ou seja: o inventário determinou qual runbook de migração fazia sentido para cada classe de host.
Por que eu não começaria “instalando o padrão” #
Porque a frota já tinha serviços reais dependendo da forma antiga de operar.
Uma migração de padrão precisa responder:
| |
Sem inventário, a resposta tende a ser genérica.
Com inventário, dá para criar grupos:
| |
Isso transforma “padronizar nove servidores” em várias mudanças menores e previsíveis.
Um cuidado: ausência na coleta não é ausência no ambiente #
Se docker compose config --services não trouxe saída, isso não prova que nunca houve Compose.
Se não encontrei um proxy HTTP local, isso não prova que não existe publicação por outra camada.
Se não apareceu cron operacional, isso não prova que não existe timer systemd.
No inventário, eu tento escrever a frase do jeito correto:
Não identificado na coleta realizada.
em vez de:
Não existe.
Parece preciosismo até a primeira vez que uma automação escondida aparece depois.
O que ficou desse trabalho #
A frota não precisava primeiro de uma padronização.
Precisava de uma linguagem comum para descrever o estado real.
Depois que cada host podia ser comparado pelas mesmas dimensões — proxy, serviço, imagem, Compose, usuário, rota, cron e exceções — ficou muito mais fácil decidir o que de fato deveria virar padrão.
A principal lição foi simples:
não use o padrão que você quer como lente para inventariar o ambiente que você tem.
Primeiro descreva o que existe.
Depois padronize com evidência.
Me chama.
Manda o contexto, os sintomas e o que já foi testado. Bora organizar as evidências antes de sair mexendo.