Introducción a la gestión de Servicios Web con Spring Cloud y Netflix OSS

5
21980
  1. Introducción
  2. Entorno
  3. Spring Cloud Config
  4. Spring Cloud Netflix Eureka
  5. Spring Cloud Netflix Zuul
  6. Servicios Rest de ejemplo
  7. Conclusiones
  8. Repositorios
  9. Referencias

En este tutorial vamos a ver una introducción a Spring Cloud y Netflix OSS como herramientas para implementar algunos de los patrones más comunes utilizados en sistemas distribuidos.

1. Introducción

Ante modelos distribuidos es muy común toparnos con aplicaciones que frente a un creciente número de servicios afronten nuevos retos en lo referente a su administración y sincronización. En tal sentido, algunas de las dudas más comunes con las que nos podemos topar suelen ser:

  • ¿Cómo me puedo asegurar que todo mis servicios están configurados correctamente?
  • ¿Como puedo saber qué servicios se despliegan y dónde?
  • ¿Cómo puedo mantener actualiza la información del enrutamiento de todos los servicios?
  • ¿Cómo puedo prevenir las fallas por servicios caídos?
  • ¿Cómo puedo verificar que todos los servicios están en funcionamiento?
  • ¿Cómo puedo asegurarme cuales son los servicios expuestos externamente?

Para brindar una solución a estos escenarios Spring.io nos ofrece un conjunto de componentes que integrados con las herramientas de Netflix OSS nos permite desarrollar de una manera fácil y rápida aplicaciones que implementen algunos de los patrones más comúnmente usados en sistemas distribuidos.

Algunos de estos patrones suelen ser:

  • La configuración distribuida.
  • El registro y auto-reconocimiento de servicios.
  • Enrutamiento.
  • Llamadas servicio a servicio.
  • Balanceo de carga.
  • Control de ruptura de comunicación con los servicios.
  • Clusterización.
  • Mensajería distribuida.

Dado que son muchas las posibilidades que nos brindan Spring y Netflix, en este tutorial nos enfocaremos en un ejemplo con los siguientes componentes:

  • Spring Cloud Config: Para gestionar de manera centralizada la configuración de nuestros servicios utilizando un repositorio git.
  • Netflix Eureka: Para registrar los servicios en tiempo de ejecución en un repositorio compartido.
  • Netflix Zuul: Como servidor de enrutamiento y filtrado de peticiones externas. Zuul utiliza Ribbon para buscar servicios disponibles y enruta las solicitudes externa a una instancia apropiada de los servicios.

Para visualizar como se vinculan estos componentes podemos fijarnos en la siguiente imagen:

Para construir un ejemplo sencillo que involucre a todos estos componentes vamos a configurar un servidor con cada uno de ellos y un par de servicios «cliente» que comprueben su funcionamiento.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.3
  • Maven – Versión: 3.3.9
  • Java – Versión: 1.8.0_112
  • Spring Boot – Versión: 1.5.1.RELEASE
  • Spring Cloud – Versión: 1.3.0.M1

3. Spring Cloud Config

Lo primero que vamos a configurar será nuestro servidor Spring Cloud Config desde el que nuestros servicios obtendrán un archivo de propiedades. Para crear nuestro servidor nos aprovecharemos de las bondades de maven para importar las dependencias necesarias y las de Spring Boot para tener un servidor autocontenido.

En este orden de ideas, lo primero que haremos será crear nuestro proyecto maven donde nuestro pom.xml debería tener la siguiente forma:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudConfig-Server</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>
 
 <properties>
  <java.version>1.8</java.version>
 </properties> 
 
 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
   </dependencies>
 </dependencyManagement>
 
 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config-server</artifactId>
  </dependency>
 </dependencies>
 
<repositories>
 <repository>
  <id>spring-milestones</id>
  <name>Spring Milestones</name>
  <url>https://repo.spring.io/libs-milestone</url>
  <snapshots>
  <enabled>false</enabled>
  </snapshots>
 </repository>
</repositories>
 
</project>

Dado que nuestro Servidor de configuración es una aplicación Spring Boot necesitaremos un Main y en él incluiremos la anotación @EnableConfigServer que lo habilitará como servidor Spring Cloud Config.

package com.autentia.spring.cloud.config.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableAutoConfiguration
@EnableConfigServer
public class ApplicationConfigServer {

 public static void main(String[] args) {
 SpringApplication.run(ApplicationConfigServer.class, args);
 }

}

El último paso que debemos tener en cuenta es incluir un archivo de configuración llamado application.yml que ubicaremos en la carpeta /src/main/resources/ y en el que configuraremos el nombre de la aplicación, el puerto en el que se ejecutará y el repositorio git donde se almacenarán los archivos de configuración de nuestros servicios.

