Cómo llevar el stack completo —13 micros + gateway, hoy en un mismo compose
con redes Docker— a una mesh Tailscale donde cada micro corre aislado, en su
propia máquina, y sigue siendo parte del sistema: se registra en Eureka, el gateway le enruta
y recibe identidad propagada sin publicar un solo puerto.
Un solo compose, una sola máquina: el borde en edge_net/gateway_net,
la infraestructura compartida y los 13 micros en backend_net, y una red por grupo
que solo conecta a cada micro con su propia BD.
borde: cliente → nginx → gateway, única puerta real descubrimiento: gateway → registry Redis (1º) · fallback → Eureka async: sandbox → MinIO · sandbox → Kafka (ejecucion.terminada) → notificaciones red por grupo: micro ↔ su BD
Membresías dobles: nginx RP en edge_net + gateway_net · gateway en gateway_net + backend_net. El gateway no consulta MinIO: descubre por el registry (Redis) y cae a Eureka. Ninguna BD sale de su red de grupo.
03
El problema: una red compartida por todos
La topología Docker resolvió el borde y el descubrimiento, pero ata el sistema a una máquina:
compartir red es compartir host.
01 · Escala
No cruza máquinas
Los bridges Docker son locales al host. Sumar un equipo significa darle acceso a la misma máquina o armar túneles a mano.
Un solo compose es también un solo punto de despliegue y de falla.
02 · Blast radius
Los micros se ven entre sí
backend_net es una red común: cualquier contenedor alcanza a los otros trece por nombre o por IP.
Un micro comprometido observa el resto del stack.
03 · Identidad
ports: publicados = spoofeables
Si el puerto de un micro queda expuesto, cualquiera llega directo e inventa los headers X-*.
El borde único deja de ser único.
La causa de fondo
El contrato del sistema asume que el gateway es la única puerta: ningún puerto de micro sale del host. Las redes Docker lo cumplen dentro de una máquina; entre equipos, no existe.
04
La idea: cambiar el transporte, no la arquitectura
Mismo sistema, otra red.
redes Docker locales · L2, un host, IPs 172.xtailnet Tailscale · L3 WireGuard entre nodos, IPs 100.x + MagicDNS
Eureka
Mismo rol: registro
Sigue siendo el catálogo de quién está vivo y en qué dirección. Solo cambia la dirección anunciada: la IP del tailnet, nunca la 172.x del bridge.
API Gateway
Misma puerta única
Valida identidad y rutea /api/* por lb://<serviceId>. Necesita ruta de salida al tailnet: sidecar propio o Tailscale a nivel host.
Identidad
Mismos contratos
Headers X-*, allowlist, catálogo de scopes y JWKS siempre por el gateway. Nada de esto cambia con la mesh.
En una línea
Lo único que cambia es cómo viaja el paquete: de un bridge local a una red privada punto a punto entre máquinas distintas.
05
El patrón sidecar: el micro hereda la red
El micro no habla Tailscale: comparte el namespace de red de un sidecar que sí lo habla.
Nada de ports: publicados — el único camino de entrada es la tailnet.
tráfico de negocio por el gateway registro en el service registry frontera del compose: BD privada, micro alcanzable solo por la mesh
Por qué modo kernel y no userspace
En userspace Tailscale no crea interfaz: expone un proxy SOCKS que solo sirve al sidecar, así que compartir el namespace no daría conectividad. El modo kernel exige /dev/net/tun y las capabilities NET_ADMIN + NET_RAW.
06
ANTES vs DESPUÉS: misma plataforma, otra topología
Los roles no se mueven: se mueve la red. Lo que era un compose con redes compartidas pasa a ser
un conjunto de nodos que se ven por la tailnet.
1 · Transporte
de bridge Docker local a tailnet L3
2 · Dirección
de IP 172.x a IP 100.x por MagicDNS
3 · Aislamiento
de un compose para todo a uno por equipo
4 · Borde
el gateway suma ruta al tailnet
El único cambio de rol: el gateway necesita ruta de salida al tailnet para iniciar conexiones hacia 100.64.0.0/10.
07
El patrón repetido: cada equipo con su compose
No hay una red que compartir ni una máquina que ceder: cada equipo conecta su micro con
su propio sidecar y todos se encuentran en la tailnet.
Sumar un equipo
Un serviceId único terminado en -service, su sidecar y su compose. Sin tocar la infraestructura de los demás.
Lo que hay que pedir
Alta en la allowlist del gateway y, si usa scopes propios, alta en el catálogo y credencial de servicio.
Lo que no se comparte
Red, base de datos, volúmenes, CI ni credenciales: cada equipo despliega cuando quiere, en su máquina.
08
Qué se comparte y qué no
Compartir la plataforma no es compartir la infraestructura: lo común son los contratos,
y eso es lo único que hay que negociar entre equipos.
Se comparte · contratos
Lo que hay que negociar
Registro Eureka:serviceId, estado y dirección alcanzable.
Allowlist del gateway: sin alta, todo da 404 por diseño.
Catálogo de scopes y credencial de servicio.
Headers X-* y el prefijo /api/<segmento>.
ACLs del tailnet: quién habla con quién y por qué puerto.
No se comparte · infraestructura
Lo que cada equipo mantiene
Red Docker: ya no hay bridge común entre equipos.
Bases de datos y volúmenes: privados de cada compose.
CI/CD y ritmo de despliegue.
Credenciales y secretos: por entorno, nunca al repo.
Operación del sidecar: auth key efímera y rotación.
La prueba del contrato
Sumar un micro solo toca allowlist, scopes y ACLs. Si hiciera falta tocar la red de otro equipo, no sería un contrato: sería acoplamiento.
1 · serviceId
único y terminado en -service
2 · Allowlist
alta en el gateway: sin esto, 404
3 · Scopes
catálogo + credencial de servicio
4 · Registro
Eureka anunciando la IP 100.x
5 · ACL
deny por defecto
09
Seguridad del tailnet: grants con deny por defecto
La tailnet por defecto deja pasar todo: sin reglas, “sin ports:” no protege. Se usan
ACLs de Tailscale — la key del array es grants, no acls — con
tags por rol y reglas explícitas: los micros solo salen al Gateway y a Kafka.
Ya no es un pendiente: el tailnet opera con deny por defecto. La verificación negativa (nc -z <ip-micro> 8086 desde otro nodo) quedó bloqueada a nivel de red.
Regla de oro para diagnosticar
404 falta allowlist · 401 sin identidad o sesión vencida · 503 el gateway no llega a ninguna instancia (es red, no registro) · 403 invalid-audience el token es para otro destino.
Registro, ruteo e identidad funcionando de punta a punta. La ACL del tailnet está activa y validada: deny por defecto en toda la red.
11
Cierre: el gateway rutea a un micro sin puertos
El gateway rutea a un micro que no publica puertos.
Qué se logró
La integración, de punta a punta
desafiospracticos-service registrado UP anunciando IP 100.x.
Ruta pública por el gateway, con X-Request-Id de ida y vuelta.
Diagnóstico en verde: {gatewayAlcanzable: 200, eurekaRegistrado: UP}.
JWKS por el gateway: keys=1 · kids=[dev].
Cero puertos publicados: el micro solo vive en la tailnet.
Para sumarte
Tres cosas, en este orden
serviceId en la allowlist
Único y terminado en -service. Sin esto: 404 por diseño.
Scope en el catálogo
Si consumís o exponés scopes propios: alta en el catálogo y credencial de servicio.
Tu propio sidecar
Compose con sidecar Tailscale, sin ports:, registrando la IP 100.x.
ACL del tailnet: activa
La mesh ya opera con deny por defecto: solo tag:plataforma entra al puerto de app de los micros.
Automatizado: skill de conexión a Tailscale
Sumar un micro a la tailnet quedó empaquetado como skill del agente (sidecar kernel + auth key efímera + registro con IP 100.x): de 0 a 100 en el primer intento, sin fallos.