Bajando de Serverless a Kubernetes y ahorrando +$43K en AWS
Cambiando de una arquitectura Serverless a una arquitectura basada en Kubernetes y ahorrando +$43K dólares en AWS.

Antes de empezar, un poco de contexto. La infraestructura está alojada en AWS y la arquitectura estaba basada en servicios Serverless:
- API Gateway
- Lambdas (Corrían un servicio de Node bastante simple que recibía una serie de parámetros, los serializaba e insertaba en una base de datos)
Para base de datos se está utilizando RDS con MySQL. La arquitectura a grosso modo se veía tal que así:

Esta arquitectura tenía tres problemas grandes:
- Debido al volumen de peticiones constantes que estábamos recibiendo, el Nº de invocaciones en simultáneo de las Lambdas se nos quedaba corto. Incluso con el incremento del límite a 10K tocábamos techo (por defecto son 1K).
- Por la confección de la Lambda en Node, en el RDS se abrían un Nº muy alto de conexiones, lo que obligaba a subir en recursos el RDS ya que utilizábamos una cantidad de RAM enorme (llegamos a tener una instancia
db.r6i.4xlarge
). - El coste de mantener esto era enorme. En solo una semana se llegó a tener un coste en Lambdas de +$6.5K y AWS daba una previsión de gasto de +$36K. Y todo esto sin contar el dineral de gasto que estaba teniendo el RDS.

Cambiando la arquitectura
Una vez detectados los problemas y cuellos de botella de la arquitectura actual teníamos que pensar como reducir el coste drásticamente y además ser capaces de atender correctamente todo este tráfico:

Barajamos la posibilidad de añadir algún sistema de colas de forma intermedia que nos permitiese invocar Lambdas solo en momentos puntuales pero nuestro volumen no solo fluctúa en picos sino que es constante, 24/7. Y justo uno de los problemas del Serverless es que es muy caro a la hora de atender peticiones de forma recurrente y con un volumen tan alto.
Dado que además la función Lambda era un servicio en Node bastante simple y su refactor para bajarlo a una API no nos llevaría tiempo, decidimos cambiar la arquitectura completa. Para ello:
- Como punto de entrada utilizamos ALB (Application Load Balancer)
- Aprovechamos nuestros clústers de Kubernetes (EKS + Autoscaling Groups) que ya corrían otros servicios.
- En cuanto al servicio de Node, decidimos dividirlo en 2 servicios:
- Una API reescrita en Python con FastAPI que recibía las peticiones y las añadía en un servicio de streaming.
- Un Worker que leía del servicio de streaming e insertaba por lotes los eventos en la base de datos para no saturarla.
- Para el servicio de Streaming vimos varias alternativas pero finalmente optamos por Redis Streams, el cual podíamos, mas adelante, utilizar como caché para otros servicios. Este lo alojamos en Elasticache.
- Finalmente para la base de datos seguimos en RDS con MySQL ya que esta contiene varios TB de datos.

Algunas cosas a destacar e interesantes en la configuración actual de Kubernetes:
- Se han desplegado 2 deployments, uno para la API y otro para el Worker. Tienen un HPA (Horizontal Pod Autoscaling) de:
- API: mínimo 10 Pods y máximo 20 Pods.
- Worker: mínimo 5 Pods y máximo 1o Pods.
- El número de nodos está gestionado por Autoscaler, aunque los límites están entre 1 - 10 nodos dependiendo de la demanda. Por el momento lo habitual parece ser un promedio de ~5-6 nodos.
Con esta nueva arquitectura ya éramos capaces de soportar el tráfico que teníamos. Además hemos solventado dos grandes problemas:
- Los límites de invocaciones recurrentes de las Lambdas. Con Kubernetes tenemos más capacidad de autoescalado.
- La escritura en base de datos al ser ahora por lotes en lugar de directamente, hizo que cayera en picado el Nº de conexiones recurrentes, que a su vez se tradujo en una demanda mucho menor de recursos:

Show me the money!
Tras poco mas de una semana con todo funcionando con la nueva arquitectura, ya podemos empezar a sacar varias conclusiones y sobre todo a hacer números de cuánto estamos ahorrando.
Infraestructura Serverless + RDS:
- API Gateway: +$1.2K con una previsión de gasto de +$7.2K
- Lambdas: +$6.5K con una previsión de gasto en el mes de +$36K.
- RDS: con una instancia tipo
db.r6i.4xlarge
la previsión de coste mensual era de +$1.5K
🔥 Total previsto al mes: +$45K
Infraestructura en K8s + Elasticache + RDS:
- Application Load Balancer: +$160, previsión al mes de ~$650
- Computo:
- EC2: +$130 con una previsión pensando que el cluster siempre tiene el nº máximo de nodos: +$800
- EKS: +$103 con una previsión de +$300
- Redis en Elasticache: Cluster con
t3.medium
con una previsión de gasto de +$103. - RDS con MySQL: De momento hemos reducido a una
db.r6g.xlarge
que tiene una previsión de gasto mensual de +$350
🤑 Total previsto al mes: ~$2K

El ahorro previsto por este cambio de arquitectura es de ~$43K al mes. De hecho posiblemente incluso podamos ahorrar aún más porque aún seguimos evaluando si podemos bajar en recursos alguno de los nodos, tenemos que verificar si podemos ajustar los tipos de instancias y comprobar finalmente Saving Plans, reservas de instancias, etc.
Pero esto, posiblemente, sea ya para otro post 👋