Serverless: llevando Cloud y Microservicios a la máxima potencia (1/2)

Todavía recuerdo, cuando a comienzos de 2018 leí un post identificando las que iban a ser las principales tendencias del año en lo que respecta a microservicios. Allí estaba Serverless, un concepto que conocía ligeramente, pero en el que no había profundizado.

Dentro de estas tendencias, Serverless no era el titular de un apartado, sino que ocupaba la sección dedicada a Event Driven Architectures (EDA).

Durante los últimos 4 años que llevamos trabajado con arquitecturas de microservicios (al menos cuando empezaron a llegar a España) los modelos de comunicación síncronos se han probado cuanto menos insuficientes para gestionar el abanico de situaciones de comunicación que tenemos en una arquitectura de microservicios. Problemas de latencias, gestión de circuit breaking anidados, patrones de orquestación…

En este contexto es donde surgen las, probablemente, dos más importantes tendencias actuales en el mundo de microservicios.

Una, que también se menciona entre las tendencias de 2018, es service-mesh (con soluciones como Istio, Envoy y Linkerd) quizá más enfocada al mundo síncrono, aunque no incompatible con el asíncrono.

La otra abrazaba completamente en mundo asíncrono como solución a esos problemas que son habitualmente inherentes a la naturaleza síncrona. Y esta es la arquitectura basadas en eventos (EDA).

Dentro de esta, las soluciones serverless también conocidas como FaaS (Function as a Service) son las que probablemente mejor representan los principios de este tipo de arquitecturas, aun siendo posible también diseñar una EDA sin utilizar serverless.

El concepto Serverless se volvió popular en 2015, después de que Amazon lanzara AWS Lambda en noviembre de 2014 y posteriormente su API Gateway en julio de 2015.

Actualmente, el evento más conocido es el serverlessconf que se celebra desde 2016. En su site podéis encontrar vídeos de las diferentes charlas de las conferencias hasta la fecha.

Cuando hablamos de soluciones serverless, a la mayoría de la gente se le vendrán a la cabeza plataformas como la ya mencionada AWS Lambda o la más reciente Google Cloud Functions como soluciones más populares. Estas soluciones tienen las características de ser propietarias y estar en la nube pública.

Más allá de esas soluciones más populares, en nuestra realidad del día a día existen muchas compañías que no pueden (o no quieren) trabajar con la nube pública o con vendor-locking. En estos casos su opción para poder trabajar con Serverless se traduce en soluciones on-premise.

Actualmente la Cloud Native Computing Foundation ha hecho un gran trabajo en este área y tiene identificados una gran cantidad de soluciones a todos los niveles (plataformas, frameworks, herramientas…) referentes al mundo serverless.

Todo ese batiburrillo de soluciones se plasma en la siguiente imagen:

Pero la labor de la CNCF no queda solo ahí. De hecho la parte más relevante probablemente sea el trabajo que están haciendo en torno a establecer diversos estándares, por ejemplo en buenas prácticas, ciclo de vida de las funciones…

En general, la mayor parte de la literatura existente se refiere principalmente a las soluciones en nube pública (incluyendo el post en el blog de Martin Fowler), ya que además de ser las más populares, también es cierto que son las más maduras.

No obstante, se puede ver un creciente ecosistema de soluciones on-premise o instalables, que si bien pueden no alcanzar el nivel de madurez de las soluciones de nube pública, solventan algunas situaciones que estas aún no pueden.

El futuro de serverless

Muchos se sorprenderán de que haga esta afirmación, pero desde hace ya tiempo, con microservicios y cloud, toda la industria del TI está tendiendo, posiblemente sin saberlo, hacia serverless.

Puede que muy pocas compañías estén ya usando serverless, sea en cloud pública o on-premise, o que lo usen en una muy pequeña cantidad de sus proyectos. Pero la realidad es que todos los patrones, soluciones y decisiones que están aplicando los llevan por un camino que en última instancia termina en serverless.

¿Por qué entonces no se ha producido ya este salto a serverless? La realidad es que todavía hoy en día tenemos problemas para que muchas organizaciones abandonen las arquitecturas monolíticas y migren a microservicios.

Si además muchas al hacerlo no se demuestran lo suficientemente maduras para llevar a cabo este cambio, ¿cómo íbamos a ser capaces de hacer la migración a serverless? Los cambios requieren tiempo. Y cambios tan grandes como este, aún más.

Al igual que ocurre con las arquitecturas de microservicios no se trata únicamente de un cambio tecnológico, sino también de un cambio cultural, procedimental y organizacional.

Resulta mucho más lógico dar un paso intermedio hacia microservicios y asimilar así los principios y prácticas y, una vez adquirida la madurez necesaria, dar el siguiente paso. Desde mi perspectiva es mucho más duro el cambio de monolito a microservicios que lo que será de microservicios a serverless.

Esto es probablemente por la larga historia que tienen las compañías con las soluciones monolíticas y que, lógicamente, se traduce en un mayor arraigo a las mismas. Por supuesto también ayuda que serverless tenga más en común con microservicios, ya que ambas no dejan de ser soluciones Cloud y por tanto muchos patrones de diseño y problemáticas son comunes.

Esto hace que muchas herramientas ya nos resulten conocidas y que nos podamos aprovechar de un ecosistema más maduro como es el de microservicios.

Pero volvamos al inicio, ¿qué me hace creer ‘que toda la industria del TI está tendiendo hacia serverless’? Si analizamos los patrones, buenas prácticas, recomendaciones y ventajas de microservicios nos encontraremos que todos están en serverless. Que si intentamos llevarlos un poco más allá, exprimirlos más, lo que obtendremos será serverless.

Desde esta perspectiva, las arquitecturas de microservicios son un paso intermedio desde los monolitos hacia serverless. Analicemos estos puntos en común entre serverless y microservicios:

  • Despliegue independiente: los microservicios deben poder desplegarse de forma independiente para así evitar bloqueos y dependencias entre los mismos evitando situaciones donde no podemos desplegar un microservicio debido a que otro no ha terminado su funcionalidad, o tiene otra incompatible con la versión que queremos desplegar o uno de sus tests está fallando.

También las funciones deben poder desplegarse de forma independiente, si no queremos vernos en situaciones similares. Así mismo esto nos permitirá agilizar los procesos de desarrollo ya que las unidades funcionales que tenemos que desarrollar y probar son más pequeñas pudiendo así reducir el tiempo necesario para poner valor en producción.

Si no cumplimos este principio, para los microservicios podemos vernos en una situación donde para un cambio en uno tenemos que desplegar, por ejemplo, otros cuatro, aumentando así tiempos y complejidad; pero imaginaros el orden de magnitud que podríamos alcanzar si estamos hablando de funciones.

  • Despliegue automatizado: recogiendo el testigo del punto anterior, la motivación en este caso, es la complejidad operativa y el tiempo requerido para desplegar digamos decenas de microservicios de forma manual. Lógicamente lo mismo aplica de forma más profunda cuando estamos hablando de cientos de funciones.
  • Desacoplados: en microservicios desacoplamos la funcionalidad para que cada uno pueda funcionar y, como indicamos en el primer punto, desplegarse de forma independiente.

Lo mismo aplica a las funciones: cada una debe funcionar de forma aislada, por tanto debe estar desacoplada de las demás. Este aislamiento, aún mayor en serverless, nos permite a su vez detectar los bugs de forma más sencilla.

De la misma forma, esto nos permite elegir el lenguaje de programación o la base de datos más adecuada para la funcionalidad a abordar por el microservicio. Esto mismo aplicará a la hora de escoger el lenguaje de programación de la función, pudiendo estar cada una en un lenguaje diferente.

  • Principio de responsabilidad única: cada microservicio debe hacer una única cosa y hacerla muy bien. Con serverless los tiempos de cómputo son menores (algunas plataformas limitan el tiempo de ejecución de la función), por tanto esa ‘cosa’ debe tener un alcance menor, siendo así más ‘única’.

