Files
message_bus/reports/0017-2026-06-07-unibus-rollout-0.11.0.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

8.6 KiB

Report 0017 — Rollout consolidado unibus 0.11.0 al cluster de 3 nodos (ROLLING, gate R3)

  • Fecha: 07/06/2026
  • Autor: agente (Claude Code)
  • Ámbito: apps/unibus (membershipd) + apps/unibus_admin (panel) — cluster HA producción magnus + homer + datardos
  • Estado: done — los 3 nodos en 0.11.0, R3 pleno, panel redeployado, sin rollbacks

Resumen

Rollout rolling del binario membershipd 0.11.0 a los tres nodos del cluster, uno a uno con gate de reconvergencia R3 entre cada nodo, activando en el mismo ciclo de restart el monitoring NATS loopback limpio (UNIBUS_NATS_MONITOR=1, desacoplado del debug-log). Un solo ciclo de restart por nodo desbloqueó las tres cosas previstas: la API /users (panel admin, antes 404 con request firmado), las métricas NATS en 127.0.0.1:8222, y el cierre del gap del binario viejo (report 0012). Después, re-deploy del panel admin en magnus. El cluster mantuvo quórum (2/3) en todo momento; cada nodo reconvergió a R3 (6/6 streams KV a followers_current=2/2) antes de tocar el siguiente. No fue necesario ningún rollback.

Cambios

Fase Acción Resultado
0 — Integrar git merge --no-ff quick/nats-monitor-flag → master (commit f31580d), app.md → 0.11.0 Sin conflictos; web/ no tocado
0 — Verificar go build ./... + tests membership/client/embeddednats/cmd/membershipd Todo verde; push a origin/master
1 — Build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /tmp/membershipd_0.11.0 ./cmd/membershipd ELF static x86-64, sha f6096a8e…, --help OK
2 — Rollout magnus → homer → datardos: backup + drop-in UNIBUS_NATS_MONITOR=1 + binario + restart + gate R3 3/3 reconvergidos, sin perder quórum
3 — Verificar healthz 3/3, /users firmado 200 3/3, 8222 3/3, sin debug, R3 6/6 a 2/2 Todo verde
4 — Panel rebuild master (Users cableado) + backup + deploy magnus + restart unibus-admin.service /api/users 200, end-to-end vía Caddy 200

Detalle del flag (0.11.0): función pura natsLogOpts(debugEnv, monitorEnv) (noLog, debug, trace, monitor) con monitor = monitorEnv=="1" || debug y noLog = !debug. Con MONITOR=1 el endpoint de monitoring abre en 127.0.0.1:8222 (bind loopback hardcoded) dejando el log del nats-server en silencio (NoLog true). El acoplamiento inverso se mantiene (DEBUG implica MONITOR). Drop-in systemd aditivo: /etc/systemd/system/membershipd-cluster.service.d/nats-monitor.conf con [Service] + Environment=UNIBUS_NATS_MONITOR=1 — no toca el unit base ni cluster.env.

El binario nuevo es drop-in del unit existente: se verificó que acepta los 17 flags del ExecStart (incluido --internal-id-file, presente solo en el unit de datardos). No se modificó ningún unit ni cluster.env.

Verificación

Unit y baseline (confirmados, no asumidos)

ExecStart=/opt/unibus/membershipd … (EnvironmentFile=/opt/unibus/cluster.env)
HTTP_PORT=8470  NATS_CLIENT_PORT=4250
healthz baseline (3 nodos): {"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"}
8222 baseline: CERRADO en los 3 (esperado, sin drop-in)
binario viejo: magnus/homer 29337645 b; datardos 29374815 b (0012, con --internal-id-file)

Hallazgo clave del gate R3 — métrica autoritativa

La métrica followers_current de /jsz solo es autoritativa consultada en el LEADER del stream. Un nodo follower (recién reiniciado) reporta su vista desactualizada de los otros peers (current=false, lag constante) durante minutos sin que sea un problema real. La señal autoritativa por nodo es el /healthz del nats-server (127.0.0.1:8222/healthz): devuelve 503 si cualquier stream del que el nodo es miembro está lagging, 200 sólo cuando todos están current. Se usó ese endpoint como gate primario por nodo, y la vista del stream-leader para el conteo explícito 2/2.

Gate R3 por nodo (orden magnus → homer → datardos)

