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

133 lines
7.0 KiB
Markdown
Raw Permalink 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.
# 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.