Files
apps-registry/docs/known-issues.md
devopsandClaude Sonnet 5 86fcc88a1e docs(known-issues): documentar fix definitivo del blackhole MTU/PMTUD cross-node
Confirmado empiricamente el limite real (1420 OK / 1450 blackhole),
aplicado y validado el fix (flannel.1 mtu 1400 + rollout restart de
ecommerce). El podAffinity de medusa.yaml se deja como red de contencion,
ya no como requisito.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011FqYP3Wf1W63Qmh7qgXAAT
2026-08-16 23:00:45 -05:00

7.0 KiB
Raw Permalink Blame History

Known issues

Blackhole de red cross-node en el cluster k3d (MTU/PMTUD, flannel VXLAN)

Estado: RESUELTO (2026-08-16). Fix definitivo aplicado y validado; el podAffinity se deja como red de contención extra, no como requisito.

Síntoma: cualquier pod que necesite hablar con commerce-postgres-0 (StatefulSet, sin réplicas, fijo a un nodo) falla de forma consistente y reproducible al 100% cuando queda agendado en un nodo distinto al de Postgres. El handshake TCP (SYN/ACK, nc, ping) funciona perfecto entre nodos, pero el primer paquete de datos real de la sesión (protocolo Postgres, pg_isready incluido) nunca llega — se cuelga hasta hacer timeout.

Diagnóstico:

  • Confirmado aislando el nodo con nodeName en pods de prueba: 100% de fallos (186/186 intentos) agendando cross-node (agent-0 → Postgres en server-0); 100% de éxito agendando en el mismo nodo.
  • MTU dentro de los pods (interfaz flannel.1/VXLAN) es 1450 en ambos nodos — consistente, no es un mismatch obvio a nivel flannel.
  • ping sin -M do (permite fragmentación) no muestra pérdida de paquetes hasta payloads de 2000 bytes entre los contenedores Docker de los nodos.
  • Encaja con un blackhole de PMTU Discovery: el bridge Docker externo que conecta los contenedores de los nodos k3d probablemente tiene un MTU real menor a 1500, y los paquetes TCP con el bit DF puesto (como los de la sesión de Postgres) se pierden en vez de fragmentarse o generar el ICMP "fragmentation needed" que permitiría el ajuste automático.

Workaround temporal (aplicado en workloads/ecommerce/commerce/medusa.yaml):

podAffinity con requiredDuringSchedulingIgnoredDuringExecution sobre medusa-deploy, forzando que sus pods se agenden siempre en el mismo nodo que commerce-postgres-0 (matchLabels: app: commerce-postgres, topologyKey: kubernetes.io/hostname). Esto evita el tráfico cross-node entre Medusa y Postgres, pero no resuelve el problema de fondo — cualquier otro workload que necesite cruzar nodos para hablar con Postgres (u otro servicio) puede pisar el mismo blackhole.

Fix definitivo aplicado (2026-08-16):

Confirmado empíricamente con un pod de debug (nicolaka/netshoot, tiene ping con soporte real de bit DF — el ping de BusyBox en la imagen de nodo de k3s no lo soporta) haciendo un sweep de payloads con ping -M do cross-node hacia el pod de commerce-postgres-0: el límite real está entre payload interno 1420 (0% pérdida) y 1450 (100% pérdida, sin ningún ICMP "frag needed" — blackhole real, no fragmentación normal). Coincide casi exactamente con el MTU que la propia interfaz flannel.1 ya reportaba (1450, calculado como eth0 1500 − 50 de overhead VXLAN) — la aritmética de flannel es correcta, pero el último tramo no soporta en la práctica el paquete VXLAN a tamaño completo.

k3s (que embebe flannel, no lo corre como binario/DaemonSet separado) no expone un flag --flannel-mtu en esta versión (confirmado con k3s server --help, ver docs/gitops o scripts/deploy-lab.sh). El único punto de ajuste real es la interfaz flannel.1 en vivo dentro de cada contenedor de nodo: docker exec <nodo> ip link set flannel.1 mtu 1400. No persiste en ningún archivo — hay que reaplicarlo cada vez que el cluster arranca. Automatizado en scripts/deploy-lab.sh (función fix_flannel_mtu, llamada tanto en create_cluster como en start_cluster).

