Mostrando entradas con la etiqueta service discovery. Mostrar todas las entradas
Mostrando entradas con la etiqueta service discovery. Mostrar todas las entradas

miércoles, 11 de agosto de 2021

Apache Camel: Service Discovery con Consul

Hoy vamos a ver como utilizar la funcionalidad de Service Discovery en un proyecto de Apache Camel, pero esta vez utilizando Consul de HashiCorp. Ya hemos visto esta misma funcionalidad pero con Eureka Netflix, aqui,. Por lo que este nuevo ejemplo no se diferenciará mucho.

Pero antes, averiguemos un poco más de Consul. A igual que Eureka es una herramienta que nos permite el registro de servicios de forma centralizada, permitiendo el descubrimiento de los mismos a otros clientes sin necesidad de conocer su dirección real, solo con un identificador. Pero además puede servirnos para almacenamiento de información o de la configuración a utilizar por los distintos clientes. Pero sobre este último apartado no veremos nada concreto en este ejemplo. 

Lo primero que vamos a hacer es levantar Consul. Para el caso de las pruebas podemos levantar una única instancia de Consul a través de un simple comando de docker. Pero nosotros lo haremos un poco más complejo, ya que Consul también puede ser levantado como un cluster de servidores, donde uno de ellos es el principal. Para llevar a cabo esta aproximación, crearemos un docker compose que nos permita levantar varias instancias de Consul y dejarlas activas para el ejemplo. Aquí abajo tenemos el docker compose y si accedemos a la URL http://localhost:8500 podremos acceder a su interfaz gráfica. 

#https://github.com/consul/consul/blob/master/docker-compose.yml
version: '3'

x-consul-container: &consul-container
  image: consul:latest
  networks:
    - consul-demo

x-consul-server: &consul-server
  <<: *consul-container
  command: "agent -server -retry-join consul-server-bootstrap -client 0.0.0.0"

services:
  consul-server-1:
    hostname: consult-server-1
    container_name: consult-server-1
    <<: *consul-server

  consul-server-bootstrap:
    hostname: consul-server-bootstrap
    container_name:   consul-server-bootstrap
    <<: *consul-agent
    ports:
      - "8400:8400"
      - "8500:8500"
      - "8600:8600"
      - "8600:8600/udp"
    command: "agent -server -bootstrap-expect 2 -ui -client 0.0.0.0"

networks:
  consul-demo:

El siguiente paso será crear un micro servicio con Apache Camel. Este micro servicio será muy básico y se conectará como cliente a Consul y registrará las distintas rutas en el. De esta forma y a través de un identificador, será consumibles por otros clientes del mismo servidor.  

Para ello, por un lado añadimos las siguientes dependencias, además de las normales para un micro servicio:

  • spring-boot-starter-actuator: Nos permite exponer rutas para la verificación del estado y los datos de la aplicación. 
  • camel-consul-starter: Nos permite preconfigurar Consul en nuestra aplicación.
Aparte indicaremos las siguientes propiedades en el fichero de configuración:
  • management.endpoints.web.exposure.include: Nos permite indicar rutas de monitorización a gestionar por Spring Actuator.
  • camel.cloud.consul.enabled: Habilitar el uso de Consul.
  • camel.cloud.consul.service-host: Indicar el servidor donde se encuentra ubicado Consul. Esta propiedad será la encargada de setear el tag service address al servicio en Consul. 
  • camel.cloud.consul.url: Para indicar la ruta completa donde se encuentra desplegado Consul.
Y ahora deberemos crear las rutas a registrar en el registro de Consul. Por cada una de las rutas, indicaremos cual será el identificador y otros meta datos asociados al servicio. Esto lo podremos hacer a través de la clase ServiceRegistrationRoutePolicy y el método routeProperty que nos permitirá indicar dichos datos. 

@Component
public class BookMockRouter extends RouteBuilder {
    private static Map<Integer, Book> books = new HashMap<>();
    static {
	books.put(1, new Book(1, "Dune", "Frank Herbert"));
	books.put(2, new Book(2, "The stars my destination", "Alfred Bester"));
	books.put(3, new Book(3, "Ender's game", "Orson S. Card"));
    }

