Files
apps-registry/workloads/docs-portal/docs/arquitectura/red-y-exposicion.md
devops 056a7fb76a docs(docs-portal): Fase 2 - contenido narrativo real de arquitectura/decisiones/aprendizajes
Reemplaza los placeholders "TODO (fase 2+)" por narrativa completa,
grounded en el codigo/manifiestos/incidentes reales del lab (no
generico):

- ADR 0001 (por que k3d) y ADR 0002 (por que Argo CD): pasan de
  template vacio a Contexto/Opciones/Decision/Consecuencias reales,
  apoyados en known-issues.md (blackhole MTU) y en la config real de
  ecommerce-app.yaml.
- vision-general.md: el "por que" de cada frontera de confianza del
  diagrama.
- gitops-flujo.md: ejemplo real con hashes de commit (Fase 3b de
  ADR 0006, v1.0.171 -> hotfix -> v1.0.172), selfHeal explicado con el
  syncPolicy real del repo.
- red-y-exposicion.md: diagrama de terminacion TLS real, por que todo
  va "proxied", patron de diagnostico "interno sano, publico roto".
- glosario.md: 9 terminos nuevos, cada uno con ejemplo real y
  verificado del repo (Namespace, ResourceQuota, LimitRange,
  NetworkPolicy, Deployment, Service, Ingress, StatefulSet, Init
  container).
- notas-sueltas.md: MinIO/S3, limites reales del subscriber
  revalidate-storefront.ts, que se automatizaria desde cero.

Validado con `docker build --target build` (mkdocs build --strict,
mismo paso que el pipeline real) -- cero links rotos, cero warnings.
2026-08-26 23:51:18 -05:00

3.7 KiB

Red y exposición

Los 5 dominios de abajo son reales y comparten exactamente el mismo camino de red — ninguno tiene un caso especial.

Dominios activos

Dominio Servicio Namespace TLS
shop.cruzcloud.net Frontend Next.js (storefront) ecommerce Cloudflare (edge)
commerce.cruzcloud.net Medusa Store/Admin API ecommerce Cloudflare (edge)
media.cruzcloud.net MinIO (imágenes de producto) ecommerce Cloudflare (edge)
gitea.cruzcloud.net Gitea — Cloudflare (edge)
docs.cruzcloud.net Este portal docs Cloudflare (edge)

Por qué todos van "proxied" (naranja), ninguno "DNS-only"

Un registro DNS-only en Cloudflare expondría directamente la IP real del túnel — pero acá no hay IP pública fija que exponer: el NAS está detrás de NAT residencial. El modo proxied es lo que hace que Cloudflare Tunnel tenga sentido en primer lugar (el tráfico entra por el edge de Cloudflare, nunca directo al origen), así que no hay ningún dominio de este lab para el que DNS-only sea una opción real.

Dónde termina TLS de verdad

flowchart LR
    User(["Usuario"]) -->|HTTPS| CF["Cloudflare edge<br/>🔒 TLS termina acá"]
    CF -->|túnel cifrado, HTTP/2| CFD["cloudflared"]
    CFD -->|HTTP plano| NPM["Nginx Proxy Manager"]
    NPM -->|HTTP plano| LB["k3d-proxy :90"]
    LB -->|HTTP plano| TR["Traefik"]
    TR -->|HTTP plano| SVC["Service interno"]

TLS termina en el edge de Cloudflare, no en Traefik ni en NPM. De ahí hacia adentro (cloudflared → NPM → k3d-proxy → Traefik → Service) todo el tráfico va en HTTP plano — seguro en este contexto porque ese tramo entero vive dentro de la red local del NAS, nunca sale a internet. Ningún componente interno necesita certificado propio ni gestionar renovación de TLS.

Por qué HTTP/2 y no QUIC en el túnel

cloudflared puede usar QUIC (UDP) o HTTP/2 (TCP) para sus conexiones salientes hacia el edge. Este lab fuerza HTTP/2 explícitamente — no es el default. Un incidente real de 504 Gateway Timeout intermitente en todos los subdominios (no solo uno) se rastreó hasta fallos recurrentes del "control stream" de QUIC sobre la red doméstica del NAS. Ver ADR 0003 para la decisión completa y el playbook del 504 para el diagnóstico paso a paso.

Patrón de diagnóstico: "interno sano, público roto"

Cuando un dominio público falla pero todo dentro del cluster responde bien (pods Running, probes verdes, kubectl exec con curl al Service funciona), la causa casi nunca está en Kubernetes — está en la capa NPM. La forma rápida de confirmarlo es saltarse NPM a propósito: curl -H "Host: <dominio>" http://192.168.68.61:90/ habla directo con k3d-proxy/Traefik sin pasar por NPM. Si eso funciona pero el dominio público no, el problema está específicamente en el forward_host/ forward_port del proxy host en NPM (ej.: un scheme o un path colado en forward_host, que rompe todo el enrutamiento).

Este patrón se repitió dos veces en este lab con la misma causa raíz exacta: el incidente de imágenes en media.cruzcloud.net (playbook completo) y un incidente posterior de commerce.cruzcloud.net devolviendo 400 por el mismo tipo de forward_host roto (sin playbook propio todavía). Una lección adicional de ese segundo incidente: editar el proxy_host a mano en el sqlite de NPM no regenera el .conf de nginx automáticamente — el cambio tiene que pasar por la propia UI/API de NPM, o el .conf hay que parchearlo a mano y recargar nginx.