Objetivos

Al finalizar el tutorial el estudiante estará en capacidad de:

Pasos previos

En particular se utilizarán los siguientes recursos:

  1. Repositorio privado con la imagen de contenedor de la aplicación suma, el cual fue publicado en el tutorial Container Registry. Por favor realice ese tutorial antes de continuar con el desarrollo de esta guía.
  2. gcloud SDK para acceder a los servicios del proveedor Google Cloud Platform a partir de la consola. En caso de no tenerla instalada puede consultar el siguiente manual de instalación:https://cloud.google.com/sdk/docs/install
  3. Herramienta de control de Kubernetes, kubectl. En caso de no tenerla instalada puede consultar el siguiente manual de instalación:https://kubernetes.io/docs/tasks/tools/

Para este tutorial manejaremos el repositorio de calculadora-numeros usado en los tutoriales pasados, en caso de no tenerlo clonado aún, puede consultarlo en el siguiente enlace. Trabajaremos sobre la rama feature/kubernetes-sum, para ello cambie la rama del repositorio actual al respectivo hash del tag, para hacerlo ejecute en su terminal:

user@192 ~ % git checkout feature/kubernetes-sum

En el repositorio hay una carpeta llamada saveNumber, esta contiene una aplicación de la calculador que se encarga de recibir un número por request (POST) y luego guardarlo en la base de datos. Pruebe un poco en su ambiente local este endpoint, para esto baje la siguiente imagen:

user@192 ~ % docker pull ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros:vDatabase

Una vez descargada, ejecútela mediante:

user@192 ~ % docker run -d -p 4000:4000 ghcr.io/misw-4301-desarrollo-apps-en-la-nube/s1-calculadora-numeros:vDatabase

Una vez el contenedor esté en ejecución, pruebe el siguiente endpoint para almacenar un número.

user@192 ~ % curl --location --request POST 'http://0.0.0.0:4000/guardar_numero' \
--header 'Content-Type: application/json' \
--data-raw '{
    "num_1": 399
}'

Obtendrá la confirmación de almacenamiento exitoso en una base de datos en disco de la contenedora (Usando SQLite).

{
  "mensaje": "numero 399 guardado correctamente."
}

Si se fija en esta línea, el código espera una variable de entorno llamada SQLALCHEMY_DATABASE_URI, si esta no es asignada por variable de entorno, simplemente redirecciona el ORM a la dirección de base de datos local. Ahora pruebe el endpoint para obtener el último número guardado en la base de datos, para esto ejecute el siguiente curl:

user@192 ~ % curl --location --request GET 'http://0.0.0.0:4000/ultimo_numero' \
--header 'Content-Type: application/json'

Obtendrá como respuesta el último número guardado:

{
    "ultimo_valor": 399
}

Detenga la contenedora pues ya no es necesario mantenerla en ejecución.

Ahora pondremos en práctica el tutorial de container registry, necesitamos crear una imagen de este nuevo endpoint en su proyecto designado de GCP, para esto navegue al directorio /saveNumber y ejecute el siguiente comando para crear la respectiva imagen en el container registry:

user@192 ~ % cd saveNumber
user@192 ~ % docker build -t us-central1-docker.pkg.dev/<PROYECTO-GCP>/<REPOSITORIO-ARTIFACT-REGISTRY>/memory:1.0 .

Para el caso del ejemplo el comando se verá de la siguiente forma:

user@192 ~ % docker build -t us-central1-docker.pkg.dev/uandes-native/uniandes-misw-native-calculadora-app/memory:1.0 .

Note que para el ejemplo hemos usado el tag db-save-1.0. Una vez termine la creación de la imagen, haga push al repositorio remoto:

user@192 ~ % docker push us-central1-docker.pkg.dev/<PROYECTO-GCP>/<REPOSITORIO-ARTIFACT-REGISTRY>/memory:1.0

en el ejemplo el comando se ve de la siguiente manera:

user@192 ~ % docker push us-central1-docker.pkg.dev/uandes-native/uniandes-misw-native-calculadora-app/memory:1.0

Una vez termine con esta instrucción, diríjase a artifact registry, allí podrá ver la imagen creada:

Para crear una red y una sub red privada puede remitirse al tutorial de la semana 3 de kubernetes, en este tutorial usaremos la misma red y subred allí creadas. Si ya las tiene, solo necesita crear una subred específicamente para la base de datos.

Subred para la base de datos

Cree el rango de direcciones que se utilizará para desplegar la instancia de base de datos:

user@192 ~ % gcloud compute addresses create red-dbs-tutoriales --global --purpose=VPC_PEERING --addresses=<RANGO-IPS> --prefix-length=24 --network=<RED> --project=<ID-PROYECTO>

Allí puede ver campos como:

  1. Purpose: Función que estará ejecutando la subred y rango de direcciones usadas.
  2. Addresses: Rango de direcciones asociadas a la subred.
  3. Prefix-length: La nueva longitud del prefijo de la subred. Debe ser más pequeño que el original y en el espacio de direcciones privadas 10.0.0.0/8, 172.16.0.0/12 o 192.168.0.0/16 definido en RFC 1918.
  4. Network: Red creada para asociar la subred (Recuerde que si siguio el tutorial de kubernetes, puede usar la misma allí creada).
  5. Project: Id del proyecto creado, puede consultarlo en el banner inicial de GCP.

Para el caso del ejemplo el comando es:

user@192 ~ % gcloud compute addresses create red-dbs-tutoriales --global --purpose=VPC_PEERING --addresses=192.168.0.0 --prefix-length=24 --network=vpn-tutoriales-misw --project=uandes-native

Por último, otorgue acceso a los servicios de administración de redes de GCP para que pueda realizar la gestión de la instancia a través de la red virtual creada:

user@192 ~ % gcloud services vpc-peerings connect --service=servicenetworking.googleapis.com --ranges=red-dbs-tutoriales --network=vpn-tutoriales-misw --project=<id-proyecto>

Regla de firewall

Finalmente, cree una regla de firewall para permitir el tráfico entre un cluster de kubernetes y la base de datos que vamos a utilizar:

user@192 ~ % gcloud compute firewall-rules create allow-db-ingress --direction=INGRESS --priority=1000 --network=<RED> --action=ALLOW --rules=tcp:5432 --source-ranges=<RANGO-IP> --target-tags=<TAG> --project=<ID-PROYECTO>

Para el caso del ejemplo:

user@192 ~ % gcloud compute firewall-rules create allow-db-ingress --direction=INGRESS --priority=1000 --network=vpn-tutoriales-misw --action=ALLOW --rules=tcp:5432 --source-ranges=192.168.1.0/24 --target-tags=basesdedatos --project=uandes-native

La aplicación como ya lo pudo evidenciar en pasos anteriores, requiere de una base de datos. Para ello, despliegue una base de datos relacional PostgreSQL 14 y configure su acceso dentro de la VPN recién creada. Siga los pasos a continuación para lograrlo.

En la consola de GCP, diríjase a la sección de SQL dentro del submenú de Bases de datos:

Una vez dentro, cree una nueva instancia de base de datos Postgres. Utilice los siguientes datos para crear la instancia:

Realice la configuración de la máquina a ser utilizada en la instancia. Utilice la siguiente configuración:

Configure la configuración de red de la instancia según los siguientes datos:

Puede que obtenga un mensaje de la base de datos que requiere conexión de acceso a servicios privados, de ser así, de click en configurar conexión y seleccione la red y subred creadas en etapas anteriores.

Luego de asignar la conexión puede esperar una vista como la siguiente:

Finalmente agregue la etiqueta de basesdedatos a la instancia.

Por último de click en crear instancia. Al finalizar la creación debe poder ver la dirección asignada a la instancia en la red privada:

Para mayor información puede visitar la documentación oficial de Cloud SQL.

Ahora que ha experimentado un poco el endpoint de almacenamiento de números, conectaremos la base de datos creada en la nube en pasos anteriores con un cluster de kubernetes.

En diversas ocasiones es requerido configurar secretos y configuraciones que necesitan nuestras aplicaciones para que funcionen correctamente. Estas variables deben ser almacenadas correctamente asegurando atributos de confidencialidad e integridad, evitando así accesos no autorizados que puedan comprometer la seguridad de la infraestructura. En Kubernetes, el almacenamiento de estas variables se realiza mediante el recurso Secreto, el cual corresponde a un llavero con diversas claves cifradas que pueden ser empleadas en la definición de otros recursos como el despliegue.

Nuestra aplicación requiere de la definición de las siguientes variables en el archivo secrets.yaml que encontrará en la carpeta de la aplicación saveNumber:

Allí debe reemplazar los siguientes datos asociandolas al postgres desplegado previamente:

apiVersion: v1
stringData:
  uri : "postgresql+psycopg2://<POSTGRES_USER>:<POSTGRES_PASSWORD>@<POSTGRES_HOST>/<POSTGRES_DB>"
kind: Secret
metadata:
  name: <NAME-SECRETS>

Para el caso del ejemplo la modificación del archivo secrets.yaml se verá de la siguiente forma después de reemplazar la información:

apiVersion: v1
stringData:
  uri : "postgresql+psycopg2://postgres:1234@192.168.0.3/postgres"
kind: Secret
metadata:
  name: appsecrets

Haga uso del usuario postgres ya que este tiene los permisos necesarios para crear esquemas y tablas, lo cual es requerido en la carga inicial de la aplicación. Una buena práctica es definir un usuario con una serie de permisos limitados en la base de datos, dado el objetivo académico haremos uso del usuario por defecto.

Para hacer uso de la base de datos usaremos el cluster creado en el tutorial de kubernetes, por lo cual cargaremos los secrets en esta agrupación, mediante:

user@192 ~ % kubectl apply -f secrets.yaml

Podrá ver el siguiente mensaje de confirmación:

user@192 ~ % kubectl apply -f secrets.yaml
To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
secret/appsecrets created

Se preguntará como el cluster de kubernetes captura estos secretos, si se fija en el archivo k8s-service.yml de la carpeta saveNumber, en la línea 50 se indica un secretKeyRef, el cual se encarga de ir al archivo de secretos creado previamente (appsecrets) y extraer el valor de la key indicada, en esta ocasión la uri de la base de datos.

env:
   - name: "SQLALCHEMY_DATABASE_URI"
     valueFrom:
       secretKeyRef:
         name: appsecrets 
         key: uri

Si va a la consola de GCP a la sección de Secrets y ConfigMaps podrá ver el secreto creado asociado a la dirección de la base de datos.

Luego de esto, el cluster de kubernetes asociado a la aplicación, estará almacenando el número capturado por body request en el sistema de persistencia de postgres creado en la nube.

Para mayor información puede visitar la documentación oficial de secrets.

Configure el archivo k8s-sservice.yml de la carpeta saveNumber cambiando el comodín <IMAGE-URI> (ubicado en la línea 39) por la URI de su repositorio privado y el tag de la imagen creada en pasos anteriores.

containers:
        - name: calculadora-numeros
          image: us-central1-docker.pkg.dev/uandes-native/uniandes-misw-native-calculadora-app/memory:1.0
          ports:
            - containerPort: 4000
          env:
            - name: "SQLALCHEMY_DATABASE_URI"
              valueFrom:
                secretKeyRef:
                  name: appsecrets 
                  key: uri
          # Realizar pull siempre a la imagen
          imagePullPolicy: Always

Para culminar, construya el despliegue a partir del archivo yml ejecutando la instrucción:

user@192 ~ % kubectl apply -f k8s-service.yml

Valide que el cluster está corriendo exitosamente mediante kubectl get pods y pruebe el siguiente endpoint:

user@192 ~ % curl --location --request POST 'http://<URL-BALANCEADOR>/guardar_numero' \
--header 'Content-Type: application/json' \
--data-raw '{
    "num_1": 399
}'

Recuerde reemplazar la URL en el curl, por la de su balanceador de carga, si no sabe cual es, puede consultarla en la sección de ingress:

Para el caso del ejemplo, el curl quedaría de la siguiente forma:

curl --location --request POST 'http://35.202.134.204:80/guardar_numero' \
--header 'Content-Type: application/json' \
--data-raw '{
    "num_1": 399
}'

Obtendrá la confirmación de almacenamiento exitoso en una base de datos en la nube (Usando Postgres).

{
  "mensaje": "numero 399 guardado correctamente."
}

Finalmente valide el endpoint GET para traer el último número almacenado en la base datos, mediante el curl para el caso de ejemplo:

user@192 ~ % curl --location --request GET 'http://35.202.134.204:80/ultimo_numero' \
--header 'Content-Type: application/json' \
--data-raw '{
    "num_1": 399
}'

Obtendrá como respuesta el último número guardado:

{
    "ultimo_valor": 399
}

¡Éxitos en el desarrollo del tutorial y nos vemos en una próxima oportunidad!

[1] «Inicio rápido Cloud SQL», [Online]. Disponible en:https://cloud.google.com/sql/docs/postgres/quickstart