Nodo Backup membershipd healthz nats /healthz meta (leader/size/pending) 8222 /varz [DBG]/[TRC] quórum otros 2
magnus membershipd.bak.20260607-192416Z 200 enforce+acl+tls+cluster+kv 200 ok homer / 3 / 0 200 0 homer 200, dd 200
homer membershipd.bak.20260607-193313Z 200 idem 200 ok (t=0) datardos / 3 / 0 200 0 magnus 200, dd 200
datardos membershipd.bak.20260607-193452Z 200 idem 200 ok (t=0) homer / 3 / 0 200 0 magnus 200, homer 200

Re-elecciones de meta-leader observadas (esperadas, quórum 2/3 intacto): inicial homer → restart homer → datardos → restart datardos → homer (final). Tras completar los tres, la vista del stream-leader (magnus) confirmó los 6 buckets KV (users/rooms/members/room_keys/rooms_by_member/nonces) a followers_current=2/2:

streams KV a 2/2: 6/6 (stream-leader=magnus); meta-leader=homer cluster_size=3 pending=0

FASE 3 — verificación post-rollout (3 nodos)

                magnus    homer    datardos
membershipd     200       200      200
posture         enforce+acl+tls+cluster+store=kv (homogénea)
/users firmado  200       200      200     (3 users idénticos — KV replicado R3)
8222 varz/jsz/connz  200/200/200   200/200/200   200/200/200
nats /healthz   ok        ok       ok
journal DBG/TRC 0         0        0

/users firmado se verificó con un verificador throwaway (client.Connect + ListUsers del propio pkg/client, identidad operator desde pass unibus/operator-identity en archivo local 0600, TLS+nkey contra cada nodo por su IP pública; SAN del cert cubre las IPs). Antes del rollout, un GET /users firmado daba 404 (ruta ausente en el binario viejo); ahora da 200 con la allowlist:

operator        role=admin  status=active
gapcheck_user   role=member status=revoked   (residuo E2E del report 0012)
gapcheck_user2  role=member status=revoked   (residuo E2E del report 0012)

El verificador y la copia local de la identidad se eliminaron (cmd/usercheck borrado del working tree; /tmp/operator.id con shred). Working tree de unibus limpio (master...origin/master).

FASE 4 — deploy del panel admin

panel binario nuevo: /opt/unibus_admin/unibus_admin (sha 812831…), backup unibus_admin.bak.20260607-194114Z
service unibus-admin.service: active; journal limpio ("users backend: control-plane", "bus TLS+nkey: ON", "cluster nodes probed: 3")
GET /api/users (loopback 8480):            HTTP 200 — lista real (3 users), ya no 502
GET /api/cluster:                          3 nodos up:true, posture homogénea, latencias 0/10/5 ms
end-to-end vía Caddy (basic auth admin):   GET https://admin-…/api/users -> HTTP 200 con lista
end-to-end sin auth:                       HTTP 401 (Caddy basic_auth protege)

El binario del panel se reconstruyó desde master (frontend web/dist ya embebido, no se tocó ningún web/). El Caddyfile no se modificó: el site (reverse_proxy 127.0.0.1:8480 + basic_auth) quedó intacto; no hizo falta caddy reload (mismo puerto/config). La cadena completa navegador → Caddy(TLS+auth) → panel → bus firmado /users → KV R3 está probada.

Seguridad

  • Secretos siempre desde pass/path, nunca en git ni argv: identidad operator a archivo local 0600 (borrada con shred); password del panel pasado a curl por stdin (-K -), nunca en la línea de comandos.
  • Monitoring vía UNIBUS_NATS_MONITOR (drop-in), nunca UNIBUS_NATS_DEBUG: confirmado 0 líneas [DBG]/[TRC] en journald de los 3 nodos. 8222 bind loopback hardcoded.
  • Backups reversibles por nodo conservados en /opt/unibus/ (membershipd) y /opt/unibus_admin/ (panel) — listados arriba.
  • Gate R3 estricto, un nodo abajo a la vez; quórum 2/3 nunca perdido. magnus (host crítico): sólo se reinició membershipd-cluster.service. Posture homogénea mantenida.

Gaps / pendientes

  • Verificación visual de la pestaña Users con navegador: no realizada. Se verificó la cadena funcionalmente end-to-end (Caddy basic_auth → panel → bus → /api/users 200 con la lista real). No se abrió el navegador para no exponer el admin-panel-password en la URL (el basic_auth de Caddy obliga a meter las credenciales en la URL o en un dialog nativo no automatable de forma segura). El render de la SPA es determinista sobre /api/users, que responde 200.
  • Los usuarios gapcheck_user/gapcheck_user2 (revoked) son residuos de los checks E2E del report 0012, no usuarios reales; pueden limpiarse del KV si se desea (no bloquea nada).
  • Binarios temporales en /tmp (locales y de los nodos) eliminados. Los backups .bak.* se conservan a propósito para reversibilidad.