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

227 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```go
// 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) |