Pic-a-daily: Lab 3: Crea un collage con las fotos más recientes

1. Descripción general

En este codelab, crearás un nuevo servicio de Cloud Run, el servicio de collage, que Cloud Scheduler activará en un intervalo regular. El servicio recupera las últimas imágenes subidas y crea un collage de ellas: busca la lista de imágenes recientes en Cloud Firestore y, luego, descarga los archivos de imágenes reales de Cloud Storage.

df20f5d0402b54b4.png

Qué aprenderás

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

  1. Accede a Google Cloud Console y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una string de caracteres que no se utiliza en las API de Google y se puede actualizar en cualquier momento.
  • El ID del proyecto debe ser único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). Cloud Console genera automáticamente una string única, que, por lo general, no importa cuál sea. En la mayoría de los codelabs, debes hacer referencia al ID del proyecto (suele ser PROJECT_ID). Por lo tanto, si no te gusta, genera otro aleatorio o prueba con uno propio y comprueba si está disponible. Después de crear el proyecto, este ID se “congela” y no se puede cambiar.
  • Además, hay un tercer valor, el Número de proyecto, que usan algunas API. Obtén más información sobre estos tres valores en la documentación.
  1. A continuación, deberás habilitar la facturación en Cloud Console para usar las API o los recursos de Cloud. Ejecutar este codelab no debería costar mucho, tal vez nada. Si quieres cerrar los recursos para no se te facture más allá de este instructivo, sigue las instrucciones de “limpieza” que se encuentran al final del codelab. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 300.

Inicia Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usarás Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

En GCP Console, haga clic en el ícono de Cloud Shell en la barra de herramientas superior derecha:

bce75f34b2c53987.png

El aprovisionamiento y la conexión al entorno deberían tomar solo unos minutos. Cuando termine el proceso, debería ver algo como lo siguiente:

f6ef2b5f13479f3a.png

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Puedes realizar todo tu trabajo en este lab usando simplemente un navegador.

3. Habilita las APIs

Necesitarás Cloud Scheduler para activar el servicio de Cloud Run en intervalos regulares. Asegúrate de que esté habilitada:

gcloud services enable cloudscheduler.googleapis.com

Deberías ver que la operación finaliza correctamente:

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. Clona el código

Clona el código, si aún no lo hiciste en el codelab anterior:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

Luego, puedes ir al directorio que contiene el servicio:

cd serverless-photosharing-workshop/services/collage/nodejs

Tendrás el siguiente diseño de archivo para el servicio:

services
 |
 ├── collage
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

Dentro de la carpeta, tienes 3 archivos:

  • index.js contiene el código de Node.js.
  • package.json define las dependencias de la biblioteca.
  • Dockerfile define la imagen de contenedor.

5. Cómo explorar el código

Dependencias

El archivo package.json define las dependencias de biblioteca necesarias:

{
  "name": "collage_service",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "bluebird": "^3.7.2",
    "express": "^4.17.1",
    "imagemagick": "^0.1.3",
    "@google-cloud/firestore": "^4.9.9",
    "@google-cloud/storage": "^5.8.3"
  }
}

Dependemos de la biblioteca de Cloud Storage para leer y guardar archivos de imagen en Cloud Storage. Declaramos una dependencia en Cloud Firestore para recuperar metadatos de imágenes que almacenamos anteriormente. Express es un framework web de JavaScript / Node. Bluebird se usa para administrar promesas, y imagemagick es una biblioteca para manipular imágenes.

Dockerfile

Dockerfile define la imagen de contenedor para la aplicación:

FROM node:14-slim