Así mismo existen otras características, estas ya más técnicas u organizativas, que aplican a microservicios y que deberemos aplicar también a serverless:

  • Equipos de desarrollo independientes: cada equipo debe estar a cargo de un conjunto de microservicios controlando su ciclo de vida completo. Varios equipos no deberían desarrollar sobre el mismo microservicio ya que puede dar lugar a problemas de coordinación y bloqueos, perdiendo así su agilidad característica.

De la misma forma estos problemas pueden ocurrir si varios equipos realizan modificaciones sobre la misma función, aunque en este caso suele ser más difícil por el limitado tamaño de la misma.

No obstante, la función deberá tener un equipo propietario y cada equipo gestionar diversas funciones que podrían corresponderse, por ejemplo, con un mismo dominio o subdominio de negocio.

  • Nuestro microservicio/función se puede caer en cualquier momento: una de las restricciones/consideraciones que introdujeron las soluciones cloud es la característica efímera de nuestra ‘instancia’.

De esta forma debemos desarrollar y arquitecturizar considerando que la instancia pueda desaparecer en cualquier momento y que podemos perder cualquier tipo de información almacenada en la misma.

Esto, que es una situación posible que debemos gestionar en las arquitecturas de microservicios, es una garantía en serverless, ya que una vez ejecutada la función es prácticamente seguro que el entorno donde se ha ejecutado desaparezca.

La mayoría de soluciones utilizan este entorno para ejecuciones subsiguientes de la función a no ser que no se realicen llamadas durante un periodo de tiempo que suele ser de minutos, pero eventualmente desaparece.

  • Nuestro servicio / función debe no tener estado: como consecuencia del punto anterior no debemos almacenar estados internos a la aplicación.

Por ejemplo, en microservicios el modelo de comunicación más habitual suele ser REST + JSON cumpliendo los principios RESTful entre los cuales está que el API no debe tener estado.

Lo mismo ocurre con las funciones, que por definición deben ser sin estado.

  • Centralización de logs: la centralización de logs es una necesidad de las arquitecturas de microservicios ya que cuando estamos hablando de decenas de microservicios, cada uno de ellos potencialmente con varias instancias necesitamos una forma sencilla de consultar dichos logs para, por ejemplo, detectar bugs.

Es más, la característica efímera de los contenedores hace que si no recolectamos dichos logs, ante una caída del contenedor, no pudiéramos consultarlos. Una vez más, esto aplica en mayor medida a serverless, ya que tendremos muchas más funciones, cuyas unidades de ejecución desaparecerán una vez terminadas.

  • Monitorización: la monitorización nos permite detectar situaciones anómalas a través del consumo de recursos. Situaciones como, por ejemplo, un código mal desarrollado que genera un bucle infinito, o que empieza a reservar memoria hasta provocar la caída de la aplicación…

Todas estas situaciones aplican hasta a la más mínima funcionalidad, siendo entonces necesario monitorizar nuestras funciones igual que monitorizamos nuestros microservicios.

  • Trazabilidad de petición: una de las problemáticas de los microservicios inherentes a su naturaleza distribuida es la trazabilidad de peticiones: saber que diversas peticiones se desencadenan en nuestro sistema como consecuencia de una petición exterior así como los tiempos involucrados en cada una, latencia de red….

Esto es clave para detectar problemáticas de latencias así como para resolver bugs. En el momento en que podemos realizar composición de funciones, o incluso en el que un procesamiento esté compuesto por varias de ellas (aunque estas dejen su salida o estados generados en una cola), es necesario poder recorrer el camino a través de las diferentes piezas que nos llevaron hasta el estado final.

Finalmente existen otras ventajas de los microservicios inherentes a su naturaleza Cloud que también serán llevadas a su máxima expresión por serverless. Veamos a cuáles nos referimos:

  • Escalado de grano fino: si tenemos un monolito equivalente a 20 microservicios, a su vez equivalentes a 200 funciones, está claro que la unidad de escalado es en cada arquitectura cada vez más pequeña, por tanto el escalado será más elástico. Esto nos permitirá ajustar más los costes.
  • Velocidad de escalado: cuando se habla de escalado la perspectiva principal suele estar en si este puede ser automático, pero uno de los factores que no se suele tener en cuenta al hablar de él, es su velocidad.

