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

1. Descripción general

En este lab, crearás un nuevo servicio de Cloud Run, el servicio de collage, que Cloud Scheduler activará a intervalos regulares. El servicio recupera las fotos subidas más recientes y crea un collage con ellas: busca la lista de fotos recientes en Cloud Firestore y, luego, descarga los archivos de fotos 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 un 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 archivos 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 los metadatos de las imágenes que almacenamos anteriormente. Express es un framework web de JavaScript / Node. Bluebird se usa para controlar las 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 ligera de Node 14. Estamos instalando la biblioteca de ImageMagick. Luego, instalamos los módulos de NPM que necesita nuestro código y ejecutamos nuestro código de Node 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');

Necesitamos las diversas dependencias necesarias para que se ejecute nuestro programa: Express es el framework web de Node que usaremos, ImageMagick es la biblioteca para manipular imágenes, Bluebird es una biblioteca para controlar las promesas de JavaScript, Path se usa para trabajar con rutas de archivos y directorios, y luego Storage y Firestore son 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 Node: nuestra app responde a las solicitudes HTTP GET. Además, controlamos los errores en caso de que algo salga mal. Ahora veamos qué hay dentro de 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 fotos más recientes que subieron nuestros usuarios a partir de los metadatos almacenados en Cloud Firestore. Verificamos si la colección resultante está vacía o no y, luego, continuamos en la rama else de nuestro código.

Recopilemos 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 miniaturas más recientes, usaremos la biblioteca de ImageMagick para crear una cuadrícula de 4x4 con esas imágenes en miniatura. Usamos la biblioteca Bluebird y su implementación de Promise para transformar el código basado en devoluciones de llamada en código compatible con async / await. Luego, esperamos la promesa que crea 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 imagen de collage se guardó en el disco de forma local en la carpeta temporal, ahora debemos subirla a Cloud Storage y, luego, devolver 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 el 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 de nuestro 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 funcione antes de implementarlo en la nube.

Dentro de 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 implementar en Cloud Run, configura la región de Cloud Run en una de las regiones compatibles y la plataforma 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 Cloud Run para que compile la imagen de contenedor por ti con los paquetes de compilación de Google Cloud.

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 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 ningún Dockerfile en el directorio del código fuente, los paquetes de compilación de Google Cloud detectan automáticamente el lenguaje que estás usando y recuperan las dependencias del código para crear una imagen de contenedor lista para la producción con una imagen base segura administrada por Google. Esto indica a Cloud Run que use los buildpacks de Google Cloud para compilar la imagen de contenedor definida en Dockerfile.

Ten en cuenta también que la implementación basada en código fuente usa Artifact Registry para almacenar contenedores compilados. Artifact Registry es una versión moderna de Google Container Registry. La CLI te pedirá que habilites 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 convierte el servicio de Cloud Run en un servicio interno que solo se activará con cuentas de servicio específicas.

8. Configura Cloud Scheduler

Ahora que el servicio de Cloud Run está listo y se implementó, es hora de crear la programación regular para invocar el servicio cada minuto.

Crea 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 que se ejecute 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 Cloud Console para ver que está configurado y que apunta a la URL del servicio de Cloud Run:

35119e28c1da53f3.png

9. Prueba el servicio

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

93922335a384be2e.png

10. Limpieza (opcional)

Si no quieres continuar con los otros labs de la serie, puedes limpiar los recursos para ahorrar costos y ser un buen ciudadano de la nube. 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

Como alternativa, 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 a un tema de Pub/Sub, se invoca tu servicio de collage de Cloud Run y puede agregar imágenes para crear la imagen resultante.

Temas abordados

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

Próximos pasos