Durante los pasados meses, Fon, mi empresa, ha organizado una experiencia de comunidades tecnológicas, que consistía en asignarnos un par de horas semanales para trabajar en un proyecto en grupo para investigar sobre tecnologías nuevas. En la comunidad a la que pertenecía, la comunidad de Backend decidimos hacer una prueba de concepto con los productos del stack de Netflix. El siguiente artículo intenta explicar lo que aprendí de uno de ellos, Eureka.
Eureka
¿Que soluciona?
Eureka es un servicio basado en REST que se utiliza para el descubrimiento y registro automático de servicios, lo que facilita las tareas de escalabilidad, balanceo de carga y tolerancia fallos en una arquitectura de microservicios. Netflix lo utiliza en AWS cloud, que es donde están desplegados sus microservicios. Eureka tiene dos componentes cliente y servidor.
¿Como funciona?
Explicado a grandes rasgos, tendríamos un servidor Eureka en nuestra arquitectura de microservicios y cada microservicio sería cliente de este servidor. Al arrancar cualquiera de los microservicios, se comunicaría vía REST con el servidor diciéndole donde está (IP y puerto), quién es (definido en la propiedad spring.application.name del fichero aplication.yml), donde esta la url donde consultar su estado interno (endpoint health de actuator por defecto), donde está su página de home, etc. A partir de ese momento cada microservicio se comunica con Eureka indicando que sigue vivo, con lo que la documentación de Eureka llama «heartbeats» que se traduciría como latidos. Por defecto los heartbeats se envían cada 30 segundos. Si el servidor Eureka no recibe está comunicación durante un tiempo configurable por parte de una instancia de microservicio (90 segundos por defecto), esta se elimina de la lista de instancias disponibles. Los microservicios utilizan la lista con información del resto de microservicios para comunicarse entre ellos, a través del balanceador de carga software llamado Ribbon, el cual está también basado en la tecnología desarrollada por Netflix. Tanto servidor como clientes tienen una caché con la información del resto de servicios disponibles. Un microservicio no se muestra como disponible hasta que su información está en las caches de servidor y clientes. Es posible definir información adicional para ser compartida entre los microservicios añadiéndola mediante configuración. Eureka como servidor incluye un panel de monitorización con información útil como por ejemplo la lista de servicios registrados en el servidor, cuales son los servidores réplica sí los hay, etc.
Modos de configurar un servidor Eureka
Un servidor Eureka se puede configurar para existir solo en nuestra arquitectura (standalone mode) o para funcionar con otras instancias de Eureka (peer awareness). En este segundo modo, las distintos servidores Eureka se registran entre ellos replicando su información, con lo que consigue mayor disponibilidad y resistencia a fallos. En el ejemplo que mostraré en la siguiente sección, se explica como configurar dos instancias de Eureka en modo peer awareness. Actualmente, Peer awareness es la manera de configurar Eureka más común en proyectos en producción. Cuando un servidor comienza a recibir información sobre los microservicios, esta información es replicada en el resto de servidores de los que tiene conocimiento. Si esta coordinación falla, la información es enviada en el siguiente heartbeat. Los servidores Eureka utilizan el mismo mecanismo para hablar entre ellos que cuando hay una comunicación Eureka cliente-servidor. Cada microservicio incluye en su fichero de properties la lista de servidores Eureka con los que se puede comunicar y se conectan al primer servidor de la lista por defecto.
Modo self-preservation
Eureka puede entrar en modo self-preservation para evitar eliminar instancias registradas cuando se experimentan fallos de red pero los microservicios gozan de buena salud. Eureka entra en este modo, si los heartbeats durante los últimos 15 minutos (valor por defecto), son menos que los heartbeats esperados, valor que se definen en un límite configurable. La fórmula sería para calcular este límite es:
- Número de instancias * Número de heatbeats esperados por minuto * porcentaje esperado
Si por ejemplo de servicios que tenemos es 4, y se esperan 2 heatbeats por minuto (1 cada 30 segundos) y el porcentaje esperado es 0.85 tendríamos: 4 * 2 * 0.85 = 7 heatbeats por minuto.
Para definir estos valores entran en juego las siguientes propiedades de la configuración de Eureka:
- eureka.instance.leaseRenewalIntervalInSeconds: define cada cuanto se envía los heartbeats al servidor Eureka
- eureka.server.renewalPercentThreshold: defíne el límite de instancias que tienen que renovar su información por debajo del cual se entra en modo self preservation.
- eureka.server.renewalThresholdUpdateIntervalMs: cada cuanto tiempo se calcula si Eureka tiene que entrar en self-preservation mode.
Ejemplo
Lo más sencillo para entender como funciona Eureka es ver un ejemplo. Podéis encontrar el código del ejemplo en github. En el ejemplo vamos a crear dos proyectos SpringBoot que serán servidores Eureka en modo peer awareness y un cliente Eureka. Este es un ejemplo pensado para ser ejecutado en una máquina, para desplegar Eureka en AWS se necesita configuración extra que podéis encontrar aquí. Para crear los proyectos recomiendo usar spring initializr que es una web que nos permite generar proyectos SpringBoot definiendo previsamente sus dependencias en maven o gradle:
Para el primero de los servidores necesitamos crear un proyecto SpringBoot con la dependencia del starter de Eureka server. Después necesitamos añadir la anotación @EnableEurekaServer en el método main de la clase Application.
package com.fon.community; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Y añadir la siguiente configuración en el fichero de propiedades application.yml:
server: port: 8761 spring: application: name: eureka-peer eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://user:password@peer2:8762/eureka/ security: basic: enabled: true user: name: user password: password
Está configuración contiene entre otras cosas, donde se va encontrar la réplica del servidor (el otro servidor que veremos a continuación), definida mediante la propiedad eureka.client.serviceUrl.defaultZone. Adicionalmente vamos a securizar el servidor, esto se hace definiendo como true la propiedad security.basic.enable y definiendo las propiedades username y password de security.basic. Para crear el otro servidor tenemos que hacer exactamente lo mismo pero definiendo otro puerto, hostname y como servidor replica indicar el primer servidor que hemos creado.
server: port: 8762 spring: application: name: eureka-peer eureka: instance: hostname: peer2 client: serviceUrl: defaultZone: http://user:password@peer1:8761/eureka/ security: basic: enabled: true user: name: user password: password
Para que el ejemplo funcione hay que añadir estas líneas al fichero /etc/hosts
127.0.0.1 peer1
127.0.0.1 peer2
Por último debemos crear otro proyecto que contendrá un cliente, que tendrá una sencilla API REST. Cuando lo arranquemos se registrará en ambos servidores Eureka. Requiere incluir la dependencia con el starter de Eureka. La clase Application requiere la anotación @EnableDiscoveryClient. También hemos añadido un sencillo endpoint en la ruta «/hello»:
package com.fon.serverclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @EnableDiscoveryClient @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @RestController class HelloController { @RequestMapping("/hello") public String serviceInstancesByApplicationName() { return "HelloWorld!"; } } }
Y añadir la siguiente configuración en el fichero de propiedades application.yml:
server: port: 0 spring: application: name: server-client eureka: client: serviceUrl: defaultZone: http://user:password@peer1:8761/eureka/, http://user:password@peer2:8761/eureka/
Por último, arrancando los 3 proyectos y accediendo al panel de Eureka de los servidores (http://localhost:8761 y http://localhost:8762) podremos ver que los cada servidor tiene como replica al otro servidor y además el cliente está registrado. Para acceder a los paneles son requeridas las credenciales que hemos definido en la configuración. Esta información no se mostrará de manera inmediata, hay que esperar a que los distintos elementos se registren y descubran entre si.
Buenas prácticas
Peer awareness
Es recomendable utilizar Eureka en modo peer awareness en lugar de standalone en entornos de alta disponibilidad. Funcionando en este modo los clientes se comunican con el primer servidor incluido en la lista de servidores Eureka de su configuración. Si este servidor deja de estar disponible pasan a comunicarse con el siguiente en la lista y si más tarde se recupera, vuelven al primero. Adicionalmente cada cliente contiene una caché con la información acerca del resto de instancias que continua disponible, si hay fallos de comunicación con el servidor Eureka. Ambos mecanismos hacen que Eureka sea muy robusto cuando funciona utilizando varias replicas.
Health check
Por defecto, Eureka usa los heartbeats para saber si el cliente está activo, pero no conoce su estado interno. Eureka se puede configurar para enviar el estado de la aplicación determinado por la librería actuator. Para hacer esto hay que aplicar la siguiente configuración:
eureka: client: healthcheck: enabled: true
Comunicación entre servicios
Entender como configurar Eureka
Eureka es altamente configurable y y es conveniente entender como funciona en detalle, si se va utilizar en un proyecto en producción. Para ampliar información sobre cómo configurar Eureka, he encontrado muy útil el siguiente artículo donde el autor explica en detalle las principales propiedades de configuración de Eureka.