Comienza a ejecutar trabajos de Cloud Run

1. Introducción

96d07289bb51daa7.png

Descripción general

Aunque los servicios de Cloud Run son una buena opción para los contenedores que se ejecutan de forma indefinida con el fin de escuchar solicitudes HTTP, los trabajos de Cloud Run pueden ser una mejor alternativa para los contenedores que se ejecutan hasta completar un proceso y que 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.

Flujo de trabajo de los trabajos

El uso de los trabajos de Cloud Run es sencillo y solo requiere los siguientes dos pasos:

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

Limitaciones de la vista previa

Durante la vista previa, los trabajos de Cloud Run tienen las siguientes restricciones:

  • Se puede implementar un máximo de 50 ejecuciones (de los mismos trabajos, o bien otros) de manera simultánea por proyecto y por región.
  • Puedes ver tus trabajos existentes, iniciar ejecuciones y supervisar su estado en la página Trabajos de Cloud Run de Cloud Console. Usa gcloud para crear trabajos nuevos, ya que, actualmente, Cloud Console no admite esta operación.
  • No uses trabajos de Cloud Run para cargas de trabajo de producción, dado que no hay garantía de rendimiento ni confiabilidad. Es posible que los trabajos de Cloud Run cambien de manera que generen incompatibilidad con versiones anteriores sin previo aviso.

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 como un trabajo en Cloud Run, actualizarás el trabajo a fin de procesar 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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

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

55efc1aaa7a4d3ad.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:

7ffe5cbb04455448.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.

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. Puedes elegir una región de una de las ubicaciones de Cloud Run.

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

Habilita las API

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.

f78880c00c0af1ef.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 node:17-alpine

# Installs latest Chromium (92) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      npm

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

# Install dependencies
COPY package*.json ./
RUN npm install

# Copy all files
COPY . .

# Run everything after as a non-privileged user.
USER pptruser

ENTRYPOINT ["node", "screenshot.js"]

5. Cómo compilar y publicar la imagen de contenedor

Artifact Registry es el servicio de almacenamiento y administración de imágenes de contenedor en Google Cloud. Consulta Trabaja con imágenes de contenedor para obtener más información. Artifact Registry puede almacenar imágenes de contenedor de OCI y Docker en un repositorio de Docker.

Crea un repositorio nuevo de Artifact Registry llamado containers:

gcloud artifacts repositories create containers --repository-format=docker --location=$REGION

Compila y publica la imagen de contenedor:

gcloud builds submit -t $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1

Después de unos minutos, deberías ver la imagen de contenedor compilada y alojada en Artifact Registry.

62e50ebe805f9a9c.png

6. Crea 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 crear un trabajo de Cloud Run que incluya la configuración necesaria a fin de ejecutar el trabajo.

gcloud beta run jobs create screenshot \
  --image=$REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1 \
  --args="https://example.com" \
  --args="https://cloud.google.com" \
  --tasks=2 \
  --task-timeout=5m \
  --set-env-vars=BUCKET_NAME=screenshot-$PROJECT_ID \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

Esto 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 1 hora. 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.

7. Ejecuta un trabajo

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

gcloud beta run jobs list

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

Ejecuta el trabajo con el siguiente comando:

gcloud beta run jobs execute screenshot

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

gcloud beta run jobs executions list --job screenshot

...
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 beta run jobs executions describe screenshot-znkmm
✔ 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:

e59ed4e532b974b1.png

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

f2f86e60b94ba47c.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 beta run jobs executions delete screenshot-znkmm

8. 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 beta run jobs update screenshot \
  --args="https://www.pinterest.com" \
  --args="https://www.apartmenttherapy.com" \
  --args="https://www.google.com" \
  --tasks=3

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

gcloud beta run jobs execute screenshot --wait

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

ce91c96dcfd271bb.png

9. Cómo programar un trabajo

Hasta ahora, en este codelab se muestran los trabajos en ejecución de forma manual. En una situación real, es probable que quieras ejecutar trabajos en respuesta a un evento o en un programa. Puedes hacerlo a través de la API de REST de Cloud Run. 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

Crea un trabajo de Cloud Scheduler para ejecutar el de Cloud Run todos los días a las 9:00:

PROJECT_NUMBER="$(gcloud projects describe $(gcloud config get-value project) --format='value(projectNumber)')"

gcloud scheduler jobs create http screenshot-scheduled --schedule "0 9 * * *" \
   --http-method=POST \
   --uri=https://$REGION-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/$PROJECT_ID/jobs/screenshot:run \
   --oauth-service-account-email=$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
   --location $REGION

Verifica que el trabajo de Cloud Scheduler esté creado y listo para llamar al trabajo de Cloud Run:

gcloud scheduler jobs list

ID: screenshot-scheduled
LOCATION: $REGION
SCHEDULE (TZ): 0 9 * * * (Etc/UTC)
TARGET_TYPE: HTTP
STATE: ENABLED

Para realizar una prueba, activa manualmente Cloud Scheduler:

gcloud scheduler jobs run screenshot-scheduled

En pocos segundos, deberías ver 3 capturas de pantalla adicionales que agregó la llamada desde Cloud Scheduler:

971ea598020cf9ba.png

10. Felicitaciones

¡Felicitaciones! Completaste el codelab.

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