# Component Info
info:
 component: SpringConfig-Server

# HTTP Server
server:
 port: 8888
 
spring:
 # Spring Cloud Config Server Repository
 cloud:
  config:
   server:
    git:
     uri: https://github.com/jmangialomini/Spring.Cloud.Config.Server

 # Spring properties 
 profiles: 
  active: dev

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Spring Config ejecutamos

mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando correctamente con nuestro browser http://localhost:8888/health la cual nos debe reportar el estado actual de nuestro servidor.

{
 "status": "UP"
}

4. Spring Cloud Netflix Eureka

El segundo servidor que incorporaremos para el soporte de nuestros servicios será el de Eureka, con el incorporaremos la capacidad de descubrir automáticamente los servicios e instancias que vayamos incorporando en tiempo de ejecución. Para ello y al igual que el servidor anterior utilizaremos Maven y Spring Boot.

En tal sentido, nuestro pom.xml debería quedar de la siguiente manera:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudNetflix-EurekaServer</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>
 
 <properties>
  <java.version>1.8</java.version>
 </properties>
 
 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka-server</artifactId>
  </dependency>
  
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
 
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
 
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
  </dependency>
 </dependencies>
 
 <repositories>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/libs-milestone</url>
   <snapshots>
   <enabled>false</enabled>
   </snapshots>
   </repository>
  </repositories> 
</project>

Paso siguiente a la inclusión de las dependencias necesarias para habilitar nuestro servidor Eureka, debemos crear nuetro ApplicationEurekaServer.java e incluir la anotación @EnableEurekaServer .

package com.autentia.spring.cloud.netflix.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class ApplicationEurekaServer {

 public static void main(String[] args) throws Exception {
 SpringApplication.run(ApplicationEurekaServer.class, args);
 }

}

Para configurar nuestro servidor incluiremos un archivo application.yml dentro de la carpeta /src/main/resources/, y en el que configuraremos el nombre de la aplicación, el puerto donde se ejecutará y le indicaremos que no debe publicarse en otro servidor eureka con la propiedad registerWithEureka en false.

# HTTP Server
server: 
 port: 8761

# Eureka Configuration Properties
eureka:
 client:
  registerWithEureka: false
   fetchRegistry: false
 server:
  waitTimeInMsWhenSyncEmpty: 0

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Eureka ejecutamos:

mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando correctamente con nuestro browser http://localhost:8761/health la cual nos debe reportar el estado actual de nuestro servidor.

{
 "description": "Spring Cloud Eureka Discovery Client",
 "status": "UP"
}

