Spring Cloud Eureka: descubrimiento de servicios en microservicios
En una arquitectura de microservicios, cada servicio necesita comunicarse con otros. El problema es que en un entorno dinámico —donde los servicios escalan horizontalmente, se reinician o se mueven entre nodos— no puedes codificar direcciones IP fijas. Necesitas un mecanismo que permita a los servicios encontrarse entre sí sin conocer de antemano su ubicación.
Eso es exactamente lo que hace Spring Cloud Eureka: proporciona un registro central donde cada servicio publica su nombre y su dirección, y donde cualquier otro servicio puede consultar esa información para establecer comunicación.
¿Por qué necesitas un registro de servicios?
Imagina tres instancias del order-service arrancando en puertos aleatorios en distintos nodos. El payment-service necesita llamar al order-service. Sin service discovery, el payment-service tendría que conocer de antemano las URLs de las tres instancias, actualizarlas cuando cambian y gestionar él solo el balanceo de carga.
Con Eureka:
- Cada instancia del
order-servicese registra en Eureka al arrancar. - Cuando el
payment-servicenecesita llamar alorder-service, consulta a Eureka y obtiene la lista de instancias disponibles. - Spring Cloud LoadBalancer elige una instancia y realiza la llamada.
- Si una instancia muere, Eureka la elimina del registro tras detectar que dejó de enviar heartbeats.
┌─────────────────────┐
│ Eureka Server │
│ (Service Registry) │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
│ registra │ consulta │ registra
▼ ▼ ▼
order-service:8081 payment-service:8082 user-service:8083
order-service:8084 (llama a order-service usando Eureka)
order-service:8085Eureka Server: el registro central
El Eureka Server es una aplicación Spring Boot dedicada a mantener el registro de servicios. Lo creas con una dependencia y una anotación.
Dependencias
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>Con el BOM de Spring Cloud en dependencyManagement:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Clase principal
Añade @EnableEurekaServer a la clase de arranque:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}Configuración
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
# El servidor no debe registrarse a sí mismo ni buscar otros registros
register-with-eureka: false
fetch-registry: false
server:
# Tiempo de espera antes de expulsar instancias sin heartbeat (en ms)
eviction-interval-timer-in-ms: 5000
# Umbral de renovación para el modo de autopreservación
renewal-percent-threshold: 0.85Con esto el servidor arranca en el puerto 8761. Puedes abrir http://localhost:8761 en el navegador para ver el dashboard de Eureka, que muestra todos los servicios registrados y su estado.
Modo de autopreservación
Eureka entra en modo de autopreservación cuando detecta que un número inusualmente alto de instancias ha dejado de enviar heartbeats. En lugar de expulsarlas inmediatamente (asumiendo que podrían ser problemas de red temporales), mantiene las entradas en el registro durante un tiempo adicional.
En desarrollo esto puede resultar confuso porque servicios muertos siguen apareciendo. Puedes desactivarlo en local:
eureka:
server:
enable-self-preservation: false # solo para desarrolloEn producción es recomendable dejarlo activado para evitar que fluctuaciones de red provoquen la eliminación masiva de instancias válidas.
Eureka Client: registrar un microservicio
Cada microservicio que quiera participar en el registro —ya sea para publicar su propia dirección o para descubrir otros servicios— necesita la dependencia del cliente.
Dependencias
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>Configuración
server:
port: 8081
spring:
application:
name: order-service # nombre con el que se registra en Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# Intervalo de refresco del registro local (por defecto 30 s)
registry-fetch-interval-seconds: 10
instance:
# Intervalo entre heartbeats (por defecto 30 s)
lease-renewal-interval-in-seconds: 10
# Tiempo máximo sin heartbeat antes de ser expulsado (por defecto 90 s)
lease-expiration-duration-in-seconds: 30
# Usar IP en lugar de hostname (útil en entornos Docker/Kubernetes)
prefer-ip-address: trueCon solo añadir la dependencia y configurar el defaultZone, el servicio se registra automáticamente en Eureka al arrancar. No necesitas ninguna anotación adicional en Spring Boot 3.x; @EnableDiscoveryClient es opcional y ya está habilitado por defecto cuando la dependencia está en el classpath.
¿Qué información publica el cliente?
Al registrarse, el cliente envía:
- Service ID: el valor de
spring.application.name(en mayúsculas en el registro). - Instance ID: identificador único de la instancia (por defecto
hostname:app-name:port). - Host y puerto: la dirección donde escucha el servicio.
- URL de health check: por defecto
/actuator/healthsi Actuator está en el classpath. - Metadatos: pares clave-valor personalizados que otros servicios pueden leer.
Puedes personalizar el instance ID para que sea más legible en entornos Docker:
eureka:
instance:
instance-id: ${spring.application.name}:${random.value}Llamadas entre servicios con balanceo de carga
Una vez que los servicios están registrados, necesitas un cliente HTTP que consulte Eureka y distribuya las peticiones entre las instancias disponibles.
Spring Cloud LoadBalancer
Spring Cloud LoadBalancer es el cliente de balanceo de carga recomendado (reemplaza al deprecado Ribbon). Viene incluido en la dependencia de Eureka Client desde Spring Cloud 2020.x en adelante.
Agrega WebClient o RestClient con soporte de load balancing:
<!-- Para proyectos reactivos -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- LoadBalancer (incluido con eureka-client, pero explícito aquí) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>Configurar WebClient con balanceo
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced // activa la resolución de nombres a través de Eureka
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}La anotación @LoadBalanced intercepta las peticiones y reemplaza el nombre del servicio (por ejemplo order-service) con la URL real de una instancia elegida por el LoadBalancer.
Usar el cliente en un servicio
@Service
public class PaymentService {
private final WebClient webClient;
public PaymentService(WebClient.Builder webClientBuilder) {
// Usar el nombre del servicio tal como aparece en Eureka
this.webClient = webClientBuilder.baseUrl("http://order-service").build();
}
public Mono<OrderDto> getOrder(Long orderId) {
return webClient
.get()
.uri("/orders/{id}", orderId)
.retrieve()
.bodyToMono(OrderDto.class);
}
}Cuando se ejecuta webClient.get().uri("/orders/123"), Spring Cloud LoadBalancer consulta el registro de Eureka, obtiene la lista de instancias activas de order-service y elige una usando la estrategia configurada (round-robin por defecto). La petición se reenvía a http://IP:PUERTO/orders/123.
RestClient con balanceo (Spring Boot 3.2+)
Si prefieres el cliente síncrono moderno:
@Configuration
public class RestClientConfig {
@Bean
@LoadBalanced
public RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
}@Service
public class PaymentService {
private final RestClient restClient;
public PaymentService(RestClient.Builder builder) {
this.restClient = builder.baseUrl("http://order-service").build();
}
public OrderDto getOrder(Long orderId) {
return restClient
.get()
.uri("/orders/{id}", orderId)
.retrieve()
.body(OrderDto.class);
}
}Cambiar la estrategia de balanceo
Por defecto el LoadBalancer usa round-robin. Puedes cambiarlo por random de forma global o por servicio:
@Configuration
@LoadBalancerClient(name = "order-service", configuration = RandomLoadBalancerConfig.class)
public class LoadBalancerConfig {}
public class RandomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}Alta disponibilidad: clúster de Eureka
En producción, un solo Eureka Server es un punto único de fallo. La solución es ejecutar múltiples instancias que se repliquen entre sí.
Con dos nodos (eureka1 y eureka2), cada uno apunta al otro como peer:
application-eureka1.yml:
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true # sí se registra, pero con sus peers
fetch-registry: true
service-url:
defaultZone: http://eureka2:8762/eureka/application-eureka2.yml:
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka2
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka1:8761/eureka/Los clientes se configuran apuntando a ambos nodos:
eureka:
client:
service-url:
defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/Si un nodo cae, el otro sigue atendiendo registros y consultas. Los clientes tienen una caché local del registro, por lo que incluso si pierden conexión con Eureka temporalmente, pueden seguir resolviendo servicios durante un tiempo.
Eureka con Spring Cloud Gateway
El artículo sobre Spring Cloud Gateway explica cómo configurar el Gateway para enrutar a instancias descubiertas dinámicamente en Eureka usando el esquema lb://.
La combinación Gateway + Eureka elimina la necesidad de conocer puertos ni IPs: el Gateway consulta Eureka, obtiene instancias disponibles y las usa como destino de las rutas.
# Dentro del Gateway
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service # Eureka resuelve esto
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1Metadatos personalizados
Puedes añadir metadatos a cada instancia para que otros servicios tomen decisiones de enrutamiento basadas en ellos:
eureka:
instance:
metadata-map:
version: "2.1.0"
region: "us-east"
zone: "zone-a"Y leer esos metadatos desde otro servicio:
@Service
public class ServiceDiscoveryService {
private final DiscoveryClient discoveryClient;
public ServiceDiscoveryService(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
public List<ServiceInstance> getInstancesByVersion(String serviceName, String version) {
return discoveryClient.getInstances(serviceName)
.stream()
.filter(instance -> version.equals(instance.getMetadata().get("version")))
.toList();
}
}Esto es útil para implementar canary deployments: enrutar solo un porcentaje del tráfico a instancias con la etiqueta version: "2.1.0".
Health checks con Actuator
Spring Boot Actuator expone un endpoint /actuator/health que Eureka puede usar para verificar el estado real de cada instancia, en lugar de depender solo del heartbeat.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>Activa la integración en la configuración del cliente:
eureka:
client:
healthcheck:
enabled: true # usa /actuator/health para reportar estado a Eureka
management:
endpoints:
web:
exposure:
include: health, info
endpoint:
health:
show-details: alwaysCon esto, si la base de datos de tu servicio se desconecta y /actuator/health devuelve DOWN, Eureka marcará esa instancia como OUT_OF_SERVICE y dejará de enviarle tráfico hasta que se recupere, sin necesidad de reiniciar el servicio.
Despliegue con Docker Compose
Un entorno local completo con Eureka Server y dos microservicios clientes:
version: "3.8"
services:
eureka-server:
image: my-eureka-server:latest
ports:
- "8761:8761"
environment:
- SPRING_PROFILES_ACTIVE=default
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8761/actuator/health"]
interval: 10s
timeout: 5s
retries: 5
order-service:
image: my-order-service:latest
environment:
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
- EUREKA_INSTANCE_PREFER_IP_ADDRESS=true
depends_on:
eureka-server:
condition: service_healthy
deploy:
replicas: 2 # dos instancias para probar balanceo de carga
payment-service:
image: my-payment-service:latest
environment:
- EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
- EUREKA_INSTANCE_PREFER_IP_ADDRESS=true
depends_on:
eureka-server:
condition: service_healthyCon prefer-ip-address: true, las instancias se registran con su IP de contenedor en lugar del hostname, que en Docker puede no ser resolvible por otros contenedores.
Resumen
Spring Cloud Eureka resuelve el problema del descubrimiento dinámico en arquitecturas de microservicios. Los conceptos clave son:
- Eureka Server: registro central donde los servicios publican su nombre y dirección. Uno o más nodos en clúster para alta disponibilidad.
- Eureka Client: cualquier microservicio que se registre en el servidor al arrancar y envíe heartbeats para mantenerse activo.
- Spring Cloud LoadBalancer: cliente HTTP que consulta Eureka para resolver nombres de servicios a instancias reales, con balanceo round-robin o random.
@LoadBalanced: anotación que activa la resolución vía Eureka enWebClient.BuilderoRestClient.Builder.- Health checks: integración con Actuator para que Eureka refleje el estado real de la instancia, no solo si está viva.
- Modo de autopreservación: protección ante fluctuaciones de red que impide expulsar instancias masivamente cuando el problema puede ser transitorio.
La trinidad Eureka + Spring Cloud Config + Spring Cloud Gateway cubre los tres pilares de infraestructura para microservicios con Spring: descubrimiento, configuración centralizada y punto de entrada único.
