- Introducción
- Entorno
- Spring Cloud Config
- Spring Cloud Netflix Eureka
- Spring Cloud Netflix Zuul
- Servicios Rest de ejemplo
- Conclusiones
- Repositorios
- 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
- Servidor Spring Cloud Config: https://github.com/jmangialomini/SpringCloudConfig-Server
- Repositorio Git Spring Cloud Config: https://github.com/jmangialomini/SpringCloudConfig-GitRepository
- Servidor Netflix Eureka: https://github.com/jmangialomini/SpringCloudNetflix-EurekaServer
- Servidor Netflix Zuul: https://github.com/jmangialomini/SpringCloudNetflix-ZuulServer
- Servicio Rest Privado: https://github.com/jmangialomini/SpringCloudNetflix-Private-RestService
- Servicio Rest Público: https://github.com/jmangialomini/SpringCloudNetflix-Public-RestService
9. Referencias
- Spring Cloud: http://projects.spring.io/spring-cloud/
- Spring Cloud Config: http://cloud.spring.io/spring-cloud-config/
- Spring Cloud Netflix: http://cloud.spring.io/spring-cloud-netflix/
Hola, kisiera preguntar que servidor de streaming usa netflix,Sldos
Hola Juan,
Por lo que he podido leer sobre la arquitectura de Netflix tienen una capa de servicio hecha a la medida para la distribución y reproducción de su contenido, que en horas pico llega a representar un tercio del tráfico de Internet en USA. Igualmente te adjunto un vínculo donde puedes ver algunos detalles que pueden dar una idea de como es su arquitectura.
https://www.techhive.com/article/2158040/how-netflix-streams-movies-to-your-tv.html
Espero haberte ayudado, un saludo.
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
Buen dia tengo una duda el servidor zuul se comunica con eureka para que el le de el end point disponible del servicio, y despues zuul se va directamente contra el servicio?
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.