ArchyntArchynt.api
Torna al blogIngegneria

Mappare le dipendenze tra servizi da /proc, senza SDK agente

Non serve strumentare nulla per sapere quale servizio parla con quale. Il kernel lo ha già annotato.

10 luglio 2026 · 8 min di lettura

"Aggiungi un agente a ogni servizio" è la risposta che la maggior parte degli strumenti di osservabilità dà per il service discovery, ed è una risposta ragionevole se sei disposto a toccare ogni manifest di deployment, riavviare ogni processo e mantenere la versione dell'SDK sincronizzata tra una dozzina di servizi di proprietà di persone che non rispondono a te. È una risposta molto meno ragionevole se stai cercando di mappare un sistema nei primi cinque minuti di utilizzo di un prodotto, prima che qualcuno abbia accettato di cambiare anche solo una riga di configurazione.

Il kernel sa già quali socket sono aperti e a chi appartengono. Devi solo andare a leggerlo.

Cosa c'è davvero in /proc

Ogni processo su Linux ha una directory sotto /proc/<pid>, e al suo interno, net/tcp (e net/tcp6 per IPv6) elenca ogni socket TCP visibile nel namespace di rete di quel processo: indirizzo locale, indirizzo remoto, stato della connessione e — il campo che conta qui — un numero di inode. Quell'inode è lo stesso numero che troverai in /proc/<pid>/fd/ come destinazione di un symlink per qualsiasi file descriptor che sia effettivamente quel socket. Percorri i file descriptor di ogni processo, fai corrispondere gli inode alla tabella dei socket, e ottieni una mappa processo-connessione senza chiedere il permesso a un solo processo.

Da lì in poi è addizione, non innovazione: mappa ogni PID al suo container (il percorso cgroup o l'API Docker te lo dice direttamente), e una lista grezza del tipo "il processo 4821 ha una connessione stabilita verso 10.0.4.12:5432" diventa "il container orders sta parlando con il container postgres". Nessun agente dentro orders, nessun agente dentro postgres, nessun percorso di codice in nessuno dei due consapevole di essere osservato.

La parte che ha davvero richiesto le iterazioni

Leggere la tabella una volta è l'80% facile. Il problema è che /proc/net/tcp è un'istantanea: una connessione che si apre e si chiude tra due polling semplicemente non compare mai in nessuno dei due campioni. Un health check che scatta ogni 45 secondi contro un intervallo di polling di 60 secondi, una chiamata in uscita che si completa in 200ms, un batch job che si connette una volta durante la tua finestra di cinque minuti e mai più — sono tutte dipendenze reali, e un diff ingenuo tra istantanee se le perde costantemente. Su un servizio molto trafficato, la maggior parte del traffico interessante è esattamente questo tipo di connessione di breve durata, il che è frustrante da scoprire dopo che la tua vista della topologia ti ha già convinto di essere completa.

La nostra soluzione è una finestra di retention scorrevole invece di una nuda istantanea: un arco visto in uno qualsiasi degli ultimi cinque cicli di polling resta nel grafo, e viene rimosso solo dopo essere stato genuinamente assente per l'intero arco di tempo. Non è un algoritmo geniale — è più simile a "ricorda quello che hai visto un po' più a lungo di quanto pensi ti serva" — ma è la differenza tra una topologia che sembra plausibile e una che è effettivamente corretta.

Dove questo approccio arriva al limite

Vale la pena essere chiari sui limiti. I setup con un namespace per container (il caso comune in Docker e Kubernetes) implicano che stai leggendo il namespace di rete proprio di ogni container invece di un'unica tabella condivisa per l'host, il che comporta più lavoro contabile ma non è fondamentalmente più difficile. Il traffico criptato non nasconde la connessione — qui ci servono solo i metadati TCP, non il payload — ma significa che sappiamo solo che due container stanno parlando, non su quale protocollo o verso quale endpoint; è a questo che serve il livello di metriche RED basato su Beyla, e i due sono pensati per essere letti insieme, non come sostituti l'uno dell'altro. E questa è fondamentalmente una vista a livello TCP: i protocolli basati su UDP richiedono una lettura completamente diversa, che è in lista ma non ancora nell'agente.

Vuoi vederlo sul tuo sistema?

Basta l'URL di un repository per il grafo statico. L'agente runtime richiede un solo file docker-compose.