Entiende el problema que Eureka resuelve, el patrón Service Discovery y cómo encaja en tu arquitectura de microservicios LingoPlay.
El problema de las IPs dinámicas
Client-side vs Server-side discovery
Arquitectura Eureka Server/Client
Flujo de registro y heartbeat
El problema que Eureka resuelve
En un sistema de microservicios, los servicios se crean, escalan y mueven constantemente. Sus IPs y puertos cambian. ¿Cómo sabe el API Gateway dónde está el Game Service en este momento?
Client-side vs Server-side Discovery
Aspecto
Client-side (Eureka + Ribbon/LoadBalancer)
Server-side (AWS ALB, Nginx)
¿Quién resuelve?
El propio cliente (Gateway, Feign)
Un componente externo (proxy/LB)
Registry
Eureka Server
Generalmente integrado al LB
Caché local
Sí — cada cliente tiene copia del registro
No — el LB centraliza
Acoplamiento
Cliente debe conocer Eureka
Cliente solo conoce la URL del LB
Escalabilidad
Muy alta — no hay cuello de botella
El LB puede ser bottleneck
Uso en Spring Cloud
Eureka + Spring Cloud LoadBalancer
AWS ELB, Kubernetes Service
💡
Spring Cloud usa Client-side Discovery
Cada cliente (API Gateway, Feign Client) descarga el registro de Eureka y elige la instancia localmente. Esto elimina el cuello de botella del servidor de descubrimiento en las llamadas de producción.
Flujo completo de Eureka
game-service arranca
→
POST /eureka/apps/GAME-SERVICE
→
Registrado en el registry
game-service
→ cada 30s →
PUT /eureka/apps/GAME-SERVICE/{id}
→
Heartbeat OK ♥
api-gateway
→ cada 30s →
GET /eureka/apps
→
Caché local actualizada
Request cliente
→
lb://GAME-SERVICE
→ caché local →
10.0.0.3:8082
⚡
Mini Quiz — Módulo 1
En el modelo Client-side Discovery de Eureka, ¿quién decide a qué instancia enviar un request cuando hay múltiples instancias de game-service?
A
El Eureka Server — actúa como proxy y redirige el tráfico
B
El cliente (Gateway/Feign) — usa su caché local del registro y el LoadBalancer para elegir
C
La base de datos MySQL — almacena las IPs de los servicios disponibles
D
El API Gateway directamente vía DNS
Módulo 02 — Servidor
Eureka Server — El registro central
Configura el servidor Eureka que actuará como directorio de todos tus microservicios en LingoPlay.
Dependencias y setup
application.yml completo
Dashboard web
Configuración de seguridad
Dependencias Maven
xml — pom.xml Eureka Server
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.0</version></parent><dependencies><!-- Eureka Server --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><!-- Seguridad opcional para el dashboard --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- Actuator para health check --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2023.0.1</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
Clase principal
Java — EurekaServerApplication.java
@SpringBootApplication@EnableEurekaServer// ← Esta anotación activa el servidorpublic classEurekaServerApplication {
public static voidmain(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
application.yml — Configuración completa
yaml — application.yml Eureka Server
server:
port: 8761spring:
application:
name: eureka-serversecurity:
user:
name: ${EUREKA_USER:admin}password: ${EUREKA_PASS:secret}eureka:
instance:
hostname: localhostclient:
# El servidor NO se registra a sí mismoregisterWithEureka: falsefetchRegistry: falseserviceUrl:
defaultZone: http://localhost:8761/eureka/server:
# Tiempo de espera antes de eliminar instancias sin heartbeateviction-interval-timer-in-ms: 5000# Modo de desarrollo: desactiva la protección contra fallos de red# En producción: true (evita que una falla de red borre todos los servicios)enable-self-preservation: false# Umbral para activar self-preservation (85% por defecto)renewal-percent-threshold: 0.85# Tiempo máximo sin heartbeat antes de expirar (segundos)response-cache-update-interval-ms: 3000management:
endpoints:
web:
exposure:
include: health,info,eurekaendpoint:
health:
show-details: always
⚠️
Self-Preservation Mode — Qué es y cuándo activarlo
En producción con enable-self-preservation: true (default), si Eureka deja de recibir heartbeats de más del 85% de sus clientes de golpe, no los elimina del registro — asume que es un problema de red, no que los servicios murieron. Evita eliminar servicios válidos por un fallo temporal de red. En desarrollo, desactívalo para que los servicios "muertos" se eliminen rápido.
Eureka incluye un dashboard visual donde puedes ver todos los servicios registrados, su estado (UP/DOWN), número de instancias, y la información de cada una. En producción, protégelo con autenticación básica como se muestra arriba.
⚡
Mini Quiz — Módulo 2
¿Por qué el Eureka Server tiene registerWithEureka: false y fetchRegistry: false?
A
Por seguridad — evita que otros servicios conozcan la IP del servidor
B
El servidor contiene también un cliente Eureka; estas propiedades le indican que no se registre ni consulte a sí mismo
C
Son propiedades de rendimiento que reducen el uso de memoria del servidor
D
Solo se usan en modo desarrollo; en producción se eliminan
Módulo 03 — Clientes
Eureka Client — Registrar microservicios
Configura cada microservicio de LingoPlay para que se registre en Eureka y participe en el Service Discovery.
Dependencia Eureka Client
application.yml por servicio
Metadata e instancias
Health checks con Actuator
Dependencia del cliente
xml — pom.xml — microservicio cliente
<!-- Eureka Client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- Actuator — para healthcheck en Eureka --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
💡
¡No necesitas @EnableEurekaClient!
Desde Spring Cloud 2020+, con solo tener la dependencia en el classpath el cliente se activa automáticamente. La anotación @EnableEurekaClient o @EnableDiscoveryClient son opcionales (aunque puedes incluirlas para ser explícito).
Configuración de cada microservicio — LingoPlay
yaml — game-service/application.yml
server:
port: 8082# 0 = puerto aleatorio (múltiples instancias)spring:
application:
name: game-service# ← Nombre con el que se registra en Eurekaeureka:
client:
# URL del Eureka Server (con usuario:password si tiene seguridad)service-url:
defaultZone: http://admin:secret@localhost:8761/eureka/# Cada cuánto el cliente obtiene el registro actualizado de Eurekaregistry-fetch-interval-seconds: 30# Habilitar healthcheck via Actuator (más preciso que heartbeat)healthcheck:
enabled: trueinstance:
# Usar IP en lugar de hostname (importante en Docker/contenedores)prefer-ip-address: true# Cuánto espera Eureka antes de considerar el servicio muerto sin heartbeatlease-expiration-duration-in-seconds: 30# Con qué frecuencia envía heartbeat al servidorlease-renewal-interval-in-seconds: 10# ID único de instancia (útil para múltiples instancias del mismo servicio)instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}# URL del healthcheck (Eureka la consulta para saber si está UP)health-check-url-path: /actuator/healthstatus-page-url-path: /actuator/info# Metadatos personalizados visibles en el dashboardmetadata-map:
version: "1.0.0"team: "backend"zone: "us-east-1"management:
endpoints:
web:
exposure:
include: health,infoendpoint:
health:
show-details: always
Tabla de configuraciones de todos los servicios LingoPlay
Servicio
spring.application.name
server.port
Rol
Eureka Server
eureka-server
8761
Registry
API Gateway
api-gateway
8080
Enrutador + LB
Auth Service
auth-service
8081
JWT + OAuth2
Game Service
game-service
8082
Lógica de juego
Score Service
score-service
8083
Puntuaciones
Admin Service
admin-service
8084
Backoffice
Múltiples instancias — Escalar un servicio
bash — Escalar game-service
# Opción 1: Puerto aleatorio (port: 0 en application.yml)# Cada instancia elige un puerto libre automáticamente
java -jar game-service.jar
java -jar game-service.jar # Segunda instancia en otro puerto# Opción 2: Puerto explícito
java -jar game-service.jar --server.port=8082
java -jar game-service.jar --server.port=8092
java -jar game-service.jar --server.port=8093
# Con Docker Compose: scale
docker compose up --scale game-service=3 -d
# Las 3 instancias aparecen en Eureka como:# GAME-SERVICE game-service:abc123 10.0.0.3:8082 UP# GAME-SERVICE game-service:def456 10.0.0.3:8092 UP# GAME-SERVICE game-service:ghi789 10.0.0.3:8093 UP# El Gateway distribuye el tráfico entre las 3 automáticamente (round-robin)
⚡
Mini Quiz — Módulo 3
Configuras server.port: 0 en game-service. ¿Cuál es el propósito de esta configuración?
A
Desactiva el servicio para que solo funcione internamente
B
El SO asigna un puerto libre aleatorio, permitiendo múltiples instancias sin conflictos
C
Impide que el servicio se registre en Eureka
D
Solo funciona si Eureka está corriendo antes de iniciar el servicio
Módulo 04 — Load Balancing
Spring Cloud LoadBalancer con Eureka
Aprende cómo el LoadBalancer distribuye el tráfico entre instancias usando el registro de Eureka, y cómo usar OpenFeign para llamadas entre servicios.
Spring Cloud LoadBalancer
Estrategias round-robin y random
WebClient + lb://
OpenFeign declarativo
Spring Cloud LoadBalancer
Es el reemplazo moderno de Netflix Ribbon (deprecated). Se integra con Eureka para distribuir tráfico entre instancias del mismo servicio.
xml — pom.xml — LoadBalancer
<!-- Incluido automáticamente con spring-cloud-starter-netflix-eureka-client --><!-- Pero puedes añadirlo explícitamente: --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- Para OpenFeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
OpenFeign te permite llamar a otros microservicios como si fueran interfaces Java locales. Integrado con Eureka y LoadBalancer automáticamente.
Java — Feign Client completo
// 1. Activar Feign en la clase principal@SpringBootApplication@EnableFeignClientspublic classScoreServiceApplication { ... }
// 2. Definir el cliente Feign@FeignClient(
name = "game-service", // Nombre en Eureka (lb:// automático)
fallback = GameClientFallback.class // Fallback si el servicio falla
)
public interfaceGameServiceClient {
@GetMapping("/juegos/{id}")
JuegoDTOobtenerJuego(@PathVariable("id") Long id);
@PostMapping("/juegos/{id}/iniciar")
SesionDTOiniciarJuego(
@PathVariable("id") Long id,
@RequestBodyIniciarJuegoRequest req
);
@GetMapping("/juegos")
Page<JuegoDTO> listarJuegos(
@RequestParamint page,
@RequestParamint size
);
}
// 3. Fallback@Componentpublic classGameClientFallbackimplementsGameServiceClient {
@OverridepublicJuegoDTOobtenerJuego(Long id) {
// Respuesta por defecto cuando game-service no respondethrow newServiceUnavailableException("Game service no disponible");
}
@OverridepublicSesionDTOiniciarJuego(Long id, IniciarJuegoRequest req) {
throw newServiceUnavailableException("No se puede iniciar el juego ahora");
}
}
// 4. Uso en un servicio@Servicepublic classScoreService {
private finalGameServiceClient gameClient;
publicScoreDTOcalcularScore(Long juegoId, Long usuarioId) {
// ¡Feign maneja la llamada HTTP internamente!JuegoDTO juego = gameClient.obtenerJuego(juegoId);
returnnewScoreDTO(juego.getNombre(), usuarioId, 100);
}
}
Configuración de Feign
yaml — Feign + timeout
feign:
client:
config:
default: # Aplica a todos los clientesconnectTimeout: 3000readTimeout: 5000loggerLevel: BASIC# NONE | BASIC | HEADERS | FULLgame-service: # Configuración específica por clienteconnectTimeout: 1000readTimeout: 2000circuitbreaker:
enabled: true# Integrar con Resilience4j
Estrategias de Load Balancing
Java — Estrategia personalizada (Random)
// Por defecto: RoundRobinLoadBalancer// Alternativa: RandomLoadBalancer@Configuration@LoadBalancerClient(name = "game-service", configuration = RandomLBConfig.class)
public classLoadBalancerConfig {}
public classRandomLBConfig {
@BeanpublicReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return newRandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
⚡
Mini Quiz — Módulo 4
¿Cuál es la diferencia principal entre usar @FeignClient y WebClient con @LoadBalanced para llamar a otro microservicio?
A
Feign es declarativo (interfaz Java, sin HTTP manual); WebClient es programático y reactivo (Mono/Flux)
B
Feign no usa Eureka; WebClient sí necesita el registro de Eureka
C
WebClient solo funciona con HTTP/2; Feign solo con HTTP/1.1
D
Solo WebClient soporta timeouts y reintentos
Módulo 05 — Alta Disponibilidad
Eureka en Alta Disponibilidad
Configura un clúster de Eureka con múltiples nodos para eliminar el punto único de fallo en producción.
Clúster de 3 nodos Eureka
Peer Awareness
Configuración por perfil
Comportamiento ante fallos
¿Por qué alta disponibilidad en Eureka?
Un único Eureka Server es un punto único de fallo (SPOF). Si cae, los servicios no pueden registrarse ni descubrir nuevos servicios (aunque los ya cacheados localmente siguen funcionando un tiempo). En producción, siempre usa al menos 2 nodos.
eureka-1
:8761
UP
eureka-2
:8762
UP
eureka-3
:8763
UP
Los 3 nodos se replican el registro entre sí — cualquiera puede caer sin perder disponibilidad
eureka:
client:
service-url:
# Lista de todos los nodos Eureka — si uno cae, usa los otrosdefaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
Tienes un clúster Eureka de 3 nodos. El nodo eureka2 cae. ¿Qué ocurre con los servicios que estaban registrados?
A
Todos los servicios se dan de baja y deben registrarse de nuevo
B
El sistema deja de funcionar completamente hasta que eureka2 vuelva
C
El sistema sigue funcionando: eureka1 y eureka3 tienen el registro replicado y atienden las consultas
D
eureka1 se convierte automáticamente en líder y eureka3 en respaldo pasivo
Módulo 06 — Contenedores
Eureka en Docker y Perfiles
Configura Eureka correctamente en entornos Docker donde los hostname e IPs son dinámicos, y usa perfiles de Spring Boot para distintos entornos.
prefer-ip-address en Docker
Perfil docker vs local
Variables de entorno
Healthcheck en Compose
El problema de Docker con Eureka
🔥
El error más común con Docker
Por defecto, Eureka registra los servicios con su hostname. Dentro de Docker, el hostname es el ID del contenedor (ej: a3f7b2c1d0e9). Otros servicios no pueden resolver ese hostname. El Gateway recibe esa dirección y falla. Solución: prefer-ip-address: true.
yaml — Configuración correcta para Docker
eureka:
instance:
# ✅ Usa la IP del contenedor en vez del hostname irresolubleprefer-ip-address: true# Alternativa: usar el nombre del servicio Docker como hostname# hostname: ${spring.application.name}# ID único para cada instancia (evita colisiones al escalar)instance-id: ${spring.application.name}:${server.port}:${random.uuid}
Perfiles: local vs docker
yaml — application.yml con perfiles
# Configuración base (todos los entornos)spring:
application:
name: game-serviceserver:
port: 8082---# Perfil: local (desarrollo)spring:
config:
activate:
on-profile: localeureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/instance:
prefer-ip-address: falselease-renewal-interval-in-seconds: 10---# Perfil: dockerspring:
config:
activate:
on-profile: dockerdatasource:
url: jdbc:mysql://mysql:3306/lingoplay_db# mysql = nombre del servicio Dockereureka:
client:
service-url:
defaultZone: http://eureka:8761/eureka/# eureka = nombre del servicio Dockerinstance:
prefer-ip-address: true
En Docker Compose, el game-service tiene defaultZone: http://eureka:8761/eureka/. ¿Por qué eureka (sin IP) en lugar de localhost:8761?
A
Es exactamente igual; eureka y localhost:8761 apuntan al mismo lugar
B
Spring Cloud exige usar nombres de servicio por restricciones de su configuración
C
Docker Compose tiene DNS interno que resuelve el nombre del servicio a la IP del contenedor en la red virtual
D
Por seguridad, Docker bloquea conexiones a localhost entre contenedores
Módulo 07 — Diagnóstico
Diagnóstico, API REST y errores comunes
Aprende a usar la API REST de Eureka, interpretar el dashboard y resolver los problemas más frecuentes.
API REST de Eureka
Dashboard — qué significa cada campo
Errores comunes y soluciones
Logs de diagnóstico
API REST de Eureka
Eureka REST API
$curl http://localhost:8761/eureka/apps<applications>
<application>
<name>GAME-SERVICE</name>
<instance>
<instanceId>game-service:8082:abc123</instanceId>
<ipAddr>10.0.0.3</ipAddr>
<port enabled="true">8082</port>
<status>UP</status>
<healthCheckUrl>http://10.0.0.3:8082/actuator/health</healthCheckUrl>
</instance>
</application>
</applications>$curl http://localhost:8761/eureka/apps/GAME-SERVICE# Solo las instancias de game-service$curl -H "Accept: application/json" http://localhost:8761/eureka/apps# Respuesta en JSON en lugar de XML$curl -X DELETE http://localhost:8761/eureka/apps/GAME-SERVICE/game-service:8082:abc123# Dar de baja una instancia manualmente$curl -X PUT http://localhost:8761/eureka/apps/GAME-SERVICE/game-service:8082:abc123/status?value=OUT_OF_SERVICE# Poner fuera de servicio sin eliminar (mantenimiento)
Errores más comunes
Connection refused a :8761
CAUSA: Eureka no está corriendo
El microservicio intenta registrarse pero Eureka no está disponible. En Docker: usa depends_on con condition: service_healthy. En local: arranca Eureka primero.
EMERGENCY! EUREKA MAY BE INCORRECT
CAUSA: Self-preservation activado
Eureka recibe menos del 85% de heartbeats esperados y activa el modo de protección. En desarrollo: enable-self-preservation: false. En producción: investigar pérdida de conectividad.
No instances available for game-service
CAUSA: Caché local desactualizada
El cliente tiene una caché vacía o desactualizada de Eureka. Espera 30s (registry-fetch-interval). En Docker verifica que prefer-ip-address: true esté activo.
Servicio en DOWN aunque está corriendo
CAUSA: Healthcheck fallando
Eureka consulta /actuator/health y recibe error (BD caída, dependencia fallida). Revisa que el endpoint Actuator esté expuesto y que todas las dependencias del servicio estén UP.
Hostname del contenedor irresoluble
CAUSA: prefer-ip-address: false en Docker
El Gateway recibe "a3f7b2c1d0e9:8082" que no puede resolver. Solución: prefer-ip-address: true en todos los microservicios cuando corren en Docker.
Instancias duplicadas en el registry
CAUSA: instance-id no único
Al escalar, múltiples instancias tienen el mismo instance-id. Solución: usar random.uuid en el instance-id: ${app.name}:${port}:${random.uuid}.