Mapeando dependencias entre servicios desde /proc, sin SDK de agente
No hace falta instrumentar nada para saber qué servicio habla con cuál. El kernel ya lo tiene anotado.
10 de julio de 2026 · 8 min de lectura
"Agregá un agente a cada servicio" es la respuesta que da la mayoría de las herramientas de observabilidad para el descubrimiento de servicios, y es una respuesta razonable si estás dispuesto a tocar cada manifiesto de deployment, reiniciar cada proceso, y mantener la versión del SDK sincronizada entre una docena de servicios que son de gente que no te reporta a vos. Es una respuesta bastante menos razonable si estás tratando de mapear un sistema en los primeros cinco minutos de usar un producto, antes de que nadie haya aceptado cambiar una sola línea de config.
El kernel ya sabe qué sockets están abiertos y quién es su dueño. Solo hay que ir a leerlo.
Lo que realmente hay en /proc
Todo proceso en Linux tiene un directorio bajo /proc/<pid>, y adentro, net/tcp (y net/tcp6 para IPv6) lista cada socket TCP visible en el namespace de red de ese proceso: dirección local, dirección remota, estado de la conexión, y — el campo que importa acá — un número de inodo. Ese inodo es el mismo número que vas a encontrar en /proc/<pid>/fd/ como destino de un symlink para cualquier file descriptor que efectivamente sea ese socket. Recorré los file descriptors de cada proceso, hacé match de los inodos contra la tabla de sockets, y tenés un mapa de proceso a conexión sin pedirle permiso a un solo proceso.
De ahí en más es suma, no innovación: mapeá cada PID a su contenedor (la ruta del cgroup o la API de Docker te lo dice directamente), y una lista cruda de "el proceso 4821 tiene una conexión establecida a 10.0.4.12:5432" se convierte en "el contenedor de orders está hablando con el contenedor de postgres". Sin agente adentro de orders, sin agente adentro de postgres, sin ningún camino de código en ninguno de los dos que sepa que lo están mirando.
La parte que en realidad llevó las iteraciones
Leer la tabla una vez es el 80% fácil. El problema es que /proc/net/tcp es una foto: una conexión que se abre y se cierra entre dos lecturas simplemente no aparece nunca en ninguna de las dos muestras. Un health check que se dispara cada 45 segundos contra un intervalo de polling de 60 segundos, una llamada saliente que se completa en 200ms, un batch job que se conecta una vez durante tu ventana de cinco minutos y nunca más — todas son dependencias reales, y un diff de fotos ingenuo se las pierde constantemente. En un servicio con mucho tráfico, la mayor parte del tráfico interesante es exactamente este tipo de conexión de vida corta, lo cual es frustrante de descubrir después de que tu vista de topología ya te convenció de que estaba completa.
Nuestra solución es una ventana de retención deslizante en vez de una foto pelada: una arista vista en cualquiera de los últimos cinco ciclos de polling se queda en el grafo, y solo se elimina después de haber estado genuinamente ausente durante todo ese lapso. No es un algoritmo genial — es más parecido a "acordate de lo que viste un rato más de lo que pensás que necesitás" — pero es la diferencia entre una topología que parece plausible y una que efectivamente es correcta.
Dónde se termina este enfoque
Vale la pena ser directo sobre los límites. Los setups con un namespace por contenedor (el caso común en Docker y Kubernetes) implican que estás leyendo el namespace de red propio de cada contenedor en vez de una tabla compartida para el host, lo cual es más contabilidad pero no fundamentalmente más difícil. El tráfico encriptado no esconde la conexión — acá solo necesitamos los metadatos de TCP, no el payload — pero sí significa que solo sabemos que dos contenedores están hablando, no bajo qué protocolo ni hacia qué endpoint; para eso está la capa de métricas RED basada en Beyla, y las dos están pensadas para leerse juntas y no como sustitutas una de la otra. Y esto es fundamentalmente una vista de la capa TCP: los protocolos basados en UDP necesitan una lectura completamente distinta, que está en la lista, pero todavía no en el agente.
¿Querés verlo en tu propio sistema?
Con la URL de un repositorio alcanza para el grafo estático. El agente de runtime necesita un solo archivo docker-compose.
