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