Una de las problemáticas habituales en microservicios Java son los tiempos de arranque. Pongamos que tu microservicios tarda unos 2 minutos en arrancar (en Java y limitando los recursos del contenedor es un tiempo normal, optimista incluso en algunos casos), si tienes un pico de carga que tarda 15 minutos o una hora en llegar no tendrás problema.

Tus dos instancias (recordemos que lo tendrás en HA) notarán la presión poco a poco y escalarán. Con suerte, cuando llegues a lo más alto del pico ya tendrás las suficientes instancias levantas.

¿Pero qué ocurre si la carga de peticiones de tu sistema se multiplica por 10 en tan solo 2 minutos? La respuesta es que no serás capaz de levantar las instancias a tiempo.

Entre que se detecta la carga y se levantan las nuevas instancias, el pico de carga habrá alcanzado a las ya existentes y, como no son capaces de aguantar dicha carga, se tendrán que encolar peticiones, muchas de ellas dando timeout, incluso pudiendo llegar a tirar las instancias.

Resultado: has tenido un pico de usuarios que podrían haber hecho triunfar tu negocio, pero todos han abandonado tu web porque cuando han entrado no funcionaba o iba muy lenta.

Un ejemplo de este tipo de situaciones son las campañas promocionales en televisión, por ejemplo las del tipo ‘vuelos a Mallorca a 15€ solo durante la próxima hora’ o las de ‘de 22:00 a 23:00 te ahorras el IVA en las compras a través de nuestra web’ muchas de las cuales se producen durante las rebajas o fechas del tipo ‘black friday’.

Así, esta problemática no se producirá en serverless, pues los tiempos de escalado siempre son en el rango de segundos.

  • Pagas por lo que usas: esta es una de las principales ventajas de las soluciones Cloud.

En microservicios tienes modelos de facturación en base a instancias que estás ejecutando o recursos que consumen las mismas (CPU, RAM, I/O) pero esto no se ajusta al uso real que haces de la infraestructura, si no que muchas veces pagas por tener esas instancias disponibles aunque no reciban llamadas.

Por contra el modelo serverless se ajusta más al uso real, ya que la facturación o consumo es en base a las peticiones que recibes, de forma que si nadie está usando tu plataforma no pagas por ella.

Esta comparativa resulta más obvia cuando además en microservicios muchas veces necesitas tener un mínimo de 2 instancias disponibles de forma continua para garantizar la alta disponibilidad.

Aquí podemos ver un muy buen ejemplo de cómo la migración de una pequeña aplicación de una solución provisionada a una solución serverless supuso una reducción del 90% en los costes de infraestructura.

  • Cuando no lo necesites lo apagas: continuando con la idea del punto anterior, tu microservicio puede no estar recibiendo ninguna petición por la noche porque, por ejemplo, el grueso de tus clientes operan durante el día.

Sin embargo, debes seguir pagando para tenerlo disponible en caso de que se realice alguna llamada. Pero en serverless el funcionamiento es el opuesto: tus funciones permanecen apagadas hasta que reciben peticiones.

Lógicamente, un modelo como este permite ajustar mucho más los costes pudiendo olvidarnos así de los asociados a la resiliencia.

  • IaaS vs PaaS vs FaaS: otra de las grandes ventajas de las soluciones cloud es que te abstraen de la infraestructura subyacente, de forma que no te tienes que preocupar de cosas como la red, máquinas… ahorrando así en costes operativos y de administración.

En este sentido existen diferentes niveles de abstracción, siendo la comparativa más habitual entre IaaS y PaaS, que se suele solventar en favor de esta última debido a que las soluciones PaaS te abstraen de mayor número de elementos y, por tanto, tienen una menor complejidad.