    @Override
    public void configure() throws Exception {
	ServiceRegistrationRoutePolicy policy = new ServiceRegistrationRoutePolicy();

	rest().get("book").produces(MediaType.APPLICATION_JSON_VALUE).route()
		.routeId("mockClientGetAll")
		.routeProperty(ServiceDefinition.SERVICE_META_NAME, "bookGetAll")
		.routeProperty(ServiceDefinition.SERVICE_META_PORT, "9092")
		.routeProperty(ServiceDefinition.SERVICE_META_PATH, "api/book")
		.routePolicy(policy)
		.bean(BookMockRouter.class, "getAll(})").marshal().json();

	rest().get("book/{id}").produces(MediaType.APPLICATION_JSON_VALUE)
		.route().routeId("mockGetById")
		.routeProperty(ServiceDefinition.SERVICE_META_NAME, "bookGetById")
		.routeProperty(ServiceDefinition.SERVICE_META_PORT, "9092")
		.routeProperty(ServiceDefinition.SERVICE_META_PATH, "api/book/")
		.routePolicy(policy)
		.bean(BookMockRouter.class, "getById(${header.id})").marshal().json();
    }

    public Collection<Book> getAll() {
	return books.values();
    }
    public Book getById(final Integer id) {
	return books.get(id);
    }
}

Con este simple servicio podremos comprobar dos cosas. Primero que si invocamos la ruta http://localhost:9092/api/book, veremos el listado de libros asociados a la primera de las rutas. Y segundo, que si accedemos de nuevo a Consul, podremos ver como tenemos dos servicios más registrados. Cliqueando sobre los mismos podremos ver los detalles que hemos indicado en el código fuente. 

Para terminar el ejemplo y ver que funciona no solo la parte del registro de aplicaciones de forma dinámica en Consul. Sino que también funciona el descubrimiento de estos servicios, crearemos a continuación otro micro servicio con Apache Camel que hará uso del método serviceCall para invocar a Consul. 

Este nuevo servicio será incluso más sencillo, puesto que solo necesitará las dependencias para usar Consul en una aplicación de Apache Camel. Y en cuanto a propiedades, solo necesitará dos específicas:

  • camel.cloud.consul.enabled: Habilitar el uso de Consul.
  • camel.cloud.consul.service-discovery.url: Indicar la ruta donde se encuentra ubicado Consul.
Y ahora a crear la ruta para descubrir e invocar a nuestro servicio anterior:

@Component
public class BookServiceRouter extends RouteBuilder {
    @Override
    public void configure() throws Exception {
	rest("/serviceCall").get().produces(MediaType.APPLICATION_JSON_VALUE)
		.route().removeHeader(Exchange.HTTP_URI).serviceCall("bookGetAll")
		.convertBodyTo(String.class).log("Body: ${body}").end();

	rest("/serviceCall/{id}").get().produces(MediaType.APPLICATION_JSON_VALUE)
		.route().removeHeader(Exchange.HTTP_PATH)
		.setHeader(Exchange.HTTP_PATH, simple("${header.id}"))
		.removeHeader(Exchange.HTTP_URI).serviceCall("bookGetById")
		.convertBodyTo(String.class).log("Body: ${body}").end();
    }
}

Cada uno de los nuevos servicios, invoca a un servicio diferente a través del método serviceCall. Indicando el identificador del servicio a consultar, Consul nos generará la ruta asociada a dicho servicio y podremos invocarlo. Esa ruta se compone en base a los meta datos indicados en el otro micro servicio.  

Y con esto, hemos aprendido un poco más del funcionamiento de Apache Camel y otro magnifico Service Discovery. Como siempre, todo el código lo podéis ver aquí. 

sábado, 12 de diciembre de 2020

Apache Camel: Service Discovery con Spring Cloud Netflix

Ya hemos visto bastante de Apache Camel, y hoy veremos como realizar Service Discovery a través de la librería de Spring Cloud Netflix. Pero antes como siempre veremos varios conceptos para entender mejor luego el ejemplo.

Debemos ubicarnos en un entorno de creación y utilización de Microservicios. Y como, si creamos Microservicios y los desplegamos a través de contenedores en la nube, sus localizaciones de red (IPs y direcciones) pueden ser dinámicas. Y por tanto nos pueden llevar a que la configuración que tengamos sobre la ubicación de los mismos no sea válida siempre. 

De ahí surge la necesidad del Service Discovery, o lo que es lo mismo, detección automática de dispositivos y servicios ofrecidos en una misma red. De esta forma, siempre tendremos la configuración correcta sobre los Microservicios disponibles. 

Para llevarlo a cabo necesitamos de un Service Registry que será un servidor encargado de recoger la información sobre los Microservicios: su ubicación, estado, etc. Y los Microservicios actuarían como los clientes de este Service Registry. Hay diferentes implementaciones de Service Registry y nosotros utilizaremos Netflix Eureka. 

Para llevar a cabo el Service Discovery se pueden utilizar varios patrones, y nosotros vamos a elegir el Client‑Side Discovery Pattern. Donde será el  cliente el encargado de registrarse en el Service Registry. 

