Files
message_bus/reports/0016-2026-06-07-unibus-nats-metrics-clean.md
T
egutierrez d43ffae3ae chore: auto-commit (17 archivos)
- reports/0001-2026-06-07-unibus-grafana-monitoring.md
- reports/0008-2026-06-07-unibus-admin-users-wired.md
- reports/0008-2026-06-07-unibus-decentralization-audit.md
- reports/0009-2026-06-07-unibus-cluster-hardening.md
- reports/0010-2026-06-07-unibus-android-native.md
- reports/0011-2026-06-07-unibus-cluster-deploy.md
- reports/0012-2026-06-07-unibus-deploy-gaps-closed.md
- reports/0013-2026-06-07-unibus-admin-panel.md
- reports/0014-2026-06-07-unibus-users-http-admin-api.md
- reports/0015-2026-06-07-unibus-web-wired.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 01:57:00 +02:00

12 KiB
Raw Blame History

Report 0016 — unibus: métricas profundas de NATS/JetStream (limpio, sin debug-log)

  • Fecha: 07/06/2026
  • Autor: Claude (agente NATS metrics)
  • Ámbito: projects/message_bus/apps/unibus (flag), functions/infra (parser del registry), projects/fleet_monitoring/apps/unibus_exporter (scraper), projects/fleet_monitoring/hub (dashboard + deploy)
  • Estado: done (código + dashboard listos y probados; rollout al cluster pendiente, por diseño)

Resumen

Se añadieron las métricas server-level de NATS/JetStream (msgs/s, conexiones, slow consumers, memoria, KV bucket msgs, RAFT leader por stream, reinicios) a la monitorización del cluster unibus, de forma limpia: desacoplando el endpoint de monitoring del nats-server (puerto 8222) del debug-log verboso al que estaba acoplado.

El problema original: el monitoring 8222 solo se abría con UNIBUS_NATS_DEBUG=1, que enciende el log verboso del nats-server (rutas/RAFT/subjects de rooms a journald en claro), incompatible con el endurecimiento del issue 0007. La causa era únicamente cómo estaba escrito el toggle: monitoring y debug-log estaban acoplados sin necesidad.

La solución es un flag dedicado UNIBUS_NATS_MONITOR=1 que abre el monitoring loopback sin tocar el nivel de log, un scraper local por nodo (modo nuevo del unibus_exporter) que lee /varz, /connz, /jsz por loopback y empuja a VictoriaMetrics reusando las funciones del grupo fleet-metrics, y un dashboard Grafana unibus-nats. No se reinició el cluster (eso es el rollout consolidado del binario 0.11.0).

Cambios

Parte 1 — flag desacoplado en unibus (rama quick/nats-monitor-flag, pusheada)

Repo: dataforge/unibus. Commit 1c93251.

Archivo Cambio
pkg/embeddednats/embeddednats.go Función pura nueva natsLogOpts(debugEnv, monitorEnv) (noLog, debug, trace, monitor); StartServer la usa. El monitoring se abre con monitor (no con debug).
pkg/embeddednats/monitor_test.go Tests del desacoplamiento (tabla pura) + server real con MONITOR=1 (8222 loopback) + server sin flag (cerrado).
deploy/cluster/membershipd-cluster.service.d/nats-monitor.conf Drop-in systemd aditivo (Environment=UNIBUS_NATS_MONITOR=1).
deploy/cluster/README.md Sección "NATS server metrics" con runbook de activación rolling + gate R3.
app.md Bump 0.10.0 → 0.11.0 + growth log.

Diff esencial (lógica):

// natsLogOpts: pura, testeable. MONITOR=1 abre el endpoint dejando el log en silencio.
func natsLogOpts(debugEnv, monitorEnv string) (noLog, debug, trace, monitor bool) {
	debug = debugEnv == "1" || debugEnv == "2"
	trace = debugEnv == "2"
	monitor = monitorEnv == "1" || debug   // debug sigue implicando monitor (compat)
	noLog = !debug
	return noLog, debug, trace, monitor
}
// ...
opts.NoLog, opts.Debug, opts.Trace = noLog, debugNATS, traceNATS
if monitorNATS {
	opts.HTTPHost = "127.0.0.1"   // loopback hardcoded — el monitoring NUNCA es público
	opts.HTTPPort = 8222
}

El bind 127.0.0.1 se mantiene hardcoded: el endpoint no tiene auth y nunca debe ser alcanzable por red. El || debugNATS preserva el comportamiento viejo (DEBUG sigue abriendo el monitoring).

Función nueva del registry — parse_nats_monitor_go_infra

Creada vía fn-constructor en el repo padre (functions/infra/). Análoga a parse_unibus_health, mismo paquete infra, reusa el tipo PromSample.

func ParseNatsMonitor(node string, varz, connz, jsz []byte) ([]PromSample, error)

Convierte /varz + /connz + /jsz?streams=1 en []PromSample con labels node/instance. Robusta: /varz es el core (error si no parsea); /connz y /jsz best-effort. Series: ver tabla del dashboard abajo. Tag de grupo fleet-metrics (+ nats, jetstream, monitoring).

Parte 2a — modo NATS-local en unibus_exporter (rama quick/nats-deep-metrics, commit local)

Repo: dataforge/unibus_exporter (sub-repo sin remote aún). Commit dcdae92.

Segundo modo de scrape, componible con el modo healthz existente:

Archivo Cambio
config.go Bloque nats_monitor {enabled, node, base_url} + overrides UNIBUS_NODE/UNIBUS_NATS_URL; validación relajada (healthz, NATS o ambos).
main.go scrapeNATS() lee loopback 8222 (/varz,/connz,/jsz?streams=1), llama ParseNatsMonitor, reusa FormatPromExposition + PushPromRemote. getJSON genérico.
nats_mode_test.go scrapeNATS contra fixtures reales + varz caído + validación de config.
testdata/nats_{varz,connz,jsz}.json Fixtures reales capturados de un nats-server 2.11.15.
systemd/unibus-exporter-nats.service Unit del modo NATS-local (por nodo).
unibus.nats.example.json Config de ejemplo nats-only.
app.md Bump 0.1.0 → 0.2.0 + tabla de métricas NATS + growth log.

El modo healthz no se tocó. Como el monitoring de NATS es loopback-only y sin auth, este modo corre en cada nodo (un exporter local por nodo), distinto del exporter healthz central de magnus.

Parte 2b — dashboard + deploy (rama quick/nats-deep-metrics, pusheada)

Repo: dataforge/fleet_monitoring. Commit dfd55dc.

Archivo Cambio
hub/dashboards/unibus-nats.json Dashboard unibus-nats (uid), datasource victoriametrics, 12 paneles. Lo recoge el provider fleet existente (escanea el path de dashboards).
hub/deploy_unibus_nats_exporter.sh Deploy del exporter en modo NATS-local por nodo. magnus → VM local sin auth; homer/datardos → ingesta pública con basic auth (pass fleet/ingest-pass, nunca en argv). Instala unibus-exporter-nats.service y sondea 8222 para avisar si el monitoring no está abierto. NO reinicia membershipd.

Verificación (evidencia ejecutable)

P1 — flag (unibus, CGO_ENABLED=0)

$ go build ./...                       → BUILD_EXIT=0
$ go test ./pkg/embeddednats/ -run 'TestNatsLogOpts|TestMonitor' -v
--- PASS: TestNatsLogOptsDecoupled (7 subcases: default off, monitor-only ⇒ log quiet,
          debug⇒monitor, trace⇒debug+monitor, both, garbage ignorado ×2)
--- PASS: TestMonitorEndpointLoopback   (server real MONITOR=1: /varz 200 en 127.0.0.1:8222)
--- PASS: TestMonitorDisabledByDefault  (sin flag: MonitorAddr nil, endpoint cerrado)
$ go test ./pkg/embeddednats/          → ok  5.953s  (suite completa, cluster_test incluido)

Función del registry (CGO_ENABLED=1 -tags fts5)

$ go test ./functions/infra/ -run 'ParseNatsMonitor|Battery'   → ok  0.004s
  Golden verifica: nats_msgs_in_total=17, nats_msgs_out_total=17, nats_mem_bytes=18288640,
  nats_jetstream_streams=3, kv_bucket_msgs{UNIBUS_users|rooms|members}=2,
  nats_jetstream_raft_leader{KV_UNIBUS_users}=1.
$ ./fn index    → Indexed 1451 functions (sin error en la función)
$ ./fn show parse_nats_monitor_go_infra → indexada OK

P2a — exporter (CGO_ENABLED=0)

$ go build ./...   → BUILD_OK
$ go vet ./...     → VET_OK
$ go test ./...    → ok  0.005s
  TestScrapeNATSFromFixtures, TestScrapeNATSVarzDown, TestConfigNatsModeValidation

E2E real — scraper contra un nats-server embebido vivo

Se levantó un nats-server embebido con el binario 0.11.0 real (UNIBUS_NATS_MONITOR=1, 8222 abierto, 3 KV buckets creados), se corrió el exporter en modo NATS-local apuntando a 127.0.0.1:8222 y empujando a un VictoriaMetrics simulado (receptor HTTP local). El exporter empujó 29 series reales, recibidas y verificadas:

unibus_exporter starting: nodes=0 nats="http://127.0.0.1:8222 (node=probe)" ...
pushed 29 samples (nodes=0 nats=true)

# series recibidas en el VM simulado (extracto):
nats_msgs_in_total{instance="probe",node="probe"} 17
nats_msgs_out_total{instance="probe",node="probe"} 17
nats_connections{instance="probe",node="probe"} 1
nats_mem_bytes{instance="probe",node="probe"} 1.855488e+07
nats_slow_consumers{...} 0
kv_bucket_msgs{bucket="UNIBUS_users",...} 2
kv_bucket_msgs{bucket="UNIBUS_rooms",...} 2
kv_bucket_msgs{bucket="UNIBUS_members",...} 2
nats_jetstream_raft_leader{stream="KV_UNIBUS_users",...} 1
nats_jetstream_streams{...} 3
nats_server_start_seconds{...} 1.78085962e+09
nats_up{...} 1   nats_scrape_error{...} 0

Esto valida toda la cadena: nats real → scrape loopback 8222 → ParseNatsMonitor → formato Prometheus → push a VictoriaMetrics. El harness y los temporales se limpiaron tras la prueba.

Dashboard

$ python3 -c "json.load(...)" → OK uid=unibus-nats title='unibus — NATS server' panels=12

Paneles: NATS up · conexiones · msgs/s in · slow consumers · JetStream msgs · reinicios (1h); msgs/s por nodo (in/out) · conexiones por nodo · KV bucket msgs por bucket · memoria por nodo; tabla "Leader RAFT por stream" · tabla "JetStream por nodo".

Runbook de activación (consolidada, HUMANO/orquestador — tras el rollout 0.11.0)

El monitoring requiere reiniciar membershipd (reinicia el miembro RAFT del nodo). Se hace dentro del rollout consolidado del binario 0.11.0, nunca dos nodos abajo a la vez.

  1. Dependencia previa (registry): commitear la función nueva al registry y push directo a master (es aditivo, exento de TBD): functions/infra/parse_nats_monitor.{go,md,_test.go} + functions/infra/testdata/nats_*.json. Sin esto el exporter no compila en otro entorno.
  2. Rollout del binario 0.11.0 a los 3 nodos en orden magnus → homer → datardos, instalando en cada uno el drop-in nats-monitor.conf + daemon-reload + restart membershipd-cluster. Entre cada nodo, esperar reconvergencia R3 (followers_current=2/2 en los 6 KV buckets) y healthz verde en el nodo reiniciado antes de pasar al siguiente (comandos en deploy/cluster/README.md § "NATS server metrics").
  3. Desplegar el scraper por nodo (una vez que su 8222 está abierto): ./hub/deploy_unibus_nats_exporter.sh magnus om (y homer homer, datardos dd).
  4. Provisionar el dashboard: copiar hub/dashboards/unibus-nats.json a /var/lib/grafana/dashboards en magnus (mismo mecanismo que unibus-cluster.json; el provider fleet lo recoge).
  5. Verificar: series nats_*/kv_bucket_msgs en VictoriaMetrics y el dashboard unibus-nats en Grafana.

Gaps / pendientes

  • Función del registry sin commitear (dependencia de integración): parse_nats_monitor está creada, indexada y testeada en el working tree del padre fn_registry, pero NO se commiteó (aislamiento: el prompt prohíbe tocar el padre; el orquestador la integra). Es el paso 1 del runbook. El exporter la importa, así que debe ir al registry antes de integrar el exporter.
  • unibus_exporter sin remote Gitea: el sub-repo aún no está sincronizado a dataforge/. El commit dcdae92 quedó local en la rama quick/nats-deep-metrics; el push se hará vía /full-git-push (que crea el repo) o configurando el remote. Las otras dos ramas (quick/nats-monitor-flag en unibus, quick/nats-deep-metrics en fleet) sí están pusheadas.
  • Validación contra el cluster vivo no realizada (por diseño): el scraper se validó e2e contra un nats-server embebido local con el binario 0.11.0 real, no contra magnus/homer/datardos. Activar UNIBUS_NATS_MONITOR en datardos habría exigido reiniciar un nodo RAFT de producción con otros agentes trabajando contra el cluster vivo — riesgo no justificado. Queda para el rollout.
  • NRestarts por proceso/stream no lo expone NATS 8222: se emite nats_server_start_seconds como proxy (un cambio de su valor = el nats-server reinició; el panel "Reinicios (1h)" usa changes()). El contador de reinicios del proceso systemd (NRestarts) vendría de otra fuente.
  • Nit de linter (cosmético): parse_nats_monitor.go usa HasPrefix+TrimPrefix donde el linter sugiere CutPrefix. No afecta corrección; opcional de pulir.
  • Cluster NO reiniciado: magnus/homer/datardos intactos (8222 cerrado, healthz OK). Correcto según el encargo.

Ramas

Repo Rama Estado
dataforge/unibus quick/nats-monitor-flag commit 1c93251, pusheada (NO merge)
dataforge/fleet_monitoring quick/nats-deep-metrics commit dfd55dc, pusheada (NO merge)
dataforge/unibus_exporter quick/nats-deep-metrics commit dcdae92, local (sub-repo sin remote)
fn_registry (padre) working tree función parse_nats_monitor sin commitear (paso 1 del runbook)