Este mismo principio aplica a la comparativa PaaS vs FaaS, donde este último nos proporciona el mayor nivel de abstracción de forma que cosas como el escalado, servidores de aplicaciones… nos son totalmente transparentes.

Así conseguimos el objetivo deseado por las dos anteriores aproximaciones: que el equipo de desarrollo se pueda centrar únicamente en la lógica de negocio mientras la plataforma se encarga de la gestión de todos estos elementos utilizando para ello las mejores prácticas y recomendaciones de diseño como los 12 Factors.

Todos estos nuevos conceptos y cambios que las arquitecturas de microservicios están extendiendo a nivel de principios de diseño, patrones de arquitectura, organizativos… es como si fuera un proceso de preparación, de educación, para dar el futuro salto al mundo serverless.

Sumando eso a que como vemos para todos y cada uno de ellos, que caracterizan a las arquitecturas de microservicios y a las soluciones Cloud, Serverless los cumple en mucha mayor medida, todo parece indicar que serverless es la evolución natural de las arquitecturas de microservicios.

Retos

Serverless supone un cambio de arquitectura y de funcionamiento respecto a lo que estamos acostumbrados, como tal deben solventarse ciertas situaciones que son nuevas, inherentes a los cambios que supone:

Arranques en frío

De cara al desarrollador, las soluciones serverless le proporcionan un entorno de ejecución donde solo necesita introducir su código para poder ejecutarlo. Pero por debajo hay una infraestructura de gran complejidad, basada en prácticamente todos los casos en contenedores.

Casi la totalidad de plataformas serverless (existen excepciones como AWS Lambda) consisten en contenedores, diseñados cada uno pensando en un entorno de ejecución, un lenguaje de programación podríamos decir.

Cuando una función es invocada, la plataforma arranca el contenedor correspondiente a su lenguaje, le inyecta el código y le reenvía la petición o evento a la función para su procesamiento.

El principal problema de este proceso es justamente el arranque del contenedor. Ese arranque va a requerir de segundos de tiempo, lo cual puede ser una opción perfectamente válida para cierto tipo de procesamientos asíncronos, pero claramente no es una opción cuando estamos hablando de llamadas síncronas.

Esta penalización, eso sí, no se producirá en todas las llamadas, ya que la mayoría de plataformas serverless reaprovechan los contenedores, es decir, una vez ejecutada la función, no se elimina el contenedor que la llevó a cabo, si no que este permanece vivo (o pausado) durante cierto tiempo para posibles futuras invocaciones de la función.

Openwhisk permite la reutilización del contenedor para diversas ejecuciones de la misma función

La mayoría de plataformas hacen esto de forma automática y con valores predefinidos, pero probablemente en el futuro estas opciones serán configurables.

De esta forma podremos indicar que ciertas funciones que resulten cruciales para nuestra lógica de negocio estén siempre disponibles, el tiempo de vida de un contenedor después de la ejecución de una función… evitando así los problemas de cold starts.

Esto puede hacer pensar al lector que perdemos parte de las ventajas de serverless al tener que mantener el contenedor siempre disponible como en, por ejemplo, una solución de microservicios. Y, en cierta medida, es cierto.

Aún así, una función consume menos recursos que un microservicio, por lo tanto el contenedor hará uso de menos recursos; solo será necesario mantener uno levantado, ya que ante una caida solo debemos enfrentarnos a un cold start de segundos, en contraposición con los microservicios donde debemos tener siempre dos instancias levantadas porque no podemos exponernos a tiempos de arranque de minutos.

Finalmente, hay que entender el volumen de la mejora de este tipo de soluciones dentro de su alcance: soluciones corporativas.

De la misma forma que en un cloud público se saca ventaja de administrar de forma única la solución para todos los clientes, reduciendo así costes, en las soluciones on-premise ocurre lo mismo a nivel corporativo con respecto a las áreas/departamentos/equipos correspondientes.

