Implementando una aplicación multitenant en GCP utilizando CloudRun con CloudSql y Secret Manager
Integrando productos autoadministrados de GCP para crear una solución en la nube del tipo Serverless, con soporte multipaís.
En el episodio anterior, estuvimos viendo cómo integrar CloudRun con Secret Manager. Nos servirá de base para entender los siguientes capítulos.
Si no tuviste la oportunidad de revisarlo, lo puedes encontrar aquí:
https://medium.com/gdg-cloud-scl/gcp-cloudrun-en-acción-15c615a1f593
Contexto:
Existe una plataforma de ecommerce que permite a los clientes hacer compras en línea. Para soportar la operación en el backoffice, existe una gran base de datos, la cual almacena información de todos los clientes y sus compras. La empresa detrás de la plataforma de ecommerce ofrece servicios de compra en linea a diferentes países de la región.
Problematica:
- Al final de cada mes hay miles de registros en la base de datos, lo que hace cada vez más ineficientes las búsquedas de información.
- Los costos de almacenamiento aumentan día a día y no es posible llevar un control eficiente de cada país.
- Administrar la información de los clientes de todos los países en una misma base de datos se hace cada vez más complejo y pone en riesgo la operación de todos los países ante fallas en el mecanismo de persistencia.
Requerimiento:
Implementar una aplicación que cada cierto tiempo borre las órdenes de compra confirmadas de los clientes que tienen una antigüedad mayor a x días, para un país en particular (tú podrías implementar un mecanismo de respaldo previo de esas órdenes antes de eliminarlas de la base de datos)
Además se requiere minimizar el riesgo de afectar las ventas y gestión de todos los países ante problemas en la base de datos.
Requisitos Técnicos
Gradle: 6.3+
Java: 1.8 (Springboot)
Google Cloud Platform
- Una cuenta personal de Google Cloud
- SDK de Google Cloud
Diseño de la solución
La solución abarca 2 requerimientos:
1.- Separar la información de los clientes por país.
Explicaré previamente un punto muy importante:
- Según el contexto de la situación actual, debemos manejar información de compras de clientes de diferentes países. En este punto, es relevante conocer el concepto de Tenant. Un tenant es una agrupación de la información que maneja un sistema de acuerdo a ciertas características comunes. Por ejemplo, clientes que pertenecen a un segmento comercial particular, clientes que pertenecen a una empresa específica dentro de un holding, o que comparten un lugar geográfico o país en común. En este caso, nuestra clasificación de clientes será por país.
Para nuestro caso de uso, tendremos clientes de Chile y Argentina. Se propone aislar la información de los cliente por país. Para ello existen 3 variantes:
- Crear una instancia de cloudsql por cada país
- Crear una sola instancia de cloudsql donde existan 2 bases de datos, una por cada país
- Crear una sola instancia de cloudsql, donde exista una sola base de datos, y dentro de ella un schema por cada país
Las 3 opciones tienen sus ventajas y desventajas. En esta oportunidad, utilizaremos la estrategia de crear una instancia de CloudSql por cada país por varias razones, dentro de las cuales destaca la capacidad de tener recursos de base de datos dedicados para cada país, que podemos aprovechar según su demanda particular, y además podemos aplicar políticas de administración de base de datos específicas según cada caso.
2.- Implementar la funcionalidad de borrado de las órdenes de compra antiguas
Para esto, se propone un microservicio que efectuara on demand la limpieza de las ordenes de compra más antiguas de los clientes, con un cierto criterio, las cuales estarán almacenadas en una tabla específica de la base de datos.
- Esta aplicación se desplegará y ejecutará sobre CloudRun.
- La información se persistirá en una base de datos relacional, CloudSql
- Los datos de configuración correspondientes al acceso a la base de datos, se externalizaran con Secret Manager.
- Para la compilación y despliegue de la solución usaremos CloudBuild
En el siguiente diagrama se muestran los componentes GCP involucrados en la solución y sus interacciones.
Productos GCP
- CloudRun: Plataforma de cómputo serverless autoadministrada
- CloudBuild: Herramienta para crear y desplegar la imagen
- Container Registry: Herramienta para gestionar las imágenes
- Secret Manager: Herramienta para resguardar nuestros datos sensibles
Implementación de la solución
Las características multitenant, para acceder a una base de datos específica según el país que se requiera, las otorgará el framework JPA/Hibernate.
Las 2 instancias de la base de datos las configuraremos en CloudSql (engine PostgreSQL 12)
Los datos de conexión a la base de datos los mantendremos en Secret Manager.
Los datasources los configuraremos en base a la información que tenemos almacenada en Secret Manager.
La aplicación la desplegaremos en CloudRun con la ayuda de Cloudbuild.
La solución debe ser capaz de discriminar de acuerdo al tenant informado en el request, a qué base de datos debe ir para eliminar los datos de las órdenes de compra antiguas.
Vamos por este desafio!
Crear la Base de datos
Crearemos 2 instancias de CloudSql, cada una de ellas con su propia base de datos. Cada base de datos representará los Tenant de Chile y Argentina , respectivamente.
Creando la instancia y Base de datos para Chile
Ir a: Menu de GCP -> SQL
Crear instancia:
Elegir PostgreSQL
Crear Base de Datos:
Nota: Si gustas, puedes agregar una password para el usuario postgres que viene por defecto.
Con esta acción hemos generado una base de datos CloudSql. Si vas a la instancia ecommerce-cl vas a ver una sección llamada “Connect to this instance”. Guarda la IP pública, la cual utilizaremos más adelante para la creación de los secretos:
Public IP Address
En este caso:
Public IP address: 34.66.xxx.xxx
Crear un usuario:
Presionar [Create user account]
Ingresar un usuario y password a tu elección. En este caso:
User name: democl
Password: democl2020
Crear Base de datos para la instancia creada (ecommerce-cl)
En este caso crearemos una base de datos dentro de la instancia ecommerce-cl llamada order-management.
Esta instancia y base de datos representará el tenant de los usuarios de Chile.
Habilitar el acceso a la base de datos:
Ir a All instances — Tu instancia (ecommerce-cl) — Menu — Connections
Ir a [+Add Network] y agregar la ip externa 0.0.0.0/0.
Luego presiona [Done — Save]
Haremos el mismo procedimiento de creación de una nueva instancia y base de datos CloudSql para el tenant de los usuarios de Argentina.
Creando la instancia y Base de datos para Argentina
Crear la instancia ecommerce-ar y una base de datos order-management
Crear el usuario:
En este caso
User: demoar
Password: demoar2020
Crear la base de datos:
La configuración final quedaria así:
Guarda las Public IP Address de cada instancia. Las vamos a necesitar cuando se creen los secretos, para configurar el string de conexión a la base de datos.
Habilitar el acceso a la instancia:
Ya tenemos nuestras dos instancias configuradas.
NOTA: En otra oportunidad vamos a ver cómo otorgarle mayor seguridad al acceso a la base de datos, accediendo solo por su IP Privada.
Ahora vamos a ver la configuración de la aplicación.
Configurar Secretos
Para configurar la información sensible de nuestra aplicación, utilizaremos Secret Manager de GCP. Para este fin se deben realizar los siguientes pasos:
Acceder a Secret Manager
Habilitar la API
Crear Mi Primer Secreto
Presionar [CREATE SECRET]
Registrar valores de cada secreto
Debes agregar el nombre del secreto y su valor. Por ejemplo,
Name: CL_DB_USER
Secret Value: democl (el usuario de base de datos que creamos anteriormente)
Label (+ ADD LABEL)
- Key: tenant
- Value: cl
Respite paso a paso el procedimiento anterior para crear todos los secretos necesarios.
Lista de secretos:
Donde:
[IP_ADRESS_CL]: Public IP Adress que le asigno GCP a tu instancia de Chile
[IP_ADRESS_AR]: Public IP Adress que le asigno GCP a tu instancia de Argentina
[PUERTO]: 5432 (por defecto)
[BASEDEDATOS_CL]: order-management
[BASEDEDATOS_AR]: order-management
En resumen, debería quedar así:
Estructura del microservicio
Capa de persistencia
Es la capa que accede directamente a los datos. Utilizaremos el concepto de Repositorio para proveer de diversas operaciones (CRUD) sin tener que implementarlas explícitamente. Para esto, en concreto aplicaremos Java Persistence API (JPA). Esta representado por el package “repositories”.
Capa de servicios
Es una abstracción entre la capa controladora y la capa de persistencia, para mayor flexibilidad. Esta representado por el package “service”.
Capa controladora
Utilizaremos Rest para exponer los servicios. Esta representada por el package “controller”.
- Configuración: Configuración del microservicio, incluyendo la referencia a los secretos y a la capacidad multitenant. Package “config”.
- Model: El objeto que representa nuestra entidad que representa una abstracción del modelo de datos. Package “model”.
- DTO: El objeto de transferencia de la respuesta. Package “dto”
Obtener secretos desde Secret Manager
Vamos a mostrar un truco para poder leer los secretos desde Secret Manger y a su vez hacerlos disponibles para la configuración de la conexión a nuestras bases de datos, todo esto dinámicamente.
Para esto, primero debemos leer los secretos (en el artículo anterior nos enfocamos a este desafío) y asignarlos a un mapa por país (mapa de secretos). Utilizaremos el prefijo del país que le agregamos a cada nombre de secreto, para poder diferenciar cada uno de ellos.
En segundo lugar, pasaremos cada mapa (que contiene la configuración de secretos de cada país) a un mapa global.
Esto seria así:
Conectar a la base de datos CloudSql
La conexión a la base de datos la vamos a manejar dentro de la aplicación para poder tener el control de la característica multitenant. Los aspectos más relevantes son los siguientes:
- Asignar la configuración de cada país a nuestros datasources respectivos en forma dinámica. Esto sería así:
MultiTenantJpaConfiguration
De esta forma, tendremos al finalizar el proceso un set de datasource identificados cada uno de ellos por su tenant (CL, AR)
Configuración Multitenant en la persistencia
Utilizaremos la estrategia multitenancy del tipo DATABASE de Hibernate.
Implementaremos un interceptor de la petición http para recibir el tenant id que debe venir en el header. Lo dejamos en el contexto.
Implementamos un “Resolver”, capaz de obtener el tenant id del contexto, para que hibernate pueda saber el datasource a utilizar.
Implementaremos una native Query para hacer la limpieza de la tabla “order” de la base de datos.
Eliminaremos los registros más antiguos que 30 días.
Pruebas Locales
Configurar variables de entorno
Previo a la compilación del componente debes configurar las siguientes variables de entorno local:
Cuenta de Servicio GCP:
$ export GOOGLE_APPLICATION_CREDENTIALS=[PATH_TO_JSON]/[JSON_FILE_NAME].json
Por ejemplo:
$ export GOOGLE_APPLICATION_CREDENTIALS=/Users/oliver/Documents/google-auth/cloudrunsa.json
GCP Project Id:
$ export PROJECT_ID=[GCP_PROJECT_ID]
Por ejemplo:
$ export PROJECT_ID=architect-poc
Ejecutar el microservicio localmente
Abre un terminal y sigue los siguientes pasos
- Compilar la aplicación
$ gradle build
- Ejecutar la aplicación
$ java -jar build/libs/[JAR_NAME].jar
Por ejemplo:
$ java -jar build/libs/clean-0.0.1-SNAPSHOT.jar
Ya puedes probar con POSTMAN tu microservicio en la siguiente url:
http://localhost:8080/TU_PATH
por ejemplo: http://localhost:8080/order
Nota: Recuerda agregar en el Header el KEY “X-TENANT_ID” y en el VALUE el prefijo del país que quieres probar.
Otra alternativa para probar locamente tu microservicio es generar tu imagen docker y probarla como contenedor o en algún cluster local (por ejemplo minikube).
Ahora vamos por el objetivo final!!
Desplegar el servicio en CloudRun
Ahora vamos a desplegar nuestro microservicio en CloudRun para que veamos la real potencia de esta solución.
Generar y subir la imagen a GCP
Para generar la imagen de tu componente debemos tener el siguiente archivo configurado:
Dockerfile
FROM openjdk:8-jdk-slim
ARG JAR_FILE=build/libs/clean-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} /app.jar
ENV PROJECT_ID=architect-poc
ENTRYPOINT [“java”, “-Djava.security.edg=file:/dev/./urandom”,”-jar”,”/app.jar”]
Luego, debemos ejecutar el siguiente comando:
$ gcloud builds submit --tag gcr.io/[GCP_PROJECT_ID]/[IMAGE_NAME]
Por ejemplo:
$ gcloud builds submit --tag gcr.io/architect-poc/order-clean-service
Desplegar la imagen en CloudRun
Realizaremos el proceso de despliegue de la imagen y creación del servicio en CloudRun con la ayuda de CloudBuild. Debes ejecutar la siguiente linea de comando en tu terminal:
$ gcloud run deploy --image gcr.io/[GCP-PROJECT-ID]/[CLOUDRUN_SERVICE_NAME] --platform managed --service-account [MY-SERVICE-ACCOUNT]@[GCP-PROJECT-ID].iam.gserviceaccount.com --memory 512 --max-instances 5
Por ejemplo:
$ gcloud run deploy --image gcr.io/architect-poc/order-clean-service --platform managed --service-account my-service-account@architect-poc.iam.gserviceaccount.com --memory 512 --max-instances 5
Nuestro servicio desplegado en CloudRun
Probando el servicio desde POSTMAN
Para esto debes utilizar como dominio la URL que se genero para tu servicio CloudRun más el path que expone el microservicio
https://[SERVICENAME]-[CLOUDRUNID].a.run.app/order
Ejemplo: https://order-clean-service-xxxxxxx.a.run.app/order
Hemos terminado!
Código Fuente: https://github.com/oliverfierro77/gcp-cloudrun-multitenant
Espero que hayas disfrutado esta experiencia. Nos vemos, como siempre, en el próximo desafío, donde veremos cómo invocar nuestra solución en forma recurrente (una pista, Schedule).
Desafíate y desafía día a día.