Mapeando dependências de serviço a partir do /proc, sem SDK de agente
Você não precisa instrumentar nada para saber qual serviço fala com qual. O kernel já anotou isso.
10 de julho de 2026 · 8 min de leitura
"Adicione um agente em cada serviço" é a resposta que a maioria das ferramentas de observabilidade dá para descoberta de serviços, e é uma resposta razoável se você está disposto a mexer em todo manifesto de deployment, reiniciar todo processo, e manter a versão do SDK sincronizada entre uma dúzia de serviços cujos donos não se reportam a você. É uma resposta bem menos razoável se você está tentando mapear um sistema nos primeiros cinco minutos de uso de um produto, antes de qualquer pessoa ter concordado em mudar uma única linha de configuração.
O kernel já sabe quais sockets estão abertos e quem é o dono de cada um. Você só precisa ir lá e ler.
O que realmente tem em /proc
Todo processo no Linux ganha um diretório em /proc/<pid>, e dentro dele, net/tcp (e net/tcp6 para IPv6) lista todo socket TCP visível no namespace de rede daquele processo: endereço local, endereço remoto, estado da conexão e — o campo que importa aqui — um número de inode. Esse inode é o mesmo número que você vai encontrar em /proc/<pid>/fd/ como o alvo de um symlink para qualquer descritor de arquivo que seja de fato aquele socket. Percorra os descritores de arquivo de todo processo, cruze os inodes de volta com a tabela de sockets, e você tem um mapa processo-para-conexão sem pedir permissão a um único processo.
A partir daí é adição, não invenção: mapeie cada PID ao seu container (o caminho do cgroup ou a API do Docker te dão isso diretamente), e uma lista bruta de "processo 4821 tem uma conexão estabelecida com 10.0.4.12:5432" vira "o container de pedidos está falando com o container do postgres." Nenhum agente dentro do pedidos, nenhum agente dentro do postgres, nenhum caminho de código em nenhum dos dois ciente de que está sendo observado.
A parte que realmente exigiu iterações
Ler a tabela uma vez é o fácil 80%. O problema é que /proc/net/tcp é um snapshot: uma conexão que abre e fecha entre duas leituras simplesmente nunca aparece em nenhuma amostra. Um health check que dispara a cada 45 segundos contra um intervalo de leitura de 60 segundos, uma chamada de saída que completa em 200ms, um job em batch que conecta uma vez durante sua janela de cinco minutos e nunca mais — todos são dependências reais, e um diff ingênuo entre snapshots os perde constantemente. Num serviço movimentado, a maior parte do tráfego interessante é exatamente esse tipo de conexão de vida curta, o que é frustrante de descobrir depois que sua visão de topologia já te convenceu de que estava completa.
Nossa correção é uma janela de retenção deslizante em vez de um snapshot puro: uma aresta vista em qualquer um dos últimos cinco ciclos de leitura permanece no grafo, e só é removida depois de estar genuinamente ausente por toda aquela janela. Não é um algoritmo sofisticado — é mais perto de "lembre do que você viu por um pouco mais de tempo do que acha necessário" — mas é a diferença entre uma topologia que parece plausível e uma que está de fato certa.
Onde essa abordagem esbarra em limites
Vale ser direto sobre as bordas. Setups com um namespace por container (o caso comum em Docker e Kubernetes) significam que você está lendo a tabela de rede própria de cada container em vez de uma tabela compartilhada para o host inteiro, o que é mais contabilidade, mas não fundamentalmente mais difícil. Tráfego criptografado não esconde a conexão — metadado TCP é tudo que precisamos aqui, não o payload — mas significa que só sabemos que dois containers estão conversando, não sobre qual protocolo ou para qual endpoint; é para isso que serve a camada de métricas RED baseada em Beyla, e as duas foram pensadas para serem lidas juntas, não como substitutas uma da outra. E essa é fundamentalmente uma visão de camada TCP: protocolos baseados em UDP precisam de uma leitura completamente diferente, que está na lista, mas ainda não está no agente.
Quer ver isso no seu próprio sistema?
A URL de um repositório já basta para o grafo estático. O agente de runtime precisa de um único arquivo docker-compose.