También podemos navegar la interfaz gráfica de Eureka (http://localhost:8761/) y validar los servicios que se vayan incorporando.


5. Spring Cloud Netflix Zuul

Como el último de los servidores de soporte para nuestros servicios web vamos a configurar los servicios de Zuul como puerta de enlace para los servicios que queramos publicar.

Para ello y al igual que en los anteriores utilizaremos Maven y Spring Boot donde el pom.xml debería lucir de la siguiente manera:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring.cloud</groupId>
 <artifactId>SpringCloudNetflix-ZuulServer</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>
 
 <properties>
  <java.version>1.8</java.version>
 </properties>
 
 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>
 
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-zuul</artifactId>
  </dependency>
 
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
 
 </dependencies>
  <repositories>
   <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/libs-milestone</url>
    <snapshots>
    <enabled>false</enabled>
    </snapshots>
   </repository>
  </repositories> 
</project>

Para habilitar el servicio Zuul en nuestra clase Main incluiremos la anotación @EnableZuulProxy.

package com.autentia.spring.cloud.netflix.zuul;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.stereotype.Controller;

@SpringBootApplication
@Controller
@EnableZuulProxy
public class ApplicationZuul {
 
 public static void main(String[] args) {
 new SpringApplicationBuilder(ApplicationZuul.class).web(true).run(args);
 }
}

Finalmente y al igual que los otros servidores incluiremos un application.yml para configurar nuestro servidor en el que debemos destacar el enrutado para las peticiones a nuestro servicio de público de ejemplo que identificamos con su Service Id:  public-restservice.

#Component Info
info:
 component: Zuul-Server
 
#Spring Application Name 
spring:
 application:
  name: Zuul-Server 
 
#Server Port
server:
 port: 8765 

#Endpoints 
endpoints:
 restart:
  enabled: true
 shutdown:
  enabled: true
 health:
  sensitive: false

#Zuul routes active 
zuul: 
 routes:
  public-restservice: 
   path: /public/**
   serviceId: public-restservice
 
#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${server.port} 

#Ribbon Activation
ribbon:
 eureka:
  enabled: true

Para probar nuestra aplicación vamos a la consola y en la carpeta de nuestro servidor Zuul ejecutamos:

 mvn spring-boot:run

Una vez iniciado nuestro servidor podemos probar que todo esté funcionando validando que se haya registrado contra nuestro servidor Eureka anteriormente iniciado.

6. Servicios Rest de prueba con Spring Boot

Finalmente y para poder probar cómo se sincroniza toda nuestra solución vamos a crear un par de servicios web que tomen un puerto aleatorio y se registren automáticamente contra nuestro servidor Eureka.

Al igual que con los servidores utilizaremos Maven y Spring Boot para facilitar nuestra implementación. Entre las dependencias que incluiremos para los servicios estarán las del Starter de Eureka como cliente y el Starter Web para habilitarlos como controladores REST.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.autentia.spring</groupId>
 <artifactId>SpringCloudNetflix-Public-RestService</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
 </parent>

 <properties>
  <java.version>1.8</java.version>
 </properties>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
 
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config</artifactId>
    <version>1.3.0.M1</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
  </dependency>
  
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>
 
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
 
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
  </dependency>
 </dependencies>

 <repositories>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/libs-milestone</url>
   <snapshots>
   <enabled>false</enabled>
   </snapshots>
   </repository>
 </repositories> 
 
</project>

Dado que es una aplicación Spring Boot necesitaremos un Main con la variante de incluir la anotación @EnableDiscoveryClient que permitirá que nuestros servicios se suscriban automáticamente en el servidor Eureka.

 

package com.autentia.spring.cloud.netflix;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class ApplicationRestService {

 public static void main(String[] args) {
  SpringApplication.run(ApplicationRestService.class, args);
 }
}

Para habilitar un ejemplo para nuestros servicios incluiremos una clase que responda al mapeo /example:

package com.autentia.spring.cloud.netflix;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceExample {
 
 @Value("${rest.service.cloud.config.example}")
 String valueExample = null; 
 
 private static Logger log = LoggerFactory.getLogger(ServiceExample.class);

@RequestMapping(value = "/example")
 public String example() { 
 
 String result = "{Empty Value}"; 
 if(valueExample.equals(null)){
 
 log.error("PublicRestService - Called with errors property rest.service.cloud.config.example is empty"); 
 
 }else{ 
 log.info("PublicRestService - Called with this property: (rest.service.cloud.config.example:"+valueExample+")");
 result = valueExample;
 }
 return result; 
 }
}

Finalmente y para diferenciar a nuestros servicios incluiremos un archivo de configuración para cada uno de ellos.

Application.yml para el servicio público:

#Application Name
spring:
 application:
  name: Public-RestService

#Component Info 
info:
 component: Public-RestService

#Port - If 0 get random port
server: 
 port: 0

#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
 
#Service Registration Method 
cloud:
 services:
  registrationMethod: route
 
#Disable HTTP Basic Authentication
security:
 basic:
  enabled: false

Y el Application.yml para el servicio privado:

#Application Name
spring:
 application:
  name: Private-RestService

#Component Info 
info:
 component: Private-RestService

#Port - If 0 get random port
server: 
 port: 0

#Eureka Instance ID
eureka:
 instance:
  instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
 
#Service Registration Method 
cloud:
 services:
  registrationMethod: route
 
#Disable HTTP Basic Authentication
security:
 basic:
  enabled: false

Para probar nuestros servicios vamos a la consola y en la carpeta de cada uno de ellos ejecutamos:

mvn spring-boot:run

Una vez iniciados los servicios podremos comprobar que todo esté funcionando en nuestro servidor Eureka, en él podremos visualizar cuántas instancias de nuestros servicios se han registrado y bajo qué puerto podemos invocarlos.

Nota: Para hacer más rigurosa nuestra prueba podemos iniciar una segunda instancia de alguno de nuestros servicios y comprobar la cantidad de instancias que están disponibles del mismo servicio.


Finalmente y para probar que el servicio público sea accesible desde el exterior a través de nuestro servidor Zuul  podemos visitar http://localhost:8765/public/example y comprobar que nuestra configuración de servicios esté respondiendo correctamente.

7. Conclusiones

Hemos visto cómo se pueden usar los componentes de Spring Cloud y Netflix OSS para simplificar la gestión y sincronización de servicios web.

8. Repositorios

9. Referencias

5 COMENTARIOS

  1. Buen tutorial, disculpa porque me sale Down cuando levanto el Config Server apuntando al mismo repo del tuto ? Tendría que hacer algo más ?

    Gracias

  2. Hola, muy bueno el tutorial, clarísimo. Solo déjame decirte que tienes pequeño un error.

    En el controlador ServiceExample, la linea @Value(«{rest.service.cloud.config.example}») debe ser cambiada por @Value($»{rest.service.cloud.config.example}»), de otra manera no pudes leer correctamente las properties que tienes en github.

    Muchas gracias.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad