ADR-032: panel de monitoreo de solo lectura y pila de observabilidad en el clúster
Aceptado el 15/11/2026 (regla de Andrés: ADR necesario y no absorbido por otro, aprobado; lo comunicó la sesión de planeación). Propuesto ese mismo día por el carril `monitoreo-en-vivo`. Pedido de Andrés del 15/11/2026: un panel de solo lectura en `monitor.andesretail.com` con logs, métricas, trazas, quién compra, la prueba de carga y el grafo de componentes en vivo.
Contexto
docs/to-be/10-observabilidad.md fija las piezas del demo (OpenTelemetry Collector, Prometheus, Loki, Tempo y Grafana en el clúster) y ADR-021 que las métricas de negocio salen en /metrics y las lee el recolector. Faltaba cómo se despliegan y cómo se muestran, y había tres puntos abiertos:
- El plan decía que en Kubernetes el agente de OpenTelemetry lo inyectaría el operador de OpenTelemetry con su recurso
Instrumentation, que pide cert-manager. Son dos piezas más en un clúster que ya corre 20 cargas, a dos días de la entrega. - El subdominio
grafanadel catálogo no existe en DNS. Andrés pidiómonitor, con el grafo de la malla además de los tableros. - El grafo de componentes en acción no es un tablero: lo dibuja Kiali con las métricas que ya emiten los sidecars de Istio.
Opciones
| Punto | Opción elegida | Alternativa | Por qué |
|---|---|---|---|
| Agente de OpenTelemetry | Contenedor de arranque en el chart workload que copia el agente desde la imagen de autoinstrumentación del operador (la misma imagen, digest y rutas que compose, deploy/compose/otel.yaml) y las mismas variables de entorno |
Operador de OpenTelemetry con cert-manager | Mismo resultado sin dos componentes más; compose y Kubernetes quedan iguales. Se pierde el cambio de versión del agente sin tocar el chart |
| Instalación | Charts propios pequeños en platform/k8s/charts/ (prometheus, loki, tempo, otel-collector, grafana, kiali), una réplica, retención corta |
Charts de la comunidad (kube-prometheus-stack, loki, tempo, kiali-operator) | Pasan k8s-verify (kubeconform y Trivy sin hallazgos) como el resto, sin operadores ni CRD nuevas, y caben en el nodo del demo |
| Métricas | El recolector, uno por nodo, lee /metrics de los pods con anotaciones prometheus.io/* (con sidecar, el puerto 15020 que une las de la aplicación y las de Envoy) y las manda a Prometheus por remote write con los filtros de ADR-021 |
Prometheus lee directo a los pods | Respeta ADR-021: los filtros de datos personales y etiquetas sin límite quedan en un solo lugar |
| Grafo de componentes | Kiali, servidor sin operador, view_only_mode, acceso anónimo y un ClusterRole que solo lee, en monitor.<zona>/kiali |
Panel de grafo de nodos de Grafana con las métricas de servicio de Tempo | Kiali dibuja la malla real (mTLS, 429, errores por arista) sin escribir código |
| Acceso | monitor.<zona> detrás de Cloudflare Access (correo), Grafana anónimo en rol Viewer sin ingreso ni edición, Prometheus, Loki y Tempo sin ruta pública |
Grafana con usuarios propios | Una sola puerta (Access) y nada que editar: el demo no puede romperse desde el panel |
Decisión
- La pila corre en el espacio
observability, fuera de la malla, con imágenes públicas fijadas enversions.env(Prometheus, Loki, Tempo, Grafana, Kiali y el recolector contrib por digest). No tiene secretos en AWS ni rol de Pod Identity: las claves que exige el software (administrador de Grafana, firma de Kiali) son aleatorias en cada arranque y nadie las conoce. El catálogo de cargas no la incluye;check-catalog.mjsla salta por espacio de nombres. observabilitytiene Pod Securityprivilegedimpuesto (avisado y auditado contrarestricted): el recolector lee/var/log/podsdel nodo en solo lectura, como root sin capacidades.- Logs: el recolector toma la salida estándar de los pods de
andes,edge,legacyydata, sin los contenedoresistio-proxy(su registro de acceso trae rutas completas) y enmascara correos si alguno se cuela (regla 5). - Trazas: el agente guarda en el tramo del servidor la cabecera
X-Correlation-Id(http.request.header.x-correlation-id), que no es dato personal; así una compra se busca en Tempo porcorrelationId, y desde Loki se salta a la traza portraceIdo porcorrelationId. monitor.<zona>reemplaza agrafana.<zona>: la raíz va a Grafana y/kialia Kiali (chartistio-routing).- k6 manda sus métricas a Prometheus por remote write cuando la pila está instalada (
tests/load/run-eks.sh). observabilityqueda fuera de la malla y sin AuthorizationPolicy (decisión de planeación del 15/11/2026, para el demo). Su único límite de red es una NetworkPolicy por almacén que deja entrar solo a los espacios que lo usan: Prometheus desdeobservability,edge,demoylegacy(k6); Loki y Tempo desdeobservability,edgeydemo. Grafana y Kiali solo los alcanza el público por el gateway de entrada, y el recolector recibe OTLP de todas las cargas.- El
correlationIdva en los tramos de las cargas Java, por variables del agente y sin código:http.request.header.x-correlation-iden HTTP ymessaging.header.ce_correlationiden Kafka. Ninguna otra cabecera (regla 5).
Qué se sacrifica
- Sin alta disponibilidad ni retención larga: una réplica de cada pieza, 3 días de métricas y 2 de logs y trazas. Es el demo; la tabla "Herramientas" de 10-observabilidad.md sigue diciendo qué se usaría en producción (servicios administrados de AWS).
- Sin Alertmanager: las alertas se evalúan en Prometheus y se ven en Grafana, no notifican a nadie.
- Seguridad de los almacenes: sin mTLS ni identidad, Tempo, Prometheus y Loki son legibles por cualquier pod de los espacios permitidos, y por cualquier pod del clúster si el CNI no aplica NetworkPolicy. En kind no la aplica: el 15/11/2026, desde
andes, Prometheus y Tempo respondieron con la política puesta. En EKS la aplica el CNI de VPC solo con su política de red encendida (por verificar en la raízcluster). Los logs y las trazas no traen datos personales (regla 5, comprobado pormake obs-live-check), lo que acota el daño. En producción irían dentro de la malla, con AuthorizationPolicy por identidad, o en los servicios administrados de AWS con IAM. - Los adaptadores Node no guardan el
correlationIden sus tramos: el agente de Node no captura cabeceras por variables de entorno y pide código (configurarheadersToSpanAttributesde la instrumentación HTTP y un gancho para la cabecera de Kafka) enplatform/templatesy en cada adaptador. El demo sigue una compra por los tramos Java (order-service, inventory-service, payment-service), y en los adaptadores por sus logs en Loki, que sí traen elcorrelationId. - La exportación OTLP sale sin pasar por el sidecar (
traffic.sidecar.istio.io/excludeOutboundPortscon el puerto del recolector, 4318, en cada carga con agente; chartworkload, 15/11/2026). El recolector está fuera de la malla, así que esa conexión no tenía mTLS; lo que se pierde es que el control de salida de la malla no ve ese puerto: una carga podría abrir una conexión a cualquier destino en el 4318. A cambio, Kiali deja de dibujar una arista de cada carga al recolector, que convertía el grafo en una columna sin nombres legibles. En producción el recolector iría dentro de la malla o como agente en el nodo. - El agente se actualiza cambiando
versions.env, no desde un operador. - Las imágenes públicas salen de Docker Hub, quay.io y ghcr.io; en EKS conviene la caché de ECR, como ya anota
platform/k8s/README.mdpara PostgreSQL, Redis y Kafka.