Comienza a ejecutar trabajos de Cloud Run

1. Introducción

1965fab24c502bd5.png

Descripción general

Los servicios de Cloud Run son una buena opción para contenedores que se ejecutan de forma indefinida escuchando solicitudes HTTP, mientras que los trabajos de Cloud Run son más adecuados para contenedores que se ejecutan hasta su finalización (en la actualidad, hasta 24 horas) y no entregan solicitudes. Por ejemplo, las actividades como procesar registros de una base de datos, procesar una lista de archivos desde un bucket de Cloud Storage o realizar una operación de larga duración (por ejemplo, calcular pi), funcionarían bien si se implementaran como un trabajo de Cloud Run.

Los trabajos no tienen la capacidad de entregar solicitudes ni escuchar en un puerto. Esto quiere decir que, a diferencia de los servicios de Cloud Run, los trabajos no deben empaquetar un servidor web. Además, los contenedores de los trabajos deben cerrarse cuando terminan.

En los trabajos de Cloud Run, se pueden especificar diversas tareas para ejecutar varias copias de tus contenedores en paralelo. Cada tarea representa una copia en ejecución del contenedor. Usar varias tareas es útil si cada una puede procesar de forma independiente un subconjunto de tus datos. Por ejemplo, procesar 10,000 registros de Cloud SQL o 10,000 archivos de Cloud Storage podría ser más rápido con 10 tareas que procesen 1,000 registros o archivos en paralelo.

El uso de trabajos de Cloud Run es un proceso de dos pasos:

  1. Crea un trabajo: Esto encapsula toda la configuración necesaria para ejecutar el trabajo, como la imagen de contenedor, la región y las variables de entorno.
  2. Ejecutar el trabajo: Esto crea una nueva ejecución del trabajo. De manera opcional, configura tu trabajo para que se ejecute según un programa con Cloud Scheduler.

En este codelab, primero explorarás una aplicación de Node.js para tomar capturas de pantalla de páginas web y almacenarlas en Cloud Storage. Luego, compilarás una imagen de contenedor para la aplicación, la ejecutarás en trabajos de Cloud Run, actualizarás el trabajo para que procese más páginas web y lo ejecutarás según un programa con Cloud Scheduler.

Qué aprenderás

  • Cómo usar una app para tomar capturas de pantalla de páginas web
  • Cómo compilar una imagen de contenedor para la aplicación
  • Cómo crear un trabajo de Cloud Run para la aplicación
  • Cómo ejecutar la aplicación como un trabajo de Cloud Run
  • Cómo actualizar el trabajo
  • Cómo programar el trabajo con Cloud Scheduler

2. Configuración y requisitos

Cómo configurar el entorno a tu propio ritmo

  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.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla cuando quieras.
  • El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. Por lo general, no importa cuál sea. En la mayoría de los codelabs, deberás hacer referencia al ID de tu proyecto (suele identificarse como PROJECT_ID). Si no te gusta el ID que se generó, podrías generar otro aleatorio. También puedes probar uno propio y ver si está disponible. No se puede cambiar después de este paso y se usa el mismo durante todo el proyecto.
  • Recuerda que hay un tercer valor, un número de proyecto, que usan algunas APIs. 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 la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no costará mucho, tal vez nada. Para cerrar recursos y evitar que se generen cobros más allá de este instructivo, puedes borrar los recursos que creaste o borrar el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.

Inicie 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 Google Cloud Console, haz clic en el ícono de Cloud Shell en la barra de herramientas en la parte superior derecha:

84688aa223b1c3a2.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:

320e18fedb7fbe0.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. Todo tu trabajo en este codelab se puede hacer en un navegador. No es necesario que instales nada.

Configura gcloud

Establece en Cloud Shell el ID del proyecto y la región en la que deseas implementar el trabajo de Cloud Run. Guárdalos como variables PROJECT_ID y REGION. En el futuro, podrás elegir una región de una de las ubicaciones de Cloud Run.

PROJECT_ID=[YOUR-PROJECT-ID]
REGION=us-central1
gcloud config set core/project $PROJECT_ID

Habilita las APIs

Habilita todos los servicios necesarios con el siguiente comando:

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com

3. Obtén el código

Primero, explorarás una aplicación de Node.js para tomar capturas de pantalla de páginas web y almacenarlas en Cloud Storage. Luego, compilarás una imagen de contenedor para la aplicación y la ejecutarás como un trabajo en Cloud Run.

En Cloud Shell, ejecuta el siguiente comando para clonar el código de la aplicación desde este repositorio:

git clone https://github.com/GoogleCloudPlatform/jobs-demos.git

Ve al directorio que contiene la aplicación:

