Nei precedenti articoli abbiamo visto cos’è l’observability e come strumentare un’applicazione .NET. In questo articolo faremo uno step ulteriore andando a trattare nel dettaglio l’OpenTelemetry Collector.
In particolare vedremo cos’è, perché utilizzarlo e come si configura.
Cos’è il Collector
L’OpenTelemetry Collector è un componente standalone che fa da strato intermedio tra le applicazioni ed i sistemi di observability, in particolare che si occupa di centralizzare la raccolta, il processamento e l’invio dei dati di telemetria.
Il Collector è organizzato in pipeline modulari, dove ogni pipeline gestisce un tipo specifico di segnale (traces, metrics, logs) attraverso tre componenti principali:
Immagine tratta dalla documentazione ufficiale di OpenTelemetry
- Receivers: ricevono dati da applicazioni, sistemi, infrastruttura, ecc. Supporta diversi protocolli di ricezione
- Processors: elaborano i dati ricevuti dai receivers e li filtrano, arricchiscono (ad esempio con nuovi attributi), trasformano e raggruppano
- Exporters: prendono i dati dai receivers e processors e li inviano ad uno o più backend di observability. Supporta diversi protocolli di invio.
é importante sottolineare che possiamo creare diversi flussi di gestione dei dati con la possibilità di inviare a vari backend di observability dei dati differenti. Per esempio potremmo volere un sistema di observability leggero in cui vedere a colpo d’occhio solo qualche dato ed un altro sistema con molti più dati con una retention differente.
Perché usarlo
I vantaggi nell’utilizzo del Collector sono molteplici ma comporta anche dei trade-off da considerare. Vediamo quattro dei principali vantaggi:
- può raccogliere le metriche infrastrutturali oltre a quelle dell’applicazione. Se ad esempio ho la mia app strumentata che esporta dati di telemetria potrei aggiungere anche le metriche relative all’host che ospita l’app per avere un quadro più completo
- disaccoppia la logica di business dell’applicazione da quella di observabilità, che viene demandata al Collector. Se volessi configurare diversamente l’esportazione o peggio se volessi aggiungere dei filtri/processing ai dati dovrei mettere mano all’applicazione, invece con il Collector lascerei invariata l’app
- riduce il rischio di perdita dei dati di telemetria. L’applicazione potrebbe esportare rapidamente i dati, lasciando al Collector il compito di gestire il batching e l’invio verso i backend in base alla capacità della rete o del sistema di telemetria. Lasciando invece il batch all’interno dell’app c’è il rischio che se l’app va in crash non riesca ad inviare i dati di telemetria (che diventano fondamentali per capire i motivi del crash) perché non è ancora arrivata a completare il batch
- supporta diversi protocolli di ricezione ed esportazione, quindi non dobbiamo preoccuparci se l’SDK che abbiamo usato per la nostra applicazione non supporta l’esportazione verso un certo sistema di observability, se ne occuperà il collector
Già con questi vantaggi capiamo che diventa fondamentale l’utilizzo di uno strato intermedio che si occupi di gestire i dati di telemetria. Ovviamente così facendo aggiungiamo un costo computazionale ed un single point of failure se non configurato correttamente, quindi diventa importante capire se e quando utilizzare il Collector. Se per esempio stiamo sperimentando o abbiamo una semplice applicazione con pochi dati ed un semplice backend di telemetria possiamo evitarlo, ma quando si ha un’applicazione in produzione a mio avviso è consigliato il Collector.
Configurazione e deploy
La configurazione del Collector avviene attraverso un file YAML di configurazione. Un esempio di file può essere:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
filter:
traces:
span:
- 'attributes["http.route"] == "/health"'
memory_limiter:
limit_mib: 128
exporters:
logging:
loglevel: debug
otlp:
endpoint: "backend.observability.com:4317"
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, filter, batch]
exporters: [logging, otlp]
Extensions: forniscono funzionalità ausiliarie come health check, profiling e monitoring del Collector stesso. Non partecipano direttamente al flusso dei dati ma sono essenziali per l’operatività in produzione.
Service: orchestra l’intero funzionamento del Collector definendo quali extensions abilitare e come costruire le pipeline di elaborazione per ogni tipo di segnale.
Le pipeline seguono sempre il flusso Receivers → Processors → Exporters, e ogni tipo di segnale (traces, metrics, logs) può avere la propria pipeline con configurazioni specifiche.
Questa flessibilità di configurazione permette di adattare il Collector a qualsiasi scenario, dalla semplice aggregazione locale fino a complesse architetture distribuite con multiple destinazioni e trasformazioni avanzate dei dati.
Per quanto riguarda il deploy può essere distribuito come container Docker, come eseguibile o come Operator per Kubernetes. Inoltre si possono avere diversi tipi di deploy, da quello locale sull’host a fianco dell’applicazione fino ad un servizio centralizzato e scalabile che raccoglie dati da diversi host ed applicazioni.
La scelta del pattern dipende dall’architettura dell’applicazione, dai volumi di dati e dai requisiti di resilienza. Nel prossimo articolo approfondiremo queste tipologie di deploy con esempi pratici e best practices per la produzione.