Aplicar el nuevo MTU solo afecta pods nuevos — los que ya estaban corriendo mantienen el MTU viejo en su veth hasta que se recrean. Se hizo kubectl rollout restart de todo el namespace ecommerce (commerce-postgres, commerce-redis, commerce-minio, medusa-deploy, catalog-api-deploy, frontend-deploy) para que todos tomaran el MTU nuevo de inmediato.

Validación post-fix: el mismo sweep de ping -M do ahora devuelve Frag needed and DF set (mtu = 1400) — PMTU Discovery funcionando correctamente, ya no hay blackhole silencioso. Confirmado además con tráfico TCP real cross-node hacia Postgres (conexión exitosa + transferencia de 50 KB sin colgarse, muy por encima del umbral que antes rompía).

El podAffinity en workloads/ecommerce/commerce/medusa.yaml que fija medusa-deploy al mismo nodo que commerce-postgres-0 se deja tal cual — ya no es necesario para evitar el blackhole, pero no tiene costo real y sirve como red de contención si el MTU real del entorno vuelve a cambiar (por ejemplo, tras una actualización de ZimaOS/Docker).

Topología de red: Cloudflare Tunnel → Nginx Proxy Manager → k3d

Estado: referencia de arquitectura — no es un issue abierto, pero el patrón de falla que describe ya se repitió una vez (ver docs/playbooks/incidente-medusa-imagenes-proxy-npm-2026-08.md) y puede volver a aparecer con otro subdominio.

Ruta real de una petición pública a *.cruzcloud.net:

Internet → Cloudflare (DNS proxied, túnel) → contenedor `cloudflared`
  (app nativa de ZimaOS, red Host) → Nginx Proxy Manager
  (contenedor `nginxproxymanager`, config en
  /DATA/AppData/nginxproxymanager/data/) → 192.168.68.61:90
  → `k3d-lab-cluster-serverlb` (k3d-proxy, publica 90→80 y 9443→443)
  → Traefik (ingress del cluster) → Service correspondiente,
  enrutado por el header `Host`.

Los proxy hosts de NPM viven en /DATA/AppData/nginxproxymanager/data/database.sqlite (tabla proxy_host) y NPM regenera solo el .conf correspondiente en data/nginx/proxy_host/<id>.conf cuando cambia la fila en la base — no hace falta editar el .conf a mano, y si se edita a mano puede sobreescribirse en cualquier cambio posterior desde la UI/DB.

Patrón de diagnóstico: si un subdominio *.cruzcloud.net responde 500 Internal Server Error con header Server: openresty cuando se accede públicamente, pero el mismo path funciona bien contra el ingress interno del cluster (curl -H "Host: <subdominio>" http://<IP-del-nodo>/...), el problema no está en el cluster ni en la app — está en el proxy host de NPM para ese subdominio. Revisar forward_host/forward_port en la tabla proxy_host y compararlos contra un host que sí funcione (ej. shop.cruzcloud.net, commerce.cruzcloud.net, ambos con forward_host='192.168.68.61', forward_port=90).

Nota de arquitectura: Medusa v2 Store API requiere region_id explícito

Estado: no es un issue, es un requisito de la API que causó un incidente real por no ser conocido — ver docs/playbooks/incidente-medusa-precios-region-id-2026-08.md.

El endpoint /store/products de Medusa v2 (validado por StoreGetProductsParams, .strict()) no acepta currency_code ni country_code como parámetros para resolver el contexto de precio (calculated_price). El único parámetro válido para ese fin es region_id, apuntando a una región existente y configurada vía Admin API. Cualquier integración nueva contra el Store API de Medusa v2 que necesite precios calculados debe pasar region_id explícito — no asumir que currency_code o country_code son suficientes, aunque lo sean en Medusa v1 o en otras APIs de e-commerce.