Publicaciones

¿Qué son los microservicios?

Los microservicios están de moda. Prometen manejar cantidades enormes de peticiones sin caerse, responder muy rápido y poder desplegar rápidamente nuevas versiones, pero ¿qué son los microservicios?

Los microservicios son una más de las arquitecturas software existentes. Conocida como MSA (MicroServices Architecture), suele verse como un subconjunto de la más conocida Arquitectura Orientada a Servicios o SOA (Services Oriented Architecture).

Estas son las áreas más diferenciadas respecto a la arquitectura SOA:

  • MSA hace mayor hincapié en el bajo acoplamiento entre servicios, que deben ser lo más independientes posible.
  • En la interacción entre servicios se prefiere huir de soluciones complejas como los ESB con inteligencia, en favor de comunicaciones muy simples, habitualmente con REST. Suele expresarse como “tuberías bobas, extremos inteligentes”. También se prefiere minimizar en lo posible las interacciones entre servicios para favorecer la independencia.
  • En lugar de utilizar despliegues manuales, se tiende a la automatización, con una mayor integración con tecnologías de despliegue continuo.
  • Se prefiere la coreografía a la orquestación, aunque ya veremos que esto muchas veces no es así y se utilizan las dos aproximaciones.

Como vemos, MSA detalla y especifica algunos puntos que SOA deja más abiertos. Veremos estos puntos en más detalle.

Microservicios ¿pequeños?

Y ante la pregunta habitual, ¿los microservicios tienen que ser pequeños? La respuesta puede sorprender: No. La división entre microservicios tiene más que ver con la visión funcional o de negocio que con el tamaño en sí. Cada microservicio debe ser una unidad funcional, que tenga sentido en la lógica de negocio. La gran ventaja de esta arquitectura se encuentra en que cada componente sea lo más independiente posible del resto.

Los servicios son débilmente acoplados y su modelo de datos independiente para cada uno. Proporcionan una funcionalidad completa y se autogestionan ellos mismos. Esto es así hasta el punto de que cada microservicio puede estar implementado con un lenguaje diferente. Podríamos encontrarnos un servicio escrito en Java junto a otro escrito en Node.js o PHP, todo depende de lo que sea mejor para ese servicio en concreto. Igualmente ocurrirá con la base de datos: podríamos tener Oracle o MySQL para unos servicios o MongoDB e incluso Redis para otros, según sea más eficiente para el microservicio que se esté implementando.

Esta división puede llevarse también a los equipos de desarrollo. Estos no necesitarán conocer todo el ecosistema y ni siquiera tendrán que tener los mismos skills. Cada microservicio podría ser implementado y mantenido por un equipo completamente diferente.

Esto nos lleva a preguntarnos cómo se comunicarán los microservicios entre sí. Según las teorías más puristas, lo ideal es que los microservicios no se llamen entre sí o al menos que se minimicen las comunicaciones entre ellos. Esto puede llegar a ser poco práctico y complicar mucho nuestra solución, aunque sí trataremos de restringir la comunicación entre componentes para evitar dependencias. Lo más habitual es usar HTTP/REST para las comunicaciones con formatos JSON, aunque también es común utilizar aplicaciones de mensajería ligera como RabbitMQ, esto puede ajustarse a cada caso concreto. Sí es conveniente que todos utilicen el mismo tipo de comunicación.

Orquestación vs Coreografía

Para realizar una tarea será habitual implicar a varios servicios. Existen dos visiones principales sobre cómo realizar la coordinación entre servicios:

Orquestación: Es la opción más clásica. Se basa en que un componente será el que coordine las llamadas a los servicios que necesita de forma secuencial, típicamente mediante llamadas de petición/respuesta. Este componente se encargaría de gestionar los errores.

Coreografía: Es la opción que más encaja con la arquitectura de microservicios, pero también más compleja de gestionar. Los servicios no se llaman entre sí, sino que se utiliza un sistema de eventos, de forma que cuando un servicio termina su tarea, deja un mensaje y todos aquellos servicios suscritos a ese canal son notificados, de forma que puedan realizar su trabajo.

Pongamos como ejemplo una tienda virtual en la que, al terminar una compra, se emite el pedido, se envía un email con la factura y se actualiza la información del usuario para registrar la compra.