cd jobs-demos/screenshot

Deberías ver el siguiente diseño de archivo:

screenshot
 |
 ├── Dockerfile
 ├── README.md
 ├── screenshot.js
 ├── package.json

A continuación, se incluye una breve descripción de cada página.

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

4. Cómo explorar el código

Para explorar el código, haz clic en el botón Open Editor ubicado en la parte superior de la ventana de Cloud Shell a fin de usar el editor de texto integrado.

15a2cdc9b7f6dfc6.png

A continuación, se incluye una explicación breve de cada archivo.

screenshot.js

screenshot.js primero agrega Puppeteer y Cloud Storage como dependencias. Puppeteer es una biblioteca de Node.js que puedes usar para tomar capturas de pantalla de páginas web:

const puppeteer = require('puppeteer');
const {Storage} = require('@google-cloud/storage');

Hay una función initBrowser para inicializar Puppeteer y una función takeScreenshot a fin de tomar capturas de pantalla de una URL determinada:

async function initBrowser() {
  console.log('Initializing browser');
  return await puppeteer.launch();
}

async function takeScreenshot(browser, url) {
  const page = await browser.newPage();

  console.log(`Navigating to ${url}`);
  await page.goto(url);

  console.log(`Taking a screenshot of ${url}`);
  return await page.screenshot({
    fullPage: true
  });
}

A continuación, hay una función para obtener o crear un bucket de Cloud Storage, y otra destinada a subir la captura de pantalla de una página web a un bucket:

async function createStorageBucketIfMissing(storage, bucketName) {
  console.log(`Checking for Cloud Storage bucket '${bucketName}' and creating if not found`);
  const bucket = storage.bucket(bucketName);
  const [exists] = await bucket.exists();
  if (exists) {
    // Bucket exists, nothing to do here
    return bucket;
  }

  // Create bucket
  const [createdBucket] = await storage.createBucket(bucketName);
  console.log(`Created Cloud Storage bucket '${createdBucket.name}'`);
  return createdBucket;
}

async function uploadImage(bucket, taskIndex, imageBuffer) {
  // Create filename using the current time and task index
  const date = new Date();
  date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
  const filename = `${date.toISOString()}-task${taskIndex}.png`;

  console.log(`Uploading screenshot as '${filename}'`)
  await bucket.file(filename).save(imageBuffer);
}

Por último, la función main es el punto de entrada:

async function main(urls) {
  console.log(`Passed in urls: ${urls}`);

  const taskIndex = process.env.CLOUD_RUN_TASK_INDEX || 0;
  const url = urls[taskIndex];
  if (!url) {
    throw new Error(`No url found for task ${taskIndex}. Ensure at least ${parseInt(taskIndex, 10) + 1} url(s) have been specified as command args.`);
  }
  const bucketName = process.env.BUCKET_NAME;
  if (!bucketName) {
    throw new Error('No bucket name specified. Set the BUCKET_NAME env var to specify which Cloud Storage bucket the screenshot will be uploaded to.');
  }

  const browser = await initBrowser();
  const imageBuffer = await takeScreenshot(browser, url).catch(async err => {
    // Make sure to close the browser if we hit an error.
    await browser.close();
    throw err;
  });
  await browser.close();

  console.log('Initializing Cloud Storage client')
  const storage = new Storage();
  const bucket = await createStorageBucketIfMissing(storage, bucketName);
  await uploadImage(bucket, taskIndex, imageBuffer);

  console.log('Upload complete!');
}

main(process.argv.slice(2)).catch(err => {
  console.error(JSON.stringify({severity: 'ERROR', message: err.message}));
  process.exit(1);
});

Ten en cuenta lo siguiente sobre el método main:

  • Las URLs se pasan como argumentos.
  • El nombre del bucket se pasa como la variable de entorno BUCKET_NAME definida por el usuario. Este nombre debe ser único a nivel global en todo Google Cloud.
  • Los trabajos de Cloud Run pasan una variable de entorno CLOUD_RUN_TASK_INDEX. Los trabajos de Cloud Run pueden ejecutar varias copias de la aplicación como tareas únicas. CLOUD_RUN_TASK_INDEX representa el índice de la tarea en ejecución. El valor predeterminado es cero cuando el código se ejecuta fuera de los trabajos de Cloud Run. Cuando la aplicación se ejecuta como varias tareas, cada tarea o contenedor recoge la URL de la que es responsable, toma una captura de pantalla y guarda la imagen en el bucket.

package.json

El archivo package.json define la aplicación y especifica las dependencias de Cloud Storage y Puppeteer:

