Relé y regiones
Un colector junto a tus agentes, y el enlace entre regiones recorrido con reintentos.
Qué es un relé
mcpd relay ejecuta el mismo binario como un colector que no
almacena nada por sí mismo. Acepta todas las solicitudes HTTP de ingesta que
acepta un daemon, escribe cada solicitud en un spool en disco, responde al
emisor y después envía la solicitud a los daemons que nombra
--upstream. No abre ningún almacén.
Ejecuta uno en cada región donde corren tus agentes. Así un emisor habla con un colector a pocos milisegundos, y el enlace entre regiones lo recorre el relé, con reintentos, en lugar de cada agente por su cuenta.
un relé en el borde con dos upstreams
mcpd relay \
--ingest-key $INGEST_KEY \
--upstream http://mcpd.eu.internal:4317 \
--upstream http://mcpd.us.internal:4317 $ 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} Qué responde
Acepta cualquier POST, en cualquier ruta que no sea una sonda. Las rutas
OTLP /v1/traces, /v1/logs y
/v1/metrics responden 200 con un partialSuccess
de OTLP, que es lo que espera un exportador de OpenTelemetry. El resto de
rutas responden 202 con la cabecera mcpd-relay: spooled y el
cuerpo {"accepted":0,"duplicate":0,"rejected":0}: la
solicitud está guardada y se entregará, y los recuentos reales son cosa del
upstream, que todavía no la ha visto. Todos los SDK leen cualquier 2xx como
éxito.
El relé comprueba --ingest-key por sí mismo, antes de escribir
nada, y aplica --max-body-bytes y
--request-timeout-seconds como lo hace un daemon. La solicitud
ya está en el spool, <spool-dir>/relay, antes de enviar
la respuesta.
Entrega y conmutación por error
Cuatro workers vacían el spool. Cada solicitud va al primer upstream, en el
orden de --upstream, que no esté en pausa. Un fallo que admite
reintento pone ese upstream en pausa, con espera exponencial y jitter o
durante el tiempo que indique su Retry-After, y la solicitud va
al siguiente upstream en el acto. Cuando acaba la pausa, se vuelve a probar
primero el principal, así que el tráfico vuelve a él por sí solo.
Un rechazo definitivo, como un 400, no se envía al siguiente upstream: el
siguiente rechazaría los mismos bytes. La solicitud se mueve a
dead/ dentro del directorio de spool y se cuenta.
Sin contar nada dos veces
La entrega es al menos una vez, y un reenvío se almacena una sola vez. Cada
solicitud lleva una cabecera Idempotency-Key: la del emisor si
mandó una y, si no, relay- seguido de un resumen de la ruta, la
codificación del contenido y el cuerpo. Todas las puertas de ingesta del
daemon respetan la cabecera, y una clave dentro del cuerpo de un lote nativo
tiene prioridad sobre ella. Una solicitud que el relé vuelve a enviar tras
agotar el tiempo de espera, o que envía al secundario después de que el
principal la aceptara y se perdiera la respuesta, se reconoce y se informa
como duplicada.
Claves
Con --upstream-key, el relé presenta esa clave a cada upstream
como token Bearer. Sin ella, la cabecera Authorization o
x-mcpd-key del emisor se guarda con la solicitud en el spool,
en un archivo que solo puede leer el usuario del relé, y se pasa tal cual.
La segunda forma conserva el proyecto y el ámbito de cada emisor tal como
los ve el upstream.
Contrapresión y disponibilidad
El spool está limitado por --spool-max-bytes, 1 GiB para el
relé salvo que lo cambies. Cuando está lleno, una solicitud se rechaza con
un 503 y Retry-After: 5. Todos los SDK y exportadores de
OpenTelemetry lo reintentan, así que el emisor conserva los datos en lugar
de que el relé los descarte.
/healthz responde siempre 200. /readyz responde:
- 200
okmientras algún upstream esté alcanzable; -
200
degraded, con undetail, cuando ningún upstream ha respondido a su/readyzni aceptado una entrega en los últimos 30 segundos. Los upstreams se sondean cada 5 segundos; - 503 cuando el spool está lleno, o cuando ningún upstream está alcanzable y el spool está lleno al menos al 80%.
Por eso un balanceador de carga sigue enviando a un relé que está acumulando en el spool, y aparta el tráfico de uno que ya no puede aceptarlo.
Métricas del relé
| Métrica | Tipo | Significado |
|---|---|---|
mcpd_relay_accepted_total | counter | Solicitudes escritas en el spool |
mcpd_relay_refused_full_total | counter | Solicitudes rechazadas con un 503 porque el spool estaba lleno |
mcpd_relay_failovers_total | counter | Solicitudes entregadas a un upstream que no es el primero |
mcpd_relay_dead_letter_total | counter | Solicitudes que un upstream rechazó de forma definitiva, movidas a dead/ |
mcpd_relay_spool_entries | gauge | Solicitudes que esperan en el spool |
mcpd_relay_spool_bytes | gauge | Bytes que guarda el spool |
mcpd_relay_spool_oldest_seconds | gauge | Antigüedad de la solicitud en espera más vieja, o 0 |
mcpd_relay_delivered_total | counter | Solicitudes que aceptó un upstream, con las etiquetas upstream y order |
mcpd_relay_failed_attempts_total | counter | Intentos que un upstream no aceptó, con las mismas etiquetas |
mcpd_relay_upstream_up | gauge | 1 cuando el upstream respondió en los últimos 30 segundos, 0 en otro caso |
mcpd_uptime_seconds | gauge | Segundos desde que arrancó este proceso |
Un mcpd_relay_failovers_total que sube significa que el
principal está fallando y el secundario lleva su tráfico.
mcpd_relay_spool_oldest_seconds indica cuánto retraso lleva la
entrega.
Varias regiones
Ejecuta varias réplicas del daemon, en una región o en varias, contra un
mismo almacén Postgres o ClickHouse, con las ejecuciones en Postgres
(--runs-data postgres://...) o desactivadas: un almacén de
ejecuciones embebido pertenece a un solo proceso.
El /readyz de cada réplica ya refleja si puede alcanzar el
almacén. Pon un relé delante de ellas en cada región, con
--upstream nombrando primero el daemon más cercano y en segundo
lugar un daemon de otra región.
Cuatro ajustes se comportan de otra forma en cuanto hay más de una réplica:
- Las claves de idempotencia se guardan en el almacén compartido, así que un reenvío que cae en otra réplica se sigue reconociendo. Postgres registra la clave en la misma transacción que el lote.
- Los topes de cuota por minuto se cuentan por réplica. Divide el tope que quieres entre el número de réplicas.
- La puntuación en vivo, el muestreo de cola y las alertas de los monitores se ejecutan en una sola réplica a la vez. Las réplicas se turnan mediante un lease en el almacén compartido: quien lo tiene lo renueva en cada pasada, lo suelta al apagarse, y otra réplica lo toma en tres pasadas como mucho si se detiene. Cada réplica evalúa sus monitores y objetivos, así que todas responden a la API de monitores, y solo quien tiene el lease envía el webhook. Configúralos igual en todas las réplicas.
- El análisis de aspectos se ejecuta en cada réplica que lo tiene configurado. Configúralo en una sola réplica.
Despliegues de ejemplo
deploy/docker/docker-compose.multi-region.yml arranca
Postgres, dos daemons y un relé en una sola máquina, que es la topología
completa en una forma que puedes probar.
El chart de Helm acepta mode: relay. Tres archivos de valores
en deploy/helm/examples/ describen una región cada uno:
region-core.yaml, region-standby.yaml y
region-edge-relay.yaml, este último para una región que solo
ejecuta un relé.
A continuación
- Copiar datos a otros proveedores: Reenvío.
- Sondas, métricas y almacenes: Operación.
- Cada flag: Configuración.