En este contexto el coste de mantener una serie de contenedores calientes es despreciable. De todas formas, siempre debemos recordar que esto aplica en caso que necesitemos procesamiento síncrono o tiempos bajos de respuesta y que tengamos las peticiones muy distribuidas en el tiempo, recordando siempre que el cold-start aplica solo a la primera petición o si se incrementa el nivel de concurrencia.

Arranques frío en Java

Dentro de este apartado, Java merece una especial mención, siendo el lenguaje más utilizado hoy en día para desarrollo, con una de sus principales características como es la portabilidad a través de diferentes dispositivos gracias al uso JVM. Sin embargo, en el mundo serverless puede ser una mala opción.

Si ya en arquitecturas de microservicios hablamos del coste en tiempos y recursos de tener que levantar una JVM por cada microservicio en comparación con la solución monolítica, estos costes se disparan cuando hablamos de funciones.

En estos casos puede ser una mejor opción optar por lenguajes interpretados (como Javascript o Python) con un menor consumo de recursos y unos mejores tiempos de arranque.

No obstante existen ya soluciones serverless con un claro enfoque al mundo Java como es spring-cloud-function, que nos permite entrar en el mundo serverless a través de nuestro framework de desarrollo Java favorito.

En todo caso, siempre deberemos considerar las características del caso de uso, seleccionando el lenguaje más adecuado, de la misma forma que en microservicios podemos implementar cada uno según las características del lenguaje. Así, una posible estrategia podría ser un análisis del patrón de uso de la aplicación.

Funcionalidades muy utilizadas, que reciben muchas invocaciones o tienen un alto nivel de complejidad pueden ser desarrolladas en Java ya que debido a esto los contenedores se mantendrán mayor tiempo en ejecución por lo que tendremos menos casos de ‘cold-starts’.

Mientras que funcionalidades de uso más puntual pueden ser implementadas en lenguajes interpretados no exponiéndonos así a tanto coste de arranque.

De todas formas, más allá del uso de estas estrategias siempre deberíamos contar con varios contenedores en estado ‘pre-warm’ (como se muestra en la imagen) de forma que ante una ejecución inesperada solo sea necesario inyectar el código y realizar la invocación.

Finalmente, si vemos que las invocaciones de ciertas funciones son a lo largo de todo el día puede que no tenga sentido movernos a serverless ya que no veríamos grandes ventajas en comparación con mantener esa funcionalidad en un microservicio.

No obstante, una gran parte de las aplicaciones, aunque tengan un uso intensivo, mantienen ese uso restringido a franjas de 12 o 16 horas, por lo que ya podríamos aprovechar varias ventajas de una solución serverless.

Esto abre la puerta a otro punto muy interesante, como es el uso de arquitecturas híbridas pero que por alcance excede el objetivo de este artículo, por lo que dejaremos para el futuro.

Hasta aquí esta primera parte en la que hemos divisado el futuro de la industria del TI y cómo ya se están poniendo las bases para que ese avance tenga lugar. Además, hemos visto el que probablemente sea el reto más duro que ahora mismo hay sobre la mesa y diversas estrategias para poder mitigar su riesgo.

En la siguiente parte seguiremos viendo las problemáticas que este tipo de arquitecturas tienen, además de sus más habituales casos de uso.

Foto de jarodriguez

Abraham Rodríguez actualmente desarrolla funciones de ingeniero backend J2EE en Paradigma donde ya ha realizado diversos proyectos enfocados a arquitecturas de microservicios. Especializado en sistemas Cloud, ha trabajado con AWS y Openshift y es Certified Google Cloud Platform Developer. Cuenta con experiencia en diversos sectores como banca, telefonía, puntocom... Y es un gran defensor de las metodologías ágiles y el software libre.

Ver toda la actividad de Abraham Rodríguez

Comentarios

  1. Juan dice:

    Siempre con muy buenos articulos y muy claros. Un placer leerlos. Seria un abuso pedirles que en casos como estos pongan links de referencia para profundizar mas el tema? Tienen algun recomendable para este articulo puntual? Saludos

Escribe un comentario