Nuestros orígenes
Como muchas startups, Applivery nació como un “side-project”. Se trataba de un servicio sencillo pero ambicioso, centrado en la distribución de aplicaciones móviles de forma eficaz y simplificada.
Dados sus humildes orígenes, los costes del servicio debían estar muy controlados, por lo que decidimos empezar desplegando las primeras versiones en el proveedor Cloud más asequible y sencillo del momento, DigitalOcean, y utilizar también un sistema de almacenamiento sencillo basado en AWS S3.
El servicio se concibió originalmente como un único monolito cuyos procesos, bases de datos y servicios se ejecutaban en un único droplet.
Sin embargo, aquel proyecto sencillo pero ambicioso se convirtió poco a poco en una plataforma que debía dar soporte a decenas de clientes y miles de usuarios activos diarios. Además, la ambición del proyecto empezó a incorporar numerosas ideas de desarrollo, lo que dio lugar a una extensa hoja de ruta que se extendería durante los 2 años siguientes. Desarrollar un producto de esas dimensiones en un único droplet empezaba a no parecer tan buena idea. Por eso decidimos ampliar el equipo y reconstruir el proyecto desde cero.
En ese momento, decidimos pasar a una arquitectura orientada a microservicios, separando el Frontend de las APIs. Todos los servicios se desplegarían en contenedores Docker a través de un sofisticado pipeline de integración continua apoyado sobre CircleCI. En un ejercicio de simplificación del proceso de transición entre la versión antigua y la nueva, decidimos mantener DigitalOcean como proveedor de computación y DNS, pero aprovechamos la ocasión para externalizar la base datos (MongoDB Atlas), el sistema de colas (CloudAMQP), y el registro de imágenes de Docker (AWS ECR).
Este nuevo sistema de microservicios se puso en funcionamiento a finales de 2019 y hacía uso de Dokku para la orquestación de los contenedores.
Sin embargo, aunque habíamos realizado un gran esfuerzo de reconstrucción y rearquitectura, seguíamos sirviendo todo el tráfico desde una única máquina virtual que hacía de cuello de botella. Por esa razón, poco tiempo después, decidimos dar el salto a Kubernetes.
DigitalOcean seguía siendo el proveedor Cloud más asequible para un servicio administrado de Kubernetes, así que no lo dudamos y migramos el orquestador de contenedores, añadiendo numerosas mejoras técnicas, arquitectónicas y de seguridad por el camino, entre las que cabría destacar:
- Migración a TypeScript.
- Infraestructura como código con Pulumi.
- Despliegues versionados con Helm.
- Autoescalado de máquinas y servicios.
- Certificados autorrenovables a través de LetsEncrypt.
- Despliegues con zero-downtime y auto-rollbacks.
- Agrupación geográfica de los servicios de computación y servicios externos (MongoDB Atlas, CloudAMQP, etc) en Frankfurt.
- Migración de la gestión de dominios de DigitalOcean a CloudFlare como servicio de DNS y Firewall.
Así mismo, para proporcionar el servicio de dominios personalizados en las Enterprise App Stores hacíamos uso de otra máquina virtual con una IP elástica, debido a que DigitalOcean no proveía de un NAT gateway para controlar la IP de salida de los workers.
En ese momento, la arquitectura tenía el siguiente aspecto:
Por fin habíamos logrado alcanzar una plataforma confiable, autoescalable y fácilmente mantenible, con la que poder enfocar nuestros esfuerzos en la evolución de nuestro producto.
Creciendo más rápido de lo previsto
Todo este sistema construído principalmente sobre DigitalOcean y apoyado por AWS funcionó decentemente y con unos costes muy controlados durante muchos meses. Sin embargo, a medida que el volumen de clientes crecía y en consecuencia, el tráfico, comenzamos a sufrir eventuales problemas que no podíamos controlar ni prever:
- El sistema de resolución de DNS de los workers experimentaba problemas ocasionales no manejables por nosotros.
- Sufrimos caídas del control plane de Kubernetes, bien por actualizaciones forzosas o por problemas internos de DigitalOcean.
- En ciertos momentos , la velocidad de transferencia entre nuestra red en DigitalOcean y AWS S3 se reducía hasta pocos KB/segundo, lo que provocaba que el servicio de subida de builds fuera inutilizable durante largos períodos de tiempo.
En ese momento (principios de 2021) estábamos implementando lo que sería la mayor evolución de Applivery: Applivery Device Management. Estos evolutivos nos hicieron pasar también por una arquitectura completa de nuestro dashboard de gestión y del branding de la compañía para orientarla a una mejor experiencia de usuario.
Sin embargo, a pesar de todos los esfuerzos, la infraestructura había resultado ser compleja y por momentos, inestable. Nuestros ambiciosos planes de expansión y la evolución del producto se veían gravemente penalizados por la inversión de tiempo en resolución de problemas e incidencias.
Necesitábamos tener mayor control sobre el servicio administrado de Kubernetes y era imprescindible mejorar la estabilidad de la red. Además, dada la expansión del servicio y del roadmap de producto queríamos aprovechar para continuar mejorando nuestra plataforma en los siguientes aspectos críticos para el negocio:
- Reducir el número de servicios y dependencias externas.
- Mejorar la seguridad de todas las conexiones y comunicaciones entre sistemas.
- Incrementar la observabilidad para prevenir y anticipar potenciales problemas.
- Buscar una región geográfica más afín a nuestro servicio y que nos garantizara poder soportar la demanda actual y futura.
El salto a Google Cloud Platform
Llegados a este punto, habíamos decidido que queríamos abandonar DigitalOcean y movernos a un hyperscaler más adecuado para nuestra realidad.
Tras varias semanas de ejecución de pruebas de concepto en Amazon Web Services (AWS), Google Cloud Platform (GCP) y Azure y, a pesar de que la experiencia del equipo técnico en los ámbitos de Cloud se centraba principalmente en AWS, nos inclinamos por GCP. Los motivos fueron varios:
Mayor control sobre el servicio administrado de Kubernetes
La calidad y facilidad de uso de Google Kubernetes Engine (GKE) nos sorprendió desde el primer momento que lo probamos. La cantidad de opciones y observabilidad que ofrece por defecto sobre el cluster era la mejor que habíamos probado hasta el momento.
Mejorar la estabilidad de la red
Google nos ofrece la opción de usar su propia red con conectividad global, que proporciona mucho mejor rendimiento y latencia que la red estándar, lo que se traduce en una mejor experiencia para todos los usuarios en cualquier parte del mundo.
Reducir el número de servicios externos
Tanto AWS como GCP nos ofrecían una gran cantidad de opciones que nos permitirían internalizar algunos de los servicios externos pero debido a nuestra fuerte dependencia con el ecosistema Android, ya estábamos integrados con algunos servicios de GCP (como Pub/Sub o la Android Management API). A todo esto había que sumarle que GCP nos ofrecía otros servicios únicos como BigQuery, Analytics, Workspace o Maps que planeamos usar en el futuro cercano.
Securizar todas las conexiones
Debido a las limitaciones que experimentábamos en DigitalOcean, teníamos un amplio margen de mejora en los aspectos relativos a seguridad. Identificamos que GCP nos ayudaría en dos aspectos principales: en primer lugar, la funcionalidad de VPC peering con importación y exportación automática de rutas simplificaría en gran medida nuestra integración con MongoDB Atlas. En segundo lugar, nos ofrecía la capacidad de autenticación sin intercambio de claves desde GitHub Actions mediante Workload Identities, evitando la exposición de cualquier tipo de claves de acceso.
Incrementar la observabilidad
Anteriormente teníamos integrado Grafana y Prometheus para gestionar la monitorización y observabilidad, servicios que funcionaban dentro del cluster y que teníamos que mantener. Con GKE esto ya no era necesario, debido a que la propia consola ya ofrecía todo lo que necesitábamos, como acceso a logs, visibilidad de nodos y pods, dashboards de gráficas o alertas.
Cambiar a un región más idónea para nuestro servicio
Finalmente, GCP lanzaba su región aquí en Madrid, España (europe-southwest1), así que sigiendo nuestra marca de identidad, y tras algunas pruebas, nos decantamos por ella.. Tras las pruebas concluímos que mejoramos sustancialmente la latencia en toda América sin afectar a Europa central o el resto del mundo comparándolo con nuestra región anterior situada en Frankfurt.
¿Cómo fué?
Empezamos rehaciendo nuestras recetas de Pulumi para levantar un nuevo entorno pre-productivo completo, haciendo migraciones a múltiples servicios de GCP, entre ellos:
-
Cambiamos el sistema de colas de RabbitMQ por GCP Pub/Sub.
-
Movimos las imágenes Docker de los contenedores y los charts de Helm desde AWS ECR y GCP ChartMuseum a Artifact Registry.
-
Asignamos IPs elásticas al balanceador y NAT gateway, lo que nos da un servicio más íntegro y fiable al tener controladas nuestras IPs de entrada y salida.
-
Cambiamos nuestro stack de observabilidad por los servicios de GCP de Monitoring, Logging y Trace.
Así mismo, gracias a las opciones que brinda GCP, realizamos también cambios en nuestros servicios auxiliares:
-
Conectamos la VPN de Cloudflare Zero Trust a nuestra nueva red privada.
-
Movimos nuestro cluster de MongoDB Atlas a la misma región que GKE, conectándolo por VPC Peering a la red privada.
-
Migramos nuestro CI/CD de CircleCI a GitHub Actions,aprovechando que ya teníamos el código allí alojado. Conseguimos flujos más eficientes, seguros e integrados con desarrollo.
- Migramos nuestra máquina para el servicio de dominios personalizados a Cloudflare Custom Hostnames.
Una vez tuneado y verificado todo el stack, procedimos a levantar el entorno productivo en cuestión de minutos gracias a la receta de Pulumi. Empezamos también un proceso de live migration de MongoDB Atlas a la nueva región.
Una vez listo, paramos los deployments del cluster antiguo, finalizamos el live migration en MongoDB Atlas y cambiamos los DNS en Cloudflare. Todo el tráfico ya llegaba al nuevo entorno. En total conseguimos menos de un minuto de pérdida de servicio.
¿Qué beneficios nos trajo esta arquitectura?
Tras toda la migración hemos conseguido mejorar en todos los aspectos medibles, como tiempos de respuesta, velocidad de transferencia o capacidad de procesamiento.
Seguridad
Mejora de la seguridad de la red y la intercomunicación de servicios, evitando las comunicaciones externas.
Contingencia
Mejoras en seguridad y contingencia contra ataques gracias al Firewall de CloudFlare. Alta disponibilidad (HA) multiregional gracias a GKE multi-cluster Ingress.
Rendimiento
Mejora de performance y el rendimeinto en el procesamiento de builds de hasta 2x y capacidad de paralelizar el procesamiento.
Experiencia del usuario
Mejora de la experiencia del usuario reduciendo significativamente los tiempos de respuesta de nuestros servicios. La media ha pasado de 120ms a 30ms, llegando incluso a alcanzar tiempos inferiores a 5ms en Madrid.
Tiempos de respuesta
Reducción de los tiempos de consulta y respuesta de base de datos, al encontrarse en la misma región y comunicarse a través de red privada en 2x.
Procesos CI/CD
Aceleración de los procesos de CI/CD y, en consecuencia, de nuestras capacidades de entrega continua a través de una experiencia de desarrollo más centralizada a través de GitHub.
Conclusiones
Applivery ha experimentado una evolución constante desde su nacimiento, nuevas necesidades y funcionalidades siguen desarrollándose continuamente. Siempre hemos mantenido un compromiso con la innovación, adaptándonos a los requisitos de negocio para seguir manteniéndonos a la vanguardia de la tecnología y ofrecer un servicio confiable y escalable a todos nuestros clientes.
Esta reciente iteración con GCP es la última muestra de ello, haciendo un gran esfuerzo a todos los niveles para consolidar las bases de nuestra plataforma, asegurando su solidez a largo plazo y permitiéndonos continuar innovando de forma ágil y sostenible en el tiempo.
¿Y ahora qué? ¡Chupito para el que diga IA generativa! 😀