Una vez asentadas las bases, podemos empezar con nuestro ejemplo. El cual consta de 3 aplicaciones:

  • Un Service Registry realizado solo con Spring Boot
  • Un Service Client realizado con Spring Boot, el cual tendrá un recurso que nos devolverá valores. 
  • Un Service Client realizado con Apache Camel, el cual se conectará al Service Registry y al otro Service Client dinámicamente. 
Empecemos por el Service Registry. Será la parte más sencilla, básicamente un proyecto de Spring Boot con la dependencia spring-cloud-starter-netflix-eureka-server y la anotación @EnableEurekaServer.

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(final String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

Eso sí, al ser una prueba de concepto en local y con un único Service Registry, tendremos que modificar el fichero de configuración de la aplicación para evitar errores de esta configuración y trazas excesivas. Y es debido a que el Service Registry está preparado para que funcione en un cluster y por tanto al iniciarse intenta buscar otras instancias y registrarse en las mismas. Por ello el fichero de configuración quedará de la siguiente forma:

server.port=8761

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

El siguiente paso será crear un cliente con Spring Boot que se conecte al Service Registry y que tenga un recurso denominado /books que devuelva información. Esta aplicación se denominará discovery-client.

Por último, procederemos a crear nuestro Microservicio con Apache Camel, el cual conectaremos al Service Registry y con el cual también invocaremos al Microservicio discovery-client. Para llevar a cabo todo esto nos basaremos en las siguientes librerías de Spring Boot y Spring Cloud. El resto serán las librerías habituales de Apache Camel. 

<dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-spring-cloud-netflix-starter</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      <version>2.2.4.RELEASE</version>
</dependency>

A nivel de configuración tendremos que indicar, cuál será el nombre de nuestra aplicación (necesario a la hora de registrarse en el servidor Eureka) y donde se encuentra el Service Registry. Recordemos que vamos a utilizar el patrón Client‑Side Discovery, por tanto debe ser el cliente el que haga saber al servidor que está disponible. 

spring.application.name=app-camel-eureka-client
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/

Si ya arrancamos nuestra aplicación y accedemos al servidor Eureka en la ruta http://localhost:8761. Podremos ver que los dos Microservicios se encuentran disponibles y aparte algo de información sobre el propio servidor. 


Con esto ya podemos comprobar que nuestra infraestructura funciona, y gracias al servidor ubicar en cada momento a nuestros Microservicios. Ahora vamos a ver como podemos invocarlos dinámicamente desde Apache Camel, y vamos a verlo de dos formas diferentes. 

Para realizar la primera invocación dinámica vamos a hacer uso del componente de Apache Camel Service Call. A través del cual podremos hacer invocaciones a servicios remotos que estén registrados en un Service Registry. Tendremos que indicar el nombre del servicio y su recurso en este caso discovery-client y books. 

@Override
public void configure() throws Exception {
    restConfiguration().component("servlet").bindingMode(RestBindingMode.json);

    rest("/serviceCall").get().produces(MediaType.APPLICATION_JSON_VALUE).route()
    .serviceCall("discovery-client/books?bridgeEndpoint=true")
    //Is it becouse http endpoint produces a Stream as the body and once the stream is read, it is no longer available.
    .convertBodyTo(String.class)
    .log("Body: ${body}").unmarshal().json();
}

De esta forma no tendremos que configurar la URL del Microservicio a invocar, solo debemos conocer cuál es su identificador en el Service Registry. 

También podremos realizar esta llamada a través de una instancia de DiscoveryClient, utilidad de Spring Boot. El cual dado un identificador de un Microservicio, nos indicará su URI y otros valores asociados a ella. 

public static final String URL_DISCOVER_BOOKS = "%s/books?bridgeEndpoint=true";

@Autowired
private DiscoveryClient dClient;

@Override
public void configure() throws Exception {
    restConfiguration().component("servlet").bindingMode(RestBindingMode.json);

    rest("/discoveryCall").get().produces(MediaType.APPLICATION_JSON_VALUE).route()
    .to(String.format(URL_DISCOVER_BOOKS, dClient.getInstances("discovery-client").get(0).getUri()))
//Is it becouse http endpoint produces a Stream as the body and once the stream is read, it is no longer available. .convertBodyTo(String.class) .log("Body: ${body}").unmarshal().json(); }

Como siempre podéis ver que con Apache Camel, Spring Boot y todos los starters podemos realizar grandes cosas y fácilmente. Si quereis podeis ver todo el código aquí.