Por que não escrevemos nosso próprio sensor eBPF
O Beyla já resolve a parte difícil, chata e perpétua do rastreamento em nível de kernel. Reconstruí-lo teria nos custado a única coisa que não dá para recuperar.
23 de junho de 2026 · 6 min de leitura
O agente de runtime precisa saber a velocidade com que um serviço está respondendo e a frequência com que está falhando, sem tocar na aplicação. Essa é uma frase fácil de escrever num pitch deck. Extrair isso de um processo vivo sem SDK, sem sidecar, sem reiniciar nada é um problema bem diferente, e nas primeiras semanas de construção do agente assumimos que resolveríamos isso sozinhos.
O plano no papel era direto: uprobes em SSL_write/SSL_read para tráfego TLS, kprobes em tcp_sendmsg para o resto, parsing de framing HTTP e gRPC num programa eBPF pequeno, e envio de métricas RED (rate, errors, duration) sem que o processo-alvo soubesse que estava sendo observado. Tivemos uma prova de conceito funcionando contra um servidor HTTP simples em Go em menos de uma semana. Depois apontamos para um serviço Spring Boot real e tudo desmoronou.
Onde realmente quebra
Não no verificador do eBPF — essa parte é genuinamente bem projetada, e o kernel rejeita um programa que possa entrar em loop ou travar muito antes de ele rodar. Quebra na fronteira do userspace, na parte que ninguém coloca na palestra de conferência: offsets de uprobe são função da build exata da biblioteca. O offset de SSL_write para OpenSSL 3.0.2 no Ubuntu 22.04 não é o mesmo do OpenSSL 3.0.13 na mesma distro seis meses depois, é um número diferente de novo na build ligada ao musl do Alpine, e diferente outra vez quando você está dentro de uma JVM fazendo seu próprio TLS através de um provider embutido em vez do provider do sistema. Multiplique isso por cada runtime de linguagem, cada imagem base de container, cada versão minor que um cliente por acaso está rodando, e você tem uma matriz de offsets que exige rederivação constante e um rig de teste para kernels que você não controla.
Nada disso é um problema de pesquisa. É um problema de manutenção, para sempre, e não para depois que você faz o deploy — cada release de ponto do OpenSSL é uma regressão em potencial que você descobre por uma mensagem no Slack de um cliente, não pelo seu próprio CI.
Por que escolhemos o Beyla
O Beyla (projeto de instrumentação eBPF do Grafana Labs) já carrega esse fardo de manutenção, em código aberto, com uma comunidade de gente cujo trabalho em tempo integral é perseguir o drift de kernel e de biblioteca exatamente nos ambientes que, do contrário, estaríamos descobrindo um cliente de cada vez. Ele nos dá métricas RED por serviço — taxa de requisições, taxa de erro, duração p50/p99 — com zero código na aplicação-alvo. Isso nos deixa gastar nosso próprio orçamento de eBPF na parte que é realmente nossa: o procnet, nosso coletor que lê /proc/<pid>/net/tcp para mapear conexões TCP ao vivo em containers, uma superfície mais estreita e mais estável do que fazer parsing de protocolos L7 dentro do kernel.
O custo honesto dessa decisão é que hoje o agente é distribuído como dois containers em vez de um — o nosso, e o do Beyla, ambos com privilégios elevados no host. Isso é uma fricção que estamos ativamente reduzindo (empacotar o Beyla como um processo filho supervisionado pelo binário do agente é o próximo passo), mas é um problema de UX a resolver, não uma aposta de engenharia que precisamos vencer contra um alvo em movimento. Preferimos entregar essa fricção com honestidade a fingir que construímos algo que não construímos.
A regra com que ficamos
Construa o que é de fato o seu produto. Para nós isso é o grafo: a resolução de entidade que dá "o serviço de pedidos" a mesma identidade seja visto de um Dockerfile, de um container rodando, ou de uma conexão TCP, e a lógica de correlação que decide se uma dependência é real. eBPF na fronteira da ABI do kernel, em toda linguagem e toda imagem base de container que um cliente possa rodar, já é o trabalho em tempo integral de outra pessoa — e eles são bons nisso.
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.
