Registro y descubrimiento de servicios con Spring Cloud Netflix

Escrito por picodotdev el .
java planeta-codigo programacion spring
Enlace permanente Comentarios

Los microservicios en una aplicación con una arquitectura de microservicios son efímeros, se crean, se escalan para atender picos de mayor demanda, pueden desaparecer por problemas de la red de comunicaciones que no es fiable y volverse a crear en una ubicación diferente. Por esta naturaleza efímera es necesario un servicio con el cual los clientes de los microservicios puedan encontrarlos y que los microservicios utilizan para registrarse cuando se inician.

Este servicio de registro y descubrimiento es esencial y crítico en una aplicación orientada a microservicios ya que sin él los clientes no pueden encontrar los servicios que necesitan. Dado que es un servicio esencial es necesario que esté siempre disponible y para ello es recomendable crear un cluster de servicios de registro y descubrimiento para que en caso de que una instancia de este servicio falle estén disponibles otras instancias para los clientes.

Una implementación de registro y descubrimiento es Consul, Consul es un servicio externo creado por Hashicorp. Spring Cloud entre sus proyectos, Spring Cloud Netflix, proporciona una implementación con Eureka de servicio de registro y descubrimiento que se pueden embeber en una aplicación de Spring Boot.

Para crear un servidor Eureka con Spring y Spring Boot hay que crear una aplicación con las dependencias adecuadas y la anotación @EnableEurekaServer para habilitar el inicio del servidor de registro y descubrimiento. Además, establecer las propiedades de configuración adecuadas para que el cluster de servidores Eureka se forme. Este microservicio es el primero que ha de iniciarse en una aplicación orientada a microservicios.

Utilizando Gradle las dependencias y la anotación @EnableEurekaServer a añadir a la clase principal de la aplicación son las siguientes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
plugins {
    id 'application'
    id 'org.springframework.boot' version '2.1.12.RELEASE'
}

application {
    mainClassName = 'io.github.picodotdev.blogbitix.springcloud.discoveryserver.Main'
}

dependencies {
    implementation(platform('org.springframework.boot:spring-boot-dependencies:2.1.12.RELEASE'))
    implementation(platform('org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR2'))
    
    // Spring
    def excludeSpringBootStarterLogging = { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    implementation('org.springframework.boot:spring-boot-starter', excludeSpringBootStarterLogging)
    implementation('org.springframework.boot:spring-boot-starter-web', excludeSpringBootStarterLogging)
    implementation('org.springframework.boot:spring-boot-starter-log4j2', excludeSpringBootStarterLogging)
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server', excludeSpringBootStarterLogging)

    runtimeOnly('com.google.code.gson:gson:2.8.5')
    runtimeOnly('com.fasterxml.jackson.core:jackson-databind:2.9.6')
    runtimeOnly('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')
}
build.gradle
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.springcloud.discoveryserver;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootApplication
@EnableEurekaServer
public class Main implements CommandLineRunner {

	@Override
	public void run(String... args) {
	}

	public static void main(String[] args) throws Exception {
		SpringApplication application = new SpringApplication(Main.class);
		application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
		SpringApplication.run(Main.class, args);
	}
}
Main.java

La propiedad de configuración principal para formar el cluster es eureka.client.serviceURL.defaultZone donde se especifica una lista hostnames donde están los servidores de registro y descubrimiento. Para dar a cada servidor en local un nombre de dominio distinto he usado el servicio de DNS xip.io que resuleve el nombre de dominio a la dirección IP indicada en el propio nombre de dominio, así ds1.127.0.0.1.xip.io se resuelve a 127.0.0.1 que es la dirección para la propia máquina local al igual que ds2.127.0.0.1.xip.io y ds3.127.0.0.1.xip.io. El servicio de xip.io evita tener que crear en el archivo de hosts local una correspondencia entre nombre de hostname y la dirección IP de loopback de la propia máquina local.

En el archivo de configuración hay tres perfiles distintos que varían algunas propiedades según sea el perfil que se active al iniciar la instancia del servicio. En el perfil ds1 el puerto donde se inicia el servicio es 8761, con el perfil ds2 el servicio se inicia en el puerto 8762 y con ds3 en el 8763, además se cambia el hostname para que la instancia sepa cual es.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
server:
  port: ${port:8761}

spring:
  application:
    name: discoveryserver

eureka:
  environment: default
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://ds1.127.0.0.1.xip.io:8761/eureka/,http://ds2.127.0.0.1.xip.io:8762/eureka/,http://ds3.127.0.0.1.xip.io:8763/eureka/

---
server:
  port: 8761

spring:
  profiles: ds1

eureka:
  instance:
    hostname: ds1.127.0.0.1.xip.io

---
server:
  port: 8762

spring:
  profiles: ds2

eureka:
  instance:
    hostname: ds2.127.0.0.1.xip.io

---
server:
  port: 8763

spring:
  profiles: ds3

eureka:
  instance:
    hostname: ds3.127.0.0.1.xip.io
application.yml

Los comandos para arrancar tres instancias de servidor de registro y descubrimiento utilizando varios perfiles de configuración de Spring son los siguientes.

1
2
3
$ ./gradlew discoveryserver:run --args="--spring.profiles.active=ds1"
$ ./gradlew discoveryserver:run --args="--spring.profiles.active=ds2"
$ ./gradlew discoveryserver:run --args="--spring.profiles.active=ds3"
run-discoveryserver.sh

Estando disponible el servicio de registro y descubrimiento ya se puede iniciar el servicio de configuración. Con estos dos servicios de infraestructura iniciados los que sería un servicio de la aplicación ya puede iniciarse que consiste en este caso en obtener una referencia de una instancia del servicio de configuración registada en el servicio de registro y descubrimiento, con esta referencia obtiene su configuración y se inicia.

1
2
$ ./gradlew configserver:run

run-configserver.sh
1
2
$ ./gradlew service:run --args="--port=8080"
$ ./gradlew service:run --args="--port=8081"
run-service.sh

Una vez iniciados los servidores de descubrimiento en la página dashboard de cualquiera de ellos cambiando el puerto de la dirección http://ds1.127.0.0.1.xip.io:8761/ se observan varias propiedades como la lista de servidores del cluster, las réplicas registradas y disponibles y los servicios registrados con su ubicación y puerto. En este caso hay tres instancias del servicio de registro y descubrimiento, una de servidor de configuración y dos instancias de un servicio.

Servicios y su estado registrados en el servicio de registro y descubrimiento

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew discoveryserver:run --args="--spring.profiles.active=ds1"

Este artículo forma parte de la serie spring-cloud:

  1. Datos de sesión externalizados con Spring Session
  2. Aplicación Java autocontenida con Spring Boot
  3. Configuración de una aplicación en diferentes entornos con Spring Cloud Config
  4. Información y métricas de la aplicación con Spring Boot Actuator
  5. Registro y descubrimiento de servicios con Spring Cloud y Consul
  6. Aplicaciones basadas en microservicios
  7. Registro y descubrimiento de servicios con Spring Cloud Netflix
  8. Servicio de configuración para microservicios con Spring Cloud Config
  9. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  10. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  11. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  12. Balanceo de carga y resilencia en un microservicio con Spring Cloud Netflix y Ribbon
  13. Proxy para microservicios con Spring Cloud Netflix y Zuul
  14. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  15. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  16. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  17. Trazabilidad en microservicios con Spring Cloud Sleuth
  18. Implementar tolerancia a fallos con Resilience4j
  19. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  20. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  21. Microservicios con Spring Cloud, Consul, Nomad y Traefik
  22. Trazabilidad en servicios distribuidos con Sleuth y Zipkin
Comparte el artículo: