Vai al contenuto
mcpd
nessun daemon

Relay e regioni

Un collector accanto ai tuoi agenti, e il collegamento fra le regioni attraversato con i tentativi.

Che cos’è un relay

mcpd relay fa girare lo stesso binario come collector che non archivia niente. Accetta ogni richiesta HTTP di ingest che accetta un daemon, scrive la richiesta in uno spool su disco, risponde al mittente e poi spedisce la richiesta ai daemon indicati da --upstream. Non apre nessuno storage.

Fanne girare uno in ogni regione in cui girano i tuoi agenti. Un mittente parla allora con un collector a pochi millisecondi di distanza, e il collegamento fra le regioni lo attraversa il relay, con i tentativi, invece di ogni agente per conto suo.

un relay di bordo con due upstream

mcpd relay \ --ingest-key $INGEST_KEY \ --upstream http://mcpd.eu.internal:4317 \ --upstream http://mcpd.us.internal:4317
relay
$ MCPD_UPSTREAMS=http://mcpd.eu.internal:4317,http://mcpd.us.internal:4317 mcpd --data /data/mcpd.db config | grep ^relayrelay           2 upstream(s) in order, http://mcpd.eu.internal:4317, http://mcpd.us.internal:4317; each sender's key passed on; spool at /data/mcpd.spool/relay, 1024 MiB$ curl -s -D - -o /dev/null localhost:4317/v1/ingest -H "content-type: application/json" -d @batch.json | grep -e ^HTTP -e mcpd-relayHTTP/1.1 202 Acceptedmcpd-relay: spooled$ curl -s localhost:4317/v1/ingest -H "content-type: application/json" -d @batch.json{"accepted":0,"duplicate":0,"rejected":0}

Che cosa risponde

Qualunque POST, su qualunque percorso diverso dalle sonde, viene accettato. I percorsi OTLP /v1/traces, /v1/logs e /v1/metrics rispondono 200 con un partialSuccess OTLP, che è quello che si aspetta un exporter OpenTelemetry. Ogni altro percorso risponde 202 con l’header mcpd-relay: spooled e il corpo {"accepted":0,"duplicate":0,"rejected":0}: la richiesta è trattenuta e verrà consegnata, e i conteggi veri spettano all’upstream, che non l’ha ancora vista. Ogni SDK legge qualunque 2xx come successo.

Il relay controlla --ingest-key da sé, prima di scrivere qualunque cosa, e applica --max-body-bytes e --request-timeout-seconds come fa un daemon. La richiesta è nello spool, <spool-dir>/relay, prima che parta la risposta.

Consegna e failover

Quattro worker spediscono lo spool. Ogni richiesta va al primo upstream, nell’ordine di --upstream, che non sta aspettando. Un errore che si può ritentare mette in attesa quell’upstream, con backoff e jitter o per tutto il tempo indicato dal suo Retry-After, e la richiesta passa subito all’upstream successivo. Quando l’attesa finisce, il primario torna a essere provato per primo, così il traffico ci ritorna da solo.

Un rifiuto definitivo, come un 400, non viene mandato all’upstream successivo: rifiuterebbe gli stessi byte. La richiesta viene spostata in dead/ nella directory di spool e contata.

Nessun conteggio doppio

La consegna è almeno una volta, e un nuovo invio viene archiviato una volta sola. Ogni richiesta porta un header Idempotency-Key: quello del mittente quando ne ha mandato uno, altrimenti relay- seguito da un digest del percorso, della codifica del contenuto e del corpo. Ogni porta di ingest del daemon rispetta l’header, e una chiave nel corpo di un batch nativo ha la precedenza su di esso. Una richiesta che il relay rimanda dopo un timeout, o che manda al secondario dopo che il primario l’ha presa e la risposta è andata persa, viene riconosciuta e riportata come duplicato.

Chiavi

Con --upstream-key, il relay presenta quella chiave a ogni upstream come token Bearer. Senza, l’header Authorization o x-mcpd-key del mittente viene conservato con la richiesta nello spool, in un file che solo l’utente del relay può leggere, e passato avanti. La seconda forma mantiene il progetto e l’ambito di ogni mittente così come li vede l’upstream.