En el caso de la orquestación, al terminar la compra se llamaría al servicio de emisión de pedidos, al responder, se llamaría al servicio de facturas y cuando este termine, se llamaría al de actualización de usuarios.

En el caso de la coreografía, los servicios de pedidos, facturas y actualización de usuarios estarían suscritos a un canal de compras. Al terminar la compra se dejaría un mensaje en este canal que sería recibido por todos los servicios suscritos.

La ventaja principal de la coreografía es su bajo nivel de acoplamiento, que permite que, si fuera necesaria otra acción, como añadir puntos de fidelidad, sería tan sencillo como crear el microservicio y suscribirlo al canal, sin necesidad de modificar el servicio de compras. El mayor inconveniente es que la gestión de errores en este sistema se complica y también a la hora de seguir su flujo y sus dependencias, que requieren sistemas específicos de monitorización.

Gestión de microservicios

De forma habitual, los microservicios tienden a crecer en número, lo que provoca dificultades en la gestión. Para facilitar esta tarea, existen algunos componentes comunes en estas arquitecturas:

  • Registro: Proporciona independencia de la ubicación lógica o física.
  • Servidor perimetral: Es el punto de acceso de cualquier llamada externa.
  • Balanceador: Reparte la carga entre los servicios.
  • Servidor de configuración: Proporciona la configuración del sistema.
  • Sistema de tolerancia a fallos: Su objetivo es evitar fallos en cascada.
  • Gestión de logs: Un sistema con el que poder explotar las trazas de los servicios.

Se suele considerar que los servicios de registro, servidor perimetral y balanceador son el conjunto mínimo necesario en una buena arquitectura de microservicios, aunque el resto proporcionan una ayuda muy valiosa.

También se pueden añadir un servidor de autorización para implementar la capa de autenticación y algún sistema de monitorización para comprobar la salud del sistema.

El registro, también llamado servicio de descubrimiento (Registry o Discovery service) es un componente que mantiene una lista de los servicios activos. Cuando un servicio es desplegado, lo primero que hace al arrancar es registrarse. Después, los servicios que lo necesiten, consultaran la lista de instancias existente para un servicio concreto. Este componente se usa tanto de forma interna como externa.

El balanceador (Load balancer) reparte la carga de peticiones entre los servicios disponibles. Para conocer los servicios que están arrancados, consulta al registro.

El registro y el balanceador son dos componentes que pueden estar encapsulados dentro del mismo componente o separados en dos partes. Además, mientras el registro es un componente esencialmente interno (del lado servidor), el balanceador puede existir tanto en el lado cliente como en el lado servidor.

Por ejemplo, el ELB (Elastic Load Balancer) de Amazon incluye estos dos componentes y la parte del balanceador está en el lado del servidor. Por otro lado, en el modelo de Netflix OSS, los componentes están divididos (Eureka para el registro y Ribbon para el balanceador) y el balanceador se encuentra en el lado cliente, comunicando con el registro para obtener la lista y balanceando sin necesidad de llamar al servidor.

El servidor perimetral (Edge service) funciona como un proxy inverso. Es una puerta de entrada única que recibe todas las peticiones y las redirige a los puntos adecuados. También puede ir unido al balanceador y ejecutar sus funciones en el lado del servidor. Al ser el punto de entrada, puede cumplir muchas funciones como seguridad, autenticación, API gateway, monitorización, control de carga para evitar la saturación y servidor de estáticos entre otros.

El servidor de configuración (Configuration service) guarda toda la configuración necesaria para cualquier servicio. Los servicios recuperan la configuración al arrancar. Algunos servidores de configuración mantienen un control de versiones sobre la configuración.

El sistema de tolerancia a fallos (Circuit breaker) es un componente que monitoriza las comunicaciones entre servicios. Cuando detecta algún tipo de error, corta la llamada (la corto-circuita) para evitar que se propague. Normalmente requiere una cantidad de errores en cierto tiempo antes de bloquear el servicio, a no ser que se esté recuperando de un error previo, en el que vuelve a cortar de inmediato.

Debido a la gran cantidad de servicios y su dispersión la gestión de logs es un elemento necesario para poder explotar esta información. Su misión es recoger los logs de todos los servicios, procesarlos y presentar una interfaz para su explotación masiva.