{
  "name": "screenshot",
  "version": "1.0.0",
  "description": "Create a job to capture screenshots",
  "main": "screenshot.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Google LLC",
  "license": "Apache-2.0",
  "dependencies": {
    "@google-cloud/storage": "^5.18.2",
    "puppeteer": "^13.5.1"
  }
}

Dockerfile

Dockerfile define la imagen de contenedor para la aplicación con todas las bibliotecas y dependencias requeridas:

FROM ghcr.io/puppeteer/puppeteer:16.1.0
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENTRYPOINT ["node", "screenshot.js"]

5. Implementa un trabajo

Antes de crear un trabajo, debes crear una cuenta de servicio que usarás para ejecutarlo.

gcloud iam service-accounts create screenshot-sa --display-name="Screenshot app service account"

Otorga el rol storage.admin a la cuenta de servicio para que pueda usarse en la creación de buckets y objetos.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --role roles/storage.admin \
  --member serviceAccount:screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

Ya está todo listo para implementar un trabajo de Cloud Run que incluya la configuración necesaria para ejecutar el trabajo.

gcloud beta run jobs deploy screenshot \
  --source=. \
  --args="https://example.com" \
  --args="https://cloud.google.com" \
  --tasks=2 \
  --task-timeout=5m \
  --region=$REGION \
  --set-env-vars=BUCKET_NAME=screenshot-$PROJECT_ID \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

Usa una implementación basada en el código fuente y crea un trabajo de Cloud Run sin ejecutarlo.

Observa cómo se pasan las páginas web como argumentos. El nombre del bucket para guardar las capturas de pantalla se pasa como una variable de entorno.

Puedes ejecutar varias copias de tu contenedor en paralelo si especificas una cantidad de tareas que se ejecutarán con la marca --tasks. Cada tarea representa una copia en ejecución del contenedor. Usar varias tareas es útil si cada una puede procesar de forma independiente un subconjunto de tus datos. Para facilitar esto, cada tarea conoce su índice, que se almacena en la variable de entorno CLOUD_RUN_TASK_INDEX. Tu código es responsable de determinar qué tarea controla qué subconjunto de los datos. Observa --tasks=2 en esta muestra. Esto garantiza que se ejecuten 2 contenedores para las 2 URLs que queremos procesar.

Cada tarea puede ejecutarse hasta por 24 horas. Puedes disminuir este tiempo de espera con la marca --task-timeout, como lo hicimos en este ejemplo. Todas las tareas deben completarse correctamente para que el trabajo se complete de la misma manera. Según la configuración predeterminada, no se reintentan las tareas con errores, pero puedes configurar tareas para que se vuelvan a intentar cuando fallen. Si alguna tarea excede la cantidad de reintentos, todo el trabajo fallará.

De forma predeterminada, tu trabajo se ejecutará con tantas tareas en paralelo como sea posible. Esta cantidad será igual a la cantidad de tareas para tu trabajo, hasta un máximo de 100. Recomendamos que la ejecución en paralelo sea más baja para los trabajos que acceden a un backend con escalabilidad limitada. Por ejemplo, una base de datos que admite una cantidad limitada de conexiones activas. Puedes reducir el paralelismo con la marca --parallelism.

6. Ejecutar un trabajo

Antes de ejecutar el trabajo, enuméralo para ver que se creó:

gcloud run jobs list

✔
JOB: screenshot
REGION: us-central
LAST RUN AT:
CREATED: 2022-02-22 12:20:50 UTC

Ejecuta el trabajo con el siguiente comando:

gcloud run jobs execute screenshot --region=$REGION

Esto ejecuta el trabajo. Puedes enumerar las ejecuciones actuales y anteriores:

gcloud run jobs executions list --job screenshot --region=$REGION

...
JOB: screenshot
EXECUTION: screenshot-znkmm
REGION: $REGION
RUNNING: 1
COMPLETE: 1 / 2
CREATED: 2022-02-22 12:40:42 UTC

Describe la ejecución. Deberías ver la marca de verificación verde y el mensaje tasks completed successfully:

gcloud run jobs executions describe screenshot-znkmm --region=$REGION

✔ Execution screenshot-znkmm in region $REGION
2 tasks completed successfully


Image:           $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot at 311b20d9...
Tasks:           2
Args:            https://example.com https://cloud.google.com
Memory:          1Gi
CPU:             1000m
Task Timeout:    3600s
Parallelism:     2
Service account: 11111111-compute@developer.gserviceaccount.com
Env vars:
  BUCKET_NAME    screenshot-$PROJECT_ID

También puede consultar la página de trabajos de Cloud Run de Cloud Console para ver el estado:

1afde14d65f0d9ce.png

Si verificas el bucket de Cloud Storage, deberías ver los dos archivos de captura de pantalla creados:

8c4d355f6f65106.png

A veces, es probable que debas detener una ejecución antes de que se complete. Puede ser porque te diste cuenta de que necesitas ejecutar el trabajo con parámetros diferentes o porque hay un error en el código y no deseas usar tiempo de procesamiento innecesario.

Para detener la ejecución de un trabajo, debes borrar la ejecución:

gcloud run jobs executions delete screenshot-znkmm --region=$REGION

7. Actualiza un trabajo

La próxima ejecución no recogerá automáticamente las versiones nuevas del contenedor mediante Cloud Run. Si cambias el código de tu trabajo, debes volver a compilar el contenedor y actualizar el trabajo. Las imágenes etiquetadas te ayudarán a identificar qué versión de la imagen se está usando actualmente.

Del mismo modo, también debes actualizar el trabajo si quieres actualizar algunas de las variables de configuración. Las ejecuciones posteriores del trabajo usarán el contenedor y la configuración nuevos.

Actualiza el trabajo y cambia las páginas de las que la app toma capturas de pantalla en la marca --args. También actualiza la marca --tasks para reflejar la cantidad de páginas.

gcloud run jobs update screenshot \
  --args="https://www.pinterest.com" \
  --args="https://www.apartmenttherapy.com" \
  --args="https://www.google.com" \
  --region=$REGION \
  --tasks=3

Vuelve a ejecutar el trabajo. Pasa este tiempo a la marca --wait para esperar a que finalicen las ejecuciones:

gcloud run jobs execute screenshot --region=$REGION --wait

Después de unos segundos, debería ver 3 capturas de pantalla más en el bucket:

ed0cbe0b5a5f9144.png

8. Cómo programar un trabajo

Hasta ahora, estás ejecutando trabajos de forma manual. En una situación real, es probable que quieras ejecutar trabajos en respuesta a un evento o en un programa. Veamos cómo ejecutar el trabajo de captura de pantalla de manera programada con Cloud Scheduler.

Primero, asegúrate de que la API de Cloud Scheduler esté habilitada:

gcloud services enable cloudscheduler.googleapis.com

Ve a la página de detalles de los trabajos de Cloud Run y haz clic en la sección Triggers:

3ae456368905472f.png

Selecciona el botón Add Scheduler Trigger:

48cbba777f75e1eb.png

Se abrirá un panel a la derecha. Crea un trabajo de Scheduler para ejecutarlo todos los días a las 9:00 a.m. con esta configuración y selecciona Continue:

81fd098be0db216.png

En la página siguiente, selecciona la cuenta de servicio de procesamiento predeterminada y elige Create:

fe479501dfb91f9f.png

Ahora, deberías ver un nuevo activador de Cloud Scheduler creado:

5a7bc6d96b970b92.png

Haz clic en View Details para ir a la página de Cloud Scheduler.

Puedes esperar hasta las 9 a.m. para que se inicie el programador o puedes activar Cloud Scheduler de forma manual seleccionando Force Run:

959525f2c8041a6a.png

Después de unos segundos, deberías ver que el trabajo de Cloud Scheduler se ejecutó de forma correcta:

d64e03fc84d61145.png

También deberías ver 3 capturas de pantalla más que agregó la llamada desde Cloud Scheduler:

56398a0e827de8b0.png

9. Felicitaciones

¡Felicitaciones! Completaste el codelab.

Realiza una limpieza (opcional)

Para evitar incurrir en cargos, es una buena idea limpiar los recursos.

Si no necesitas el proyecto, simplemente puedes borrarlo:

gcloud projects delete $PROJECT_ID

Si lo necesitas, puedes borrar recursos de forma individual.

Borra el código fuente:

rm -rf ~/jobs-demos/

Borra el repositorio de Artifact Registry:

gcloud artifacts repositories delete containers --location=$REGION

Borra la cuenta de servicio:

gcloud iam service-accounts delete screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

Borra el trabajo de Cloud Run:

gcloud run jobs delete screenshot --region=$REGION

Borra el trabajo de Cloud Scheduler:

gcloud scheduler jobs delete screenshot-scheduler-trigger --location=$REGION

Borra el bucket de Cloud Storage:

gcloud storage rm --recursive gs://screenshot-$PROJECT_ID

Temas abordados

  • Cómo usar una app para tomar capturas de pantalla de páginas web
  • Cómo compilar una imagen de contenedor para la aplicación
  • Cómo crear un trabajo de Cloud Run para la aplicación
  • Cómo ejecutar la aplicación como un trabajo de Cloud Run
  • Cómo actualizar el trabajo
  • Cómo programar el trabajo con Cloud Scheduler