Contropressione e prontezza

Lo spool è limitato da --spool-max-bytes, 1 GiB per il relay se non lo imposti. Quando è pieno, una richiesta viene rifiutata con un 503 e Retry-After: 5. Ogni SDK ed exporter OpenTelemetry lo ritenta, così i dati li tiene il mittente invece di essere scartati dal relay.

/healthz risponde sempre 200. /readyz risponde:

  • 200 ok finché un upstream è raggiungibile;
  • 200 degraded, con un detail, quando nessun upstream ha risposto al suo /readyz o ha preso una consegna negli ultimi 30 secondi. Gli upstream vengono sondati ogni 5 secondi;
  • 503 quando lo spool è pieno, o quando nessun upstream è raggiungibile e lo spool è pieno almeno all’80%.

Un load balancer quindi continua a mandare traffico a un relay che sta accumulando, e lo sposta via da uno che non può più prenderlo.

Metriche del relay

MetricaTipoSignificato
mcpd_relay_accepted_totalcounterRichieste scritte nello spool
mcpd_relay_refused_full_totalcounterRichieste rifiutate con un 503 perché lo spool era pieno
mcpd_relay_failovers_totalcounterRichieste consegnate a un upstream diverso dal primo
mcpd_relay_dead_letter_totalcounterRichieste che un upstream ha rifiutato in modo definitivo, spostate in dead/
mcpd_relay_spool_entriesgaugeRichieste in attesa nello spool
mcpd_relay_spool_bytesgaugeByte contenuti nello spool
mcpd_relay_spool_oldest_secondsgaugeEtà della richiesta in attesa più vecchia, oppure 0
mcpd_relay_delivered_totalcounterRichieste accettate da un upstream, con le etichette upstream e order
mcpd_relay_failed_attempts_totalcounterTentativi che un upstream non ha accettato, con le stesse etichette
mcpd_relay_upstream_upgauge1 quando l’upstream ha risposto negli ultimi 30 secondi, altrimenti 0
mcpd_uptime_secondsgaugeSecondi dall’avvio di questo processo

Un mcpd_relay_failovers_total che sale vuol dire che il primario sta fallendo e che il secondario porta il suo traffico. mcpd_relay_spool_oldest_seconds dice quanto è indietro la consegna.

Più regioni

Fai girare più repliche del daemon, in una regione o in più regioni, su un solo storage Postgres o ClickHouse, con le esecuzioni in Postgres (--runs-data postgres://...) o spente: uno storage delle esecuzioni incorporato appartiene a un solo processo. Il /readyz di ogni replica riflette già se raggiunge lo storage. Metti davanti a loro un relay in ogni regione, con --upstream che elenca prima il daemon più vicino e poi un daemon in un’altra regione.

Quattro impostazioni si comportano in modo diverso quando c’è più di una replica:

  • Le chiavi di idempotenza stanno nello storage condiviso, quindi un nuovo invio che arriva su un’altra replica viene comunque riconosciuto. Postgres registra la chiave nella stessa transazione del batch.
  • I tetti di quota al minuto sono contati per replica. Dividi il tetto che vuoi per il numero di repliche.
  • Il punteggio dal vivo, il campionamento di coda e gli alert dei monitor girano su una replica alla volta. Le repliche fanno a turno attraverso un lease nello storage condiviso: chi lo tiene lo rinnova a ogni passata, lo lascia quando si spegne, e un’altra replica subentra entro tre passate se si ferma. Ogni replica valuta i suoi monitor e obiettivi, quindi ognuna risponde all’API dei monitor, e solo chi tiene il lease manda il webhook. Configurali allo stesso modo su ogni replica.
  • L’analisi degli aspetti gira su ogni replica che la ha configurata. Configurala su una replica.

Deploy di esempio

deploy/docker/docker-compose.multi-region.yml avvia Postgres, due daemon e un relay su una sola macchina: tutta la topologia in una forma che puoi provare.

Il chart Helm accetta mode: relay. Tre file di valori sotto deploy/helm/examples/ descrivono una regione ciascuno: region-core.yaml, region-standby.yaml e region-edge-relay.yaml, l’ultimo per una regione che fa girare solo un relay.

Avanti