ASO-GIT
Flask
Google Cloud Run
Creamos una imagen nueva a partir de python:latest
.
El archivo dockerfile hace referencia a requirements.txt
que con tiene el nombre de las bibliotecas (dependencias) que necesitamos que el contenedor tenga disponibles.
Creamos el programa que responderá a las peticiones del servicio web:
Para contruir ese programa haremos uso de Flask, y para ello lo añadimos como dependencia al archivo requirements.txt
.
y el programa correspondiente en python, app/main.py
Con esto, ya podemos construir la imagen:
Y por último podemos ejecutarlo en nuestra máquina local haciendo:
Mapeamos el puerto 80 al 5000 porque es en este puerto en el que escucha el servidor de pruebas de Flask. Como sugiere el mensaje de este servidor de prueba, este es un servidor solo para desarrollo. Para producción sería necesario utilizar un conector compatible con WSGI, como gunicorn
o uwsgi
.
Para verificar su funcionamiento, lo invocamos con un navegador web, o con curl
El código, hasta este punto, se puede consultar en el siguiente repositorio de Github: https://github.com/uah-sol/sample-app/tree/feature/greetings
Cloud Run es un servicio que permite ejecutar contenedores Docker sin tener que montar un servidor con su correspondiente sistema operativo y software. Pertenece al tipo de servicio conocido como PaaS, Platform as a Service. PaaS es un modelo de computación en la nube que proporciona un entorno completo ejecutar y gestionar aplicaciones sin tener que preocuparse por la infraestructura subyacente
Para poder usar este servicio necesitamos subir la imagen a Google Cloud Registry, que es un registor de imágenes, similar al del dockerhub.
Para que docker reconozca al servicio de registro de google, tenemos que hacer:
También es importante habilitar el permiso de la máquina virtual en la que estamos trabajando para que pueda tener permiso de escritura en GCR.
Para subir una imagen a GCR tenemos que etiquetarla siguiendo un convenio:
Por ejemplo
Para esto haremos:
Ahora ya podemos configurar un servicio de ClourRun para que ejecute la imagen.
En este apartado vamos a añadir un nuevo microservicio que denominaremos catalog
. A través de él vamos a gestionar un pequeño catálogo de productos a través de los siguientes endpoints:
Creamos una nueva carpeta en nuestro repositorio para el nuevo microservicio. Podemos reutilizar mucho código del microservicio de greetings
que acabamos de hacer. Desde la raíz del repositorio haremos:
Ahora la estructura de nuestro repo será:
Modificamos el archivo catalog/app/main.py
para incluir los nuevos endpoints requeridos.
Además, hemos añadido un nuevo módulo que permite generar datos de prueba (fake data). Esto es muy útil para hacer pruebas cuando no se tienen datos reales.
El código resultante se puede consultar en la rama feature/catalog-step-1
Llegados a este punto podemos construir la imagen y ejecutar un contenedor haciendo:
Ahora se puede comprobar el sistema haciendo una llamada con cualquier cliente HTTP, por ejemplo curl
:
La respuesta será un JSON similar a la siguiente:
En este paso vamos a sustituir las respuestas falsas de prueba (fake) por datos procedentes de una BBDD real. Primero lo haremos en un entorno local y finalmente utilizaremos una instancia de Cloud SQL en Google Cloud.
Postgres
en un contedor dockerUsaremos Postgres como base de datos relacional SQL. Concretamente una imagen docker obtenida de dockerhub https://hub.docker.com/_/postgres
De las instrucciones obtenemos que necesitamos crear una serie de variables de entorno cuando se crea el contenedor:
POSTGRES_USER
que indica el nombre del usuario que operará la base de datos.POSTGRES_PASSWORD
, que indica la contraseña del usuario anterior.POSTGRES_DB
, que indica el nombre de la BD que se quiere utilizar, y que se creará de no existir.Crearemos una carpeta para los elementos relacionados con la base de datos, otra con la aplicación tal y como está en este momento, y un archivo docker-compose
que nos facilite la labor de arrancar todos los contenedores necesarios. El aspecto de nuestro proyecto ahora será el siguiente:
El archivo docker-compose.yml
será el siguiente por ahora:
Con esto ya podemos contruir y ejecutar la nueva imagen de Postgres haciendo
Para conectar a la BBDD necesitaremos un cliente compatible con el protocolo que utiliza Posgress. Una opción habitual es usar psql
, que es una aplicación que proporciona el fabricante para este fin, y además viene en la propia imagen de postgres
, así es que lo podemos ejecutar desde el mismo contenedor que acabamos de poner en marcha.
docker compose exec db psql -U myuser mydb
Podemos listar las distintas BBDD haciendo \l
y comprobamos que mydb
ya está presente.
Ahora vamos a crear la tabla para contener a nuestros productos:
También podemos poner este contenido en un archivo, por ejemplo dentro de una nueva carpeta database
, llamarlo db_productos.sql
y hacer:
Comprobamos que todo ha ido bien haciendo una consulta. Importante acordarse de seleccionar la base de datos db
, que es donde hemos creado la tabla.
Para ello necesitamos elegir un conector adecuado: psycopg2
concretamente fijaremos la versión a igual o mayor que 2.8.6, para lo cual editamos requirements.txt
y añadiremos:
En un módulo aparte crearemos un par de funciones que nos ayudarán a conectar con la base de datos. Crearemos una carpeta db
para que contenga un nuevo paquete python. Para eso añadiremos en el ella el archivo vacío __init__.py
, haciendo, desde el directorio principal (el que tiene el archivo docker-compose.yml
):
A continuación añadiremos el siguiente archivo database.py
al paquete.
Como se puede deducir del código, estas dos funciones nos ayudarán a abrir y cerrar respectivamente la conexión a la BBDD. Utilizaremos el paquete de flask g
para colocar objeto en el área global de la aplicación: el objeto db
que nos proporciona accesso a la BD en concreto.
Modificamos ahora catalog.py
para que obtenga los productos desde la BD en lugar de devolver una lista falsa.
Usamos la siguiente orden curl
para probar:
products
sobre BBDDOperaciones:
E.O: Si insertamos un producto haciendo una llamada CURL (o postman) el producto es visible en el listado general de GET /product
requirements.txt
GET /product/<sku>
El servicio que hemos creado ahora depende de otros dos sistemas externos: una base de datos (Postgress) y de una base de datos en memoria (redis). En nuestro entorno de desarrollo hemos utilizado docker-compose
para disponer de ambos. Podríamos replicar exactamente lo mismo en una máquina de Compute Engine (IaaS), pero en su lugar vamos vamos a emplear otra estrategia: utilizar servicios aprovisionados y gestionados por el proveedor (PaaS). Estos servicios gestionados serán una BBDD Postgres y una base de datos en memoria compatible con REDIS.
El servicio gestionado de bases de datos relacionales en GCP es Cloud SQL:
app-database
xPo2G3Bt7ntrL8Or
)connection name
, que nos hará falta luego. En mi caso es aso-git-290916:europe-west1:app-database
. Usaremos esto para conectar con la BBDD.Users
de la columna de la izquierda.
dbuser
supercomplexpasswd
Databases
catalog
También hay que habilitar el Cloud SQL Admin API
Si tienes gcloud
correctamente configurado, también puedes crear esta máquina haciendo:
gcloud
Para conectar con la instancia que acabamos de crear podemos utilizar gcloud
desde nuestra máquina de Google Compute Engine. Para ello debemos asegurarnos de que dicha máquina tiene permisos para interactuar con Cloud SQL.
También necesitaremos tener el cliente de postgres
con el que realizar las operaciones oportunas. En nuestra máquina de compute engine, que tiene la distribución Debian 9 de Linux, podemos instalar ese cliente haciendo:
Ahora ya podemos conectar haciendo:
De la misma forma que hicimos al principio, podemos ejecutar el código SQL
para construir la estructura de nuestra BD, así como insertar algunos artículos de ejemplo, haciendo:
https://cloud.google.com/sql/docs/mysql/quickstart-proxy-test
Para arrancar el proxy necesitamos conocer el nombre de conexión de nuestra instancia (INSTANCE_CONNECTION_NAME
)
Con el proxy escuchando en el puerto de nuestra máquina local 5432, podemos hacer:
Ahora vamos a modificar la aplicación para que use la BBDD gestionada que acabamos de crear. Para esto hay que tener en cuenta que cuando Cloud Run ejecute nuestra aplicación, a nuestro contenedor docker le injectará un socket de tipo UNIX que la pondrá en contacto con la BBDD (ver patrón sidecar en contenedores docker y SQLProxy). Con esto en mente inspeccionamos la parte de nuestro código que tiene que ver con la conexión a la BBDD, que está en app/db/database.py
:
Tarde o temprano tendríamos que corregir una cosa: es una mala idea usar constantes literales, como por ejemplo dbuser
, pero mucho peor es poner una contraseña en texto claro. Pero en nuestro caso hay otro motivo adicional, que queremos que nuestra aplicación funcione en nuestro entorno de desarrollo, pero también cuando se ejecute en Cloud Run. Y claro, los valores de user
, password
, etc. serán diferentes dependiendo del entorno en el que nos encontremos. Este es un problema muy habitual, y una de las técnicas clásicas para resolverlo es utilizar variables de entorno del sistema operativo. Es decir, que el entorno de ejecución, ya sea Cloud Run, docker-compose o el que sea, inyectará unas variables de entorno que nosotros digamos, con los valores apropiados. Con esto en mente, vamos a utilizar las siguiente variables de entorno para cuando ejecutemos nuestro servicio en local:
DB_USERNAME
con el nombre de usuario de la BBDD que creamos en local, dbuser
DB_PASSWORD
con la contraseña para ese usuario secreto
DB_NAME
con el nombre de la BBDD db
DB_HOST
con el nombre del contenedor que tiene la BBDD db
DB_PORT
con el puerto TCP en el que escucha el contenedor de la BBDD 5432
Por su parte, cuando se ejecute en Cloud Run, estos valores serán:
DB_USERNAME
con el nombre de usuario de la BBDD que hemos creado antes, dbuser
DB_PASSWORD
con la contraseña para ese usuario supercomplexpasswd
DB_NAME
con el nombre de la BBDD catalog
CLOUD_SQL_CONNECTION_NAME
con el que nos ha proporcionado Cloud SQL durante la creación de la BBDD aso-git-290916:europe-west1:app-database
Ahora tenemos que hacer llegar esas variables de entorno a nuestro contenedor docker. Para ello vamos a comenzar con docker-compose
y luego veremos como hacerlo en Cloud Run.
Vamos a modificar el archivo docker-compose.yml
de la siguiente forma:
Podemos verificar que esto funciona haciendo:
Lo siguiente será modificar nuestra aplicación para que obtenga los valores de esas variables de entorno a la hora de configurar la conexión a la BBDD:
No hay que olvidar import os
para poder disponer de la función os.environ.get
.
Para definir estas variables en Cloud Run, tenemos que acceder a dicha funcionalidad en la consola de Google Cloud, editar nuestra aplicación y buscar la pestaña VARIABLES
Vamos a aprovechar a crear la conexión a la BBDD. A la derecha de la pestaña de VARIABLES
encontraremos otra que dice CONNECTIONS
. Allí encontraremos un desplegable para seleccionar la conexión a la base de datos que creamos anteriormente. Con todo esto ya podemos desplegar la nueva aplicación, aunque como este despliegue se hará con el código antiguo, aun no entrará nada de lo que hemos hecho en funcionamiento. De hecho aún no podemos desplegar el nuevo código porque lo hemos hecho dependiente del servicio de REDIS, y no hemos creado este servicio en Google Cloud. Podríamos configurar dicho servicio y desplegar todo junto, pero creo que es mejor ir haciendo cosas de forma incremental, así es que lo primero que haremos será modificar nuestra aplicación para que no sea dependiente de REDIS.
Así, si no declaramos las variables de entorno REDIS_HOST
y REDIS_PORT
el programa no hará uso de cache.
Con esto ya podemos desplegar nuestra aplicación. Como tenemos configurada la integración continua con el repositorio de github, basta con mergear la rama en la que he estado trabajando a la rama main
. Para ello realizamos los commit
necesarios, subimos la rama al repositorio, cambiamos a main
, mergeamos feature/redis
y subimos main al repositorio. Esta última acción provocará el nuevo despliegue:
Si ahora vamos a Cloud Run comprobaremos que es está desplegando la nueva versión. Una vez desplegada, podemos intengar obtener el listado de productos, pero obtendremos el siguiente error:
Efectivamente, no hemos creado las tablas oportunas en la BBDD. Necesitamos conectar a la BD para ejecutar las operaciones DDL que hicimos cuando creamos la BD local. Hay varias formas de conectar con la BD. Una de ellas es usando la utilidad gcloud
.
Esta utilidad debería estar configurada para operar con nuestro proyecto. Para verificar esto podemos hacer gcloud config configurations list
.SSi necesitamos crear una nueva configuración podemos hacer gcloud init
Ahora, con gcloud
configurado correctamente, podemos obtener un listado de todas las instancias de Cloud SQL haciendo:
Vamos a hacer un despliegue parcial:
Para ello desmarcamos la opción que dice serve this revision inmediately y damos a desplegar.