Uno de los mayores casos de éxito en el área de microservicios es el de Netflix. Esta empresa ha decidido liberar parte de sus componentes y lo ha publicado en su plataforma Netflix OSS (Open Source Software). Estos son los principales componentes:

  • Eureka (Registry)
  • Ribbon (Balanceador)
  • Zuul (Servidor perimetral)
  • Hystrix (Tolerancia a fallos y monitorización)
  • Turbine (Monitorización)

Este software suele combinarse con Spring Cloud que aporta el servidor de configuración con control de versiones en GIT y algún gestor de logs, siendo la pila de ELK (Elastic Search, Logstash y Kibana) una de las más comunes.

Hemos dicho anteriormente que Ribbon es un balanceador a nivel de cliente. Ribbon, desde el lado cliente, se conecta a Eureka para obtener la lista de servicios y, ya localmente, implementará el balanceo. Sin embargo, también es posible configurarlo para la parte interna. En este caso se configura Zuul, como punto de entrada de todas las peticiones, para que utilice Ribbon para balancear sobre los servicios internos. En este caso Zuul actúa como cliente, pero obtiene de Eureka la lista de los servicios internos.

Existen otros conjuntos de componentes, como los proporcionados por Kubernetes, e incluso es posible mezclarlos hasta cierto punto.

Desplegando microservicios

El despliegue de los microservicios es una parte primordial de esta arquitectura. Muchas de las ventajas que aportan, como la escalabilidad, son posibles gracias al sistema de despliegue.

Aunque es posible crear una arquitectura de microservicios utilizando únicamente Spring Cloud para el arranque y levantando los servicios a mano, este modelo no sería útil y perdería buena parte de las ventajas de esta arquitectura.

Los microservicios están íntimamente ligados al concepto de contenedores (una especie de máquinas virtuales ligeras que corren de forma independiente, pero utilizando directamente los recursos del host en lugar de un SO completo). Hablar de contenedores es hablar de Docker. Con este software se pueden crear las imágenes de los contenedores para después crear instancias a demanda.

Las imágenes son como plantillas. Constan de un conjunto de capas y cada una aporta un conjunto de software a lo anterior, hasta construir una imagen completa. Por ejemplo, podríamos tener una imagen con una capa Ubuntu y otra capa con un servidor LAMP. De esta forma tendríamos una imagen para ejecutar como servidor PHP. Las capas suelen ser bastante ligeras. La capa de Ubuntu, por ejemplo, contiene algunos los ficheros del SO y otros, como el Kernel, los toma del host.

Los contenedores toman una imagen y la ejecutan, añadiendo una capa de lectura/escritura, ya que las imágenes son de sólo lectura. Dada su naturaleza volátil (el contenedor puede parar en cualquier momento y volver a arrancarse otra instancia), para el almacenamiento se usan volúmenes, que están fuera de los contenedores.

Sin embargo, esto no es suficiente para dotar a nuestro sistema de una buena escalabilidad. Se necesita también una plataforma que gestione los contenedores, y para ello existen soluciones como Kubernetes.

Ya habíamos hablado de este software en el apartado de componentes debido a que proporciona un entorno completo. Además, proporciona también esta parte de despliegue automático, que puede utilizarse con sus componentes o con componentes de otras tecnologías como Spring Cloud+Netflix OSS. Kubernetes nos permite gestionar grandes cantidades de contenedores, agrupándolos en pods. También se encarga de gestionar servicios que estos necesitan, como conexiones de red y almacenamiento, entre otros.

Todavía se puede dar una vuelta de tuerca más, incluyendo otra capa por encima de Docker y Kubernetes: Openshift. En este caso estamos hablando de un PaaS que, utilizando Docker y Kubernetes, realiza una gestión más completa y amigable de nuestro sistema de microservicios. Por ejemplo, nos evita interactuar con la interfaz CLI de Kubernetes y simplifica algunos procesos. Además, nos provee de más herramientas para una gestión más completa del ciclo de vida, como construcción, test y creación de imágenes. Incluye los despliegues automáticos como parte de sus servicios y, en sus últimas versiones, el escalado automático.

Openshift también proporciona sus propios componentes, que de nuevo pueden se mezclarse con los de otras tecnologías.

Ventajas

La principal ventaja que proporciona la arquitectura de microservicios es la facilidad para escalar horizontalmente, su elasticidad. Unido a algún gestor de contenedores, es relativamente sencillo lanzar nuevas instancias de nuestros microservicios para atender grandes picos de demanda o eliminarlas para ahorrar costes cuando la demanda sea baja.

A esto se añade la capacidad para recuperarse de situaciones difíciles, su resiliencia. Al existir varias instancias de los microservicios, la caída de algunas de ellas puede superarse desplegando rápidamente nuevas instancias, incluso de forma automática.

Otra gran ventaja viene dada por el despliegue automático y el bajo nivel de acoplamiento. Permite a las aplicaciones ser más independientes y que, por tanto, puedan ser actualizadas afectando en menor medida al resto del sistema, posibilitando ciclos de entrega más cortos.

También se nos permite poder seleccionar la tecnología más conveniente para cada servicio, al permitir integrar de forma sencilla distintos lenguajes y otras piezas software, como la base de datos. Además, gracias a esta independencia, los cambios de tecnología pueden realizarse con mayor rapidez, mejorando la renovación de las herramientas.

Los dos puntos anteriores favorecen que los equipos de desarrollo sean más independientes y puedan enfocarse en áreas funcionales y tecnológicas más concretas, especializándose en sus áreas y mejorando su productividad y capacidad de innovación.

Inconvenientes

Sin embargo, todo tiene un coste. Con la gran cantidad de microservicios e instancias a desplegar, aumenta considerablemente la complejidad del sistema y su gestión puede dar algún dolor de cabeza. Requerirá un esfuerzo extra en monitorización y gestión de errores.

A diferencia de un sistema monolítico o simplemente más acoplado, la solución no permite ser tan eficiente como en estos sistemas, lo que lleva a necesitar mayor cantidad de recursos hardware para procesar la misma carga de trabajo. Este problema puede verse compensado, en parte, por la facilidad para escalar el sistema, especialmente en entornos de pago por uso de recursos.

Una arquitectura de microservicios tiene implicaciones más allá del propio software. Para aprovechar realmente sus capacidades, requiere la adopción de tecnologías de integración y despliegue continuos, así como la especialización de los equipos de trabajo. También implicará realizar un esfuerzo para la organización del sistema en base a su funcionalidad para la división en microservicios y un extra de configuración y en la parte de sistemas. Es en definitiva un cambio de filosofía que debe calar en los equipos para ser efectivo.

A menudo se tiende a pensar que es una nueva arquitectura a la que hay que adaptarse sí o sí, pero nada más lejos de la realidad. Como en todo, antes de decidir adoptar una arquitectura de microservicios, será necesario evaluar si las ventajas superan a los inconvenientes para nuestro caso concreto y si el esfuerzo merecerá la pena. Si, por ejemplo, la escalabilidad no es un factor demasiado importante, la adopción de una arquitectura MSA podría no ser lo más indicado. 

Conclusión

La arquitectura de microservicios definen una forma de trabajar con grandes aplicaciones más desacoplada y surgen para atender unas demandas muy exigentes en cuanto a alta disponibilidad y capacidad de adaptación y cambio. No tiene por qué ser la mejor solución para nuestro caso. En general, está indicado para grandes aplicaciones, con grandes volúmenes de peticiones (especialmente si se sufren fuertes picos de demanda) y con necesidad de alta disponibilidad. Si esta es la situación, esta arquitectura nos ayudará a ser más ágiles y conseguir atender nuestras necesidades de una forma estructurada y probada.

Conoce al autor

Alejandro Ladera

Alejandro es Analista Senior del área de System Development&Integration, en la práctica de DxD de Deloitte. Está especializado en la tecnología Java, en la que cuenta con 5 certificaciones de Oracle y 8 años de experiencia profesional. Ha trabajado, principalmente, en sistemas de venta del sector de transporte para grandes clientes, con los mayores volúmenes de transacciones en España. También ha llevado a cabo con éxito pruebas de stress automáticas en entornos grandes y complejos, así como mejoras de eficiencia con gran impacto.