# installing Imagemagick
RUN set -ex; \
  apt-get -y update; \
  apt-get -y install imagemagick; \
  rm -rf /var/lib/apt/lists/*

WORKDIR /picadaily/services/collage
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]

Usamos una imagen base de Node 14 clara. Estamos instalando la biblioteca imagemagick. Luego, instalaremos los módulos de NPM que necesita nuestro código y ejecutaremos el código de nuestro nodo con npm start.

index.js

Analicemos con más detalle nuestro código index.js:

const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

Exigimos las diversas dependencias necesarias para que nuestro programa se ejecute: Express es el framework web de Node que usaremos, ImageMagick es la biblioteca para manipular imágenes, Bluebird es una biblioteca para manejar promesas de JavaScript, Path se usa para tratar archivos y rutas de directorios, y, luego, Storage y Firestore sirven para trabajar respectivamente con Google Cloud Storage (nuestros buckets de imágenes) y el almacén de datos de Cloud Firestore.

const app = express();

app.get('/', async (req, res) => {
    try {
        console.log('Collage request');

        /* ... */

    } catch (err) {
        console.log(`Error: creating the collage: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

Arriba, tenemos la estructura de nuestro controlador de nodo: nuestra app responde a las solicitudes HTTP GET. Además, realizamos un poco de manejo de errores en caso de que algo salga mal. Ahora, veamos qué contiene esta estructura.

const thumbnailFiles = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
    .where('thumbnail', '==', true)
    .orderBy('created', 'desc')
    .limit(4).get();

if (snapshot.empty) {
    console.log('Empty collection, no collage to make');
    res.status(204).send("No collage created.");
} else {

    /* ... */

}

Nuestro servicio de collage necesitará al menos cuatro imágenes (cuyas miniaturas se hayan generado), así que asegúrate de subir 4 imágenes primero.

Recuperamos las 4 imágenes más recientes que subieron nuestros usuarios, de los metadatos almacenados en Cloud Firerstore. Verificamos si la colección resultante está vacía o no y, luego, continuamos con la rama else de nuestro código.

Recopilamos la lista de nombres de archivos:

snapshot.forEach(doc => {
    thumbnailFiles.push(doc.id);
});
console.log(`Picture file names: ${JSON.stringify(thumbnailFiles)}`);

Descargaremos cada uno de esos archivos del bucket de miniaturas, cuyo nombre proviene de una variable de entorno que configuramos en el momento de la implementación:

const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);

await Promise.all(thumbnailFiles.map(async fileName => {
    const filePath = path.resolve('/tmp', fileName);
    await thumbBucket.file(fileName).download({
        destination: filePath
    });
}));
console.log('Downloaded all thumbnails');

Una vez que se suban las últimas miniaturas, usaremos la biblioteca de ImageMagick para crear una cuadrícula de 4 x 4 con esas miniaturas. Usamos la biblioteca Bluebird y su implementación de Promise para transformar el código basado en devoluciones de llamada en un código compatible con async / await. Luego, esperamos la promesa que está creando el collage de imágenes:

const collagePath = path.resolve('/tmp', 'collage.png');

const thumbnailPaths = thumbnailFiles.map(f => path.resolve('/tmp', f));
const convert = Promise.promisify(im.convert);
await convert([
    '(', ...thumbnailPaths.slice(0, 2), '+append', ')',
    '(', ...thumbnailPaths.slice(2), '+append', ')',
    '-size', '400x400', 'xc:none', '-background', 'none',  '-append',
    collagePath]);
console.log("Created local collage picture");

Como la foto de collage se guardó en el disco de forma local en la carpeta temporal, ahora debemos subirla a Cloud Storage y, luego, mostrar una respuesta correcta (código de estado 2xx):

await thumbBucket.upload(collagePath);
console.log("Uploaded collage to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}");

res.status(204).send("Collage created.");

Ahora, es momento de hacer que nuestra secuencia de comandos de Node escuche las solicitudes entrantes:

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started collage service on port ${PORT}`);
});

Al final del archivo fuente, tenemos las instrucciones para que Express inicie nuestra aplicación web en el puerto predeterminado 8080.

6. Realiza pruebas locales

Prueba el código de forma local para asegurarte de que funciona antes de implementarlo en la nube.

En la carpeta collage/nodejs, instala las dependencias de npm y, luego, inicia el servidor:

npm install; npm start

Si todo salió bien, debería iniciar el servidor en el puerto 8080:

Started collage service on port 8080

Usa CTRL-C para salir.

7. Compila e implementa en Cloud Run

Antes de realizar implementaciones en Cloud Run, configura la región de Cloud Run en una de las regiones y la plataforma compatibles en managed:

gcloud config set run/region europe-west1
gcloud config set run/platform managed

Puedes verificar que la configuración esté establecida:

gcloud config list

...
[run]
platform = managed
region = europe-west1

En lugar de compilar y publicar la imagen de contenedor con Cloud Build de forma manual, también puedes confiar en que Cloud Run compilará la imagen de contenedor por ti con Google Cloud Buildpacks.

Ejecuta el siguiente comando para compilar la imagen del contenedor:

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
SERVICE_NAME=collage-service
gcloud run deploy $SERVICE_NAME \
    --source . \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

Observa la marca –-source. Esta es la implementación basada en el código fuente en Cloud Run. Si hay un Dockerfile en el directorio del código fuente, el código fuente subido se compila con ese Dockerfile. Si no hay Dockerfile en el directorio del código fuente, los paquetes de compilación de Google Cloud detectan automáticamente el lenguaje que se está usando y recupera las dependencias del código para crear una imagen de contenedor lista para la producción a través de una imagen base segura administrada por Google. Esto marca Cloud Run para que use Google Cloud Buildpacks para compilar la imagen de contenedor definida en Dockerfile.

Además, ten en cuenta que la implementación basada en el código fuente usa Artifact Registry para almacenar contenedores compilados. Artifact Registry es una versión moderna de Google Container Registry. La CLI solicitará que se habilite la API si aún no está habilitada en el proyecto y creará un repositorio con el nombre cloud-run-source-deploy en la región en la que realizas la implementación.

La marca --no-allow-unauthenticated hace que el servicio de Cloud Run sea un servicio interno que solo las cuentas de servicio específicas activarán.

8. Configura Cloud Scheduler

Ahora que el servicio de Cloud Run está implementado y listo, es momento de crear un programa regular para invocar el servicio cada minuto.

Crear una cuenta de servicio:

SERVICE_ACCOUNT=collage-scheduler-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
   --display-name "Collage Scheduler Service Account"

Otorga permiso a la cuenta de servicio para invocar el servicio de Cloud Run:

gcloud run services add-iam-policy-binding $SERVICE_NAME \
   --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --role=roles/run.invoker

Crea un trabajo de Cloud Scheduler para ejecutarlo cada 1 minuto:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud scheduler jobs create http $SERVICE_NAME-job --schedule "* * * * *" \
   --http-method=GET \
   --location=europe-west1 \
   --uri=$SERVICE_URL \
   --oidc-service-account-email=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --oidc-token-audience=$SERVICE_URL

Puedes ir a la sección Cloud Scheduler en la consola de Cloud para ver que esté configurada y que apunte a la URL del servicio de Cloud Run:

35119e28c1da53f3.png

9. Prueba el servicio

Para probar si la configuración funciona, busca la imagen del collage (llamada collage.png) en el bucket thumbnails. También puedes verificar los registros del servicio:

93922335a384be2e.png

10. Limpieza (opcional)

Si no pretendes continuar con los otros labs de la serie, puedes liberar recursos para ahorrar costos y ser un buen ciudadano de la nube en general. Puedes limpiar los recursos de forma individual de la siguiente manera.

Borra el servicio:

gcloud run services delete $SERVICE_NAME -q

Borra el trabajo de Cloud Scheduler:

gcloud scheduler jobs delete $SERVICE_NAME-job -q

También puedes borrar todo el proyecto:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

11. ¡Felicitaciones!

¡Felicitaciones! Creaste un servicio programado: gracias a Cloud Scheduler, que envía un mensaje cada minuto en un tema de Pub/Sub, se invoca tu servicio de collage de Cloud Run y puede adjuntar imágenes para crear la imagen resultante.

Temas abordados

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

Próximos pasos