Aller au contenu
mcpd
aucun daemon

Relais et régions

Un collecteur à côté de vos agents, et le lien entre régions franchi avec de nouvelles tentatives.

Ce qu’est un relais

mcpd relay fait tourner le même binaire comme un collecteur qui ne stocke rien lui-même. Il accepte toutes les requêtes d’ingestion HTTP qu’accepte un daemon, écrit la requête dans un spool sur disque, répond à l’émetteur, puis expédie la requête aux daemons que nomme --upstream. Il n’ouvre aucun stockage.

Faites-en tourner un dans chaque région où tournent vos agents. Un émetteur parle alors à un collecteur situé à quelques millisecondes, et le lien entre régions est franchi par le relais, avec de nouvelles tentatives, plutôt que par chaque agent de son côté.

un relais en périphérie avec deux upstreams

mcpd relay \ --ingest-key $INGEST_KEY \ --upstream http://mcpd.eu.internal:4317 \ --upstream http://mcpd.us.internal:4317
relais
$ 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}

Ce qu’il répond

Tout POST, sur tout chemin autre que les sondes, est accepté. Les chemins OTLP /v1/traces, /v1/logs et /v1/metrics répondent 200 avec un partialSuccess OTLP, ce qu’attend un exporter OpenTelemetry. Tous les autres chemins répondent 202 avec l’en-tête mcpd-relay: spooled et le corps {"accepted":0,"duplicate":0,"rejected":0} : la requête est conservée et sera livrée, et les vrais décomptes reviennent à l’upstream, qui ne l’a pas encore vue. Tous les SDK lisent n’importe quel 2xx comme un succès.

Le relais vérifie --ingest-key lui-même, avant d’écrire quoi que ce soit, et applique --max-body-bytes et --request-timeout-seconds comme le fait un daemon. La requête est dans le spool, <spool-dir>/relay, avant l’envoi de la réponse.

Livraison et bascule

Quatre workers vident le spool. Chaque requête part vers le premier upstream, dans l’ordre de --upstream, qui n’est pas en pause. Un échec qui peut être réessayé met cet upstream en pause, avec backoff et jitter ou pour la durée qu’indique son Retry-After, et la requête part aussitôt vers l’upstream suivant. À la fin de la pause, le primaire est de nouveau essayé en premier : le trafic y revient de lui-même.

Un refus définitif, comme un 400, n’est pas envoyé à l’upstream suivant : celui-ci refuserait les mêmes octets. La requête est déplacée dans dead/ du répertoire de spool et comptée.

Pas de double comptage

La livraison se fait au moins une fois, et un renvoi n’est stocké qu’une fois. Chaque requête porte un en-tête Idempotency-Key : celui de l’émetteur s’il en a envoyé un, sinon relay- suivi d’un condensé du chemin, de l’encodage du contenu et du corps. Chaque porte d’ingestion du daemon respecte cet en-tête, et une clé présente dans le corps d’un lot natif l’emporte sur lui. Une requête que le relais renvoie après un délai dépassé, ou qu’il envoie au secondaire après que le primaire l’a prise et que la réponse s’est perdue, est reconnue et signalée comme doublon.

Clés

Avec --upstream-key, le relais présente cette clé à chaque upstream en jeton Bearer. Sans elle, l’en-tête Authorization ou x-mcpd-key de l’émetteur est gardé avec la requête mise en spool, dans un fichier que seul l’utilisateur du relais peut lire, puis transmis. La seconde forme conserve le projet et la portée de chaque émetteur tels que l’upstream les voit.

Contre-pression et disponibilité

Le spool est borné par --spool-max-bytes, 1 GiB pour le relais sauf réglage contraire. Quand il est plein, une requête est refusée avec un 503 et Retry-After: 5. Tous les SDK et les exporters OpenTelemetry réessaient dans ce cas : c’est l’émetteur qui garde les données, au lieu que le relais les jette.

/healthz répond toujours 200. /readyz répond :

  • 200 ok tant qu’un upstream est joignable ;
  • 200 degraded, avec un detail, quand aucun upstream n’a répondu à son /readyz ni pris une livraison dans les 30 dernières secondes. Les upstreams sont sondés toutes les 5 secondes ;
  • 503 quand le spool est plein, ou quand aucun upstream n’est joignable et que le spool est plein à 80 % au moins.

Un load balancer continue donc d’envoyer vers un relais qui met en tampon, et détourne le trafic d’un relais qui ne peut plus en prendre.

Métriques du relais

MétriqueTypeSignification
mcpd_relay_accepted_totalcompteurRequêtes écrites dans le spool
mcpd_relay_refused_full_totalcompteurRequêtes refusées avec un 503 parce que le spool était plein
mcpd_relay_failovers_totalcompteurRequêtes livrées à un autre upstream que le premier
mcpd_relay_dead_letter_totalcompteurRequêtes refusées pour de bon par un upstream, déplacées dans dead/
mcpd_relay_spool_entriesjaugeRequêtes en attente dans le spool
mcpd_relay_spool_bytesjaugeOctets contenus dans le spool
mcpd_relay_spool_oldest_secondsjaugeÂge de la plus ancienne requête en attente, ou 0
mcpd_relay_delivered_totalcompteurRequêtes acceptées par un upstream, avec les labels upstream et order
mcpd_relay_failed_attempts_totalcompteurTentatives qu’un upstream n’a pas acceptées, avec les mêmes labels
mcpd_relay_upstream_upjauge1 quand l’upstream a répondu dans les 30 dernières secondes, 0 sinon
mcpd_uptime_secondsjaugeSecondes écoulées depuis le démarrage de ce processus

Un mcpd_relay_failovers_total qui monte signifie que le primaire échoue et que le secondaire porte son trafic. mcpd_relay_spool_oldest_seconds mesure le retard de la livraison.

Plusieurs régions

Faites tourner plusieurs réplicas du daemon, dans une région ou plus, sur un même stockage Postgres ou ClickHouse, avec les exécutions dans Postgres (--runs-data postgres://...) ou désactivées : un stockage d’exécutions embarqué appartient à un seul processus. Le /readyz de chaque réplica indique déjà s’il peut joindre le stockage. Placez devant eux un relais dans chaque région, avec un --upstream qui liste d’abord le daemon le plus proche, puis un daemon d’une autre région.

Quatre réglages se comportent autrement dès qu’il y a plus d’un réplica :

  • Les clés d’idempotence sont conservées dans le stockage partagé : un renvoi qui arrive sur un autre réplica est donc toujours reconnu. Postgres enregistre la clé dans la même transaction que le lot.
  • Les plafonds de quota par minute sont comptés par réplica. Divisez le plafond voulu par le nombre de réplicas.
  • La notation en direct, l’échantillonnage en queue et les alertes des moniteurs tournent sur un seul réplica à la fois. Les réplicas se relaient grâce à un bail dans le stockage partagé : son détenteur le renouvelle à chaque passe, le libère à l’arrêt, et un autre réplica prend le relais en trois passes au plus s’il s’arrête. Chaque réplica évalue ses moniteurs et ses objectifs, donc chacun répond à l’API des moniteurs, et seul le détenteur poste le webhook. Configurez-les de la même façon sur chaque réplica.
  • L’analyse d’aspects tourne sur chaque réplica qui l’a configurée. Configurez-la sur un seul réplica.

Exemples de déploiement

deploy/docker/docker-compose.multi-region.yml démarre Postgres, deux daemons et un relais sur une seule machine : toute la topologie, sous une forme que vous pouvez essayer.

Le chart Helm accepte mode: relay. Trois fichiers de valeurs sous deploy/helm/examples/ décrivent chacun une région : region-core.yaml, region-standby.yaml et region-edge-relay.yaml, ce dernier pour une région qui ne fait tourner qu’un relais.

La suite