Premiers pas avec les tâches Cloud Run

1. Présentation

96d07289bb51daa.png

Aperçu

Bien que les services Cloud Run conviennent bien aux conteneurs qui écoutent indéfiniment les requêtes HTTP, les tâches Cloud Run peuvent mieux convenir aux conteneurs qui sont exécutés et qui ne diffusent pas les requêtes. Par exemple, le traitement des enregistrements d'une base de données, le traitement d'une liste de fichiers à partir d'un bucket Cloud Storage ou l'exécution d'une opération de longue durée, comme le calcul de Pi, fonctionnent bien s'ils sont implémentés en tant que tâche Cloud Run.

Les tâches ne peuvent pas diffuser de requêtes ni écouter un port. Cela signifie que, contrairement aux services Cloud Run, les tâches ne doivent pas regrouper un serveur Web. Les conteneurs de tâches doivent être supprimés dès qu'ils sont terminés.

Dans les tâches Cloud Run, vous pouvez exécuter plusieurs copies de votre conteneur en parallèle en spécifiant un nombre de tâches. Chaque tâche correspond à une copie en cours d'exécution du conteneur. L'utilisation de plusieurs tâches est utile si chacune d'elles peut traiter indépendamment un sous-ensemble de vos données. Par exemple, le traitement de 10 000 enregistrements de Cloud SQL ou de 10 000 fichiers de Cloud Storage peut être effectué plus rapidement avec 10 tâches traitant 1 000 enregistrements ou fichiers, chacun en parallèle.

Workflows

Il est facile d'utiliser des tâches Cloud Run, avec seulement deux étapes:

  1. Créez une offre d'emploi. Il encapsule toutes les configurations nécessaires à l'exécution de la tâche, telles que l'image de conteneur, la région et les variables d'environnement.
  2. Exécutez la tâche. Cela crée une exécution pour la tâche. Vous pouvez également configurer votre tâche pour qu'elle s'exécute selon un calendrier spécifique à l'aide de Cloud Scheduler.

Limites de la version bêta

Lors de la prévisualisation, les tâches Cloud Run présentent les contraintes suivantes:

  • Un maximum de 50 exécutions (de tâches identiques ou différentes) peuvent être exécutées simultanément par projet et par région.
  • Vous pouvez consulter vos tâches existantes, en lancer et contrôler l'état d'exécution sur la page Tâches Cloud Run de Cloud Console. Utilisez gcloud pour créer des tâches, car Cloud Console ne permet pas actuellement de créer de nouvelles tâches.
  • N'utilisez pas les tâches Cloud Run pour les charges de travail de production. Nous ne garantissons pas la fiabilité ni les performances. Les tâches Cloud Run peuvent faire l'objet de modifications susceptibles d'affecter la compatibilité ascendante, avec peu de préavis avant le lancement général.

Dans cet atelier de programmation, vous allez d'abord explorer une application Node.js pour effectuer des captures d'écran de pages Web et les stocker dans Cloud Storage. Vous allez ensuite créer une image de conteneur pour l'application, l'exécuter en tant que tâche sur Cloud Run, la mettre à jour pour traiter davantage de pages Web et l'exécuter de manière planifiée avec Cloud Scheduler.

Points abordés

  • Utiliser une application pour effectuer des captures d'écran de pages Web
  • Créer une image de conteneur pour l'application
  • Créer une tâche Cloud Run pour l'application
  • Exécuter l'application en tant que tâche Cloud Run
  • Mettre à jour la tâche
  • Planifier une tâche avec Cloud Scheduler

2. Prérequis

Configuration de l'environnement au rythme de chacun

  1. Connectez-vous à Google Cloud Console, puis créez un projet ou réutilisez un projet existant. Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.

b35bf95b8bf3d5d8.png

A99b7ace416376c4.png

bd84a6d3003637c5.png

  • Le nom du projet est le nom à afficher pour les participants à ce projet. Il s'agit d'une chaîne de caractères qui n'est pas utilisée par les API Google. Vous pouvez la modifier à tout moment.
  • L'ID du projet doit être unique parmi tous les projets Google Cloud et doit être immuable (il ne peut pas être modifié une fois défini). Cloud Console génère automatiquement une chaîne unique. vous n'avez généralement pas besoin de vous en occuper. Dans la plupart des ateliers de programmation, vous devrez référencer l'ID du projet. De plus, il est généralement identifié comme PROJECT_ID. Si vous n'aimez pas cet outil, générez-en un autre au hasard, ou essayez le vôtre et vérifiez si est disponible. Ensuite, elle est "gelée" après la création du projet.
  • Le numéro de projet, utilisé par certaines API, est troisième. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
  1. Vous devez ensuite activer la facturation dans Cloud Console afin d'utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire gratuite. Pour arrêter les ressources afin d'éviter toute facturation en dehors de ce tutoriel, suivez les instructions de nettoyage indiquées à la fin de l'atelier de programmation. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit de 300 USD.

Démarrer Cloud Shell

Vous pouvez utiliser Google Cloud à distance depuis votre ordinateur portable. Dans cet atelier de programmation, vous allez utiliser Google Cloud Shell, un environnement de ligne de commande fonctionnant dans le cloud.

Dans Google Cloud Console, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure:

55efc1aaa7a4d3ad.png

Le provisionnement de l'environnement et la connexion ne devraient pas prendre plus de quelques minutes. Une fois l'opération terminée, le résultat devrait ressembler à ceci:

7ffe5cbb04455448.png

Cette machine virtuelle contient tous les outils de développement nécessaires. Il s'agit d'un répertoire d'accueil persistant de 5 Go qui s'exécute sur Google Cloud, ce qui améliore considérablement les performances et l'authentification du réseau. Vous pouvez travailler simplement dans un atelier à l'aide d'un simple navigateur.

Configurer gcloud

Dans Cloud Shell, définissez votre ID de projet et la région dans laquelle vous souhaitez déployer la tâche Cloud Run. Enregistrez-les sous la forme de variables PROJECT_ID et REGION. Vous pouvez choisir une région à partir de l'un des emplacements Cloud Run.

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

Activer les API

Activez tous les services nécessaires:

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

3. Obtenir le code

Vous allez d'abord explorer une application Node.js pour prendre des captures d'écran de pages Web et les stocker dans Cloud Storage. Plus tard, vous allez créer une image de conteneur pour l'application et l'exécuter en tant que tâche sur Cloud Run.

À partir de Cloud Shell, exécutez la commande suivante pour cloner le code de l'application à partir de ce dépôt:

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

Accédez au répertoire contenant l'application:

cd jobs-demos/screenshot

Vous devriez voir cette mise en page de fichiers:

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

Voici une brève description de chaque fichier:

  • screenshot.js contient le code Node.js de l'application.
  • package.json définit les dépendances de la bibliothèque.
  • Dockerfile définit l'image du conteneur.

4. Explorer le code

Pour explorer le code, utilisez l'éditeur de texte intégré en cliquant sur le bouton Open Editor en haut de la fenêtre Cloud Shell.

F78880c00c0af1ef.png

Voici une brève explication de chaque fichier.

capture.js

screenshot.js ajoute d'abord Puppeteer et Cloud Storage en tant que dépendances. Puppeteer est une bibliothèque Node.js qui vous permet d'effectuer des captures d'écran de pages Web:

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

Il existe une fonction initBrowser pour initialiser Puppeteer et la fonction takeScreenshot pour effectuer des captures d'écran d'une URL donnée:

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
  });
}

Ensuite, il existe une fonction permettant d'obtenir ou de créer un bucket Cloud Storage, et une autre permettant d'importer la capture d'écran d'une page Web dans 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);
}

Enfin, la fonction main est le point d'entrée:

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);
});

Remarquez les points suivants concernant la méthode main:

  • Les URL sont transmises en tant qu'arguments.
  • Le nom du bucket est transmis en tant que variable d'environnement BUCKET_NAME définie par l'utilisateur. Le nom du bucket doit être unique sur l'ensemble de Google Cloud.
  • Une variable d'environnement CLOUD_RUN_TASK_INDEX est transmise par les tâches Cloud Run. Les tâches Cloud Run peuvent exécuter plusieurs copies de l'application en tant que tâches uniques. CLOUD_RUN_TASK_INDEX représente l'index de la tâche en cours d'exécution. La valeur par défaut est zéro si le code est exécuté en dehors des tâches Cloud Run. Lorsque l'application s'exécute en plusieurs tâches, chaque tâche/conteneur récupère l'URL dont il est responsable, effectue une capture d'écran et enregistre l'image dans le bucket.

package.json

Le fichier package.json définit l'application et spécifie les dépendances de Cloud Storage et de 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

Le Dockerfile définit l'image de conteneur pour l'application avec toutes les bibliothèques et dépendances requises:

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. Créer et publier l'image de conteneur

Artifact Registry est le service de stockage et de gestion d'images de conteneurs sur Google Cloud. Pour plus d'informations, consultez la section Utiliser des images de conteneurs. Artifact Registry peut stocker des images de conteneurs Docker et OCI dans un dépôt Docker.

Créez un dépôt Artifact Registry appelé containers:

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

Créez et publiez l'image de conteneur:

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

Au bout de quelques minutes, vous devriez voir l'image de conteneur créée et hébergée sur Artifact Registry.

57e50ebe805f9a9c.png

6. Créer une tâche

Avant de créer une tâche, vous devez créer un compte de service que vous utiliserez pour l'exécuter.

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

Attribuez le rôle storage.admin au compte de service afin de pouvoir l'utiliser pour créer des buckets et des objets.

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

Vous êtes maintenant prêt à créer une tâche Cloud Run incluant la configuration requise pour l'exécuter.

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

Cette action crée une tâche Cloud Run sans l'exécuter.

Notez que les pages Web sont transmises en tant qu'arguments. Le nom du bucket permettant d'enregistrer les captures d'écran est transmis en tant que variable d'environnement.

Vous pouvez exécuter plusieurs copies de votre conteneur en parallèle en spécifiant un nombre de tâches à exécuter avec l'indicateur --tasks. Chaque tâche correspond à une copie en cours d'exécution du conteneur. L'utilisation de plusieurs tâches est utile si chacune d'elles peut traiter indépendamment un sous-ensemble de vos données. Pour faciliter cette tâche, chaque tâche tient compte de son index, stocké dans la variable d'environnement CLOUD_RUN_TASK_INDEX. Votre code est chargé de déterminer quelle tâche gère quel sous-ensemble de données. Regardez --tasks=2 dans cet exemple. Cela permet de garantir que deux conteneurs sont exécutés pour les deux URL que nous souhaitons traiter.

Chaque tâche peut durer jusqu'à 1 heure. Vous pouvez réduire ce délai à l'aide de l'option --task-timeout, comme dans cet exemple. Toutes les tâches doivent être réussies pour que la tâche soit effectuée correctement. Par défaut, les tâches ayant échoué ne font pas l'objet d'une nouvelle tentative. Vous pouvez configurer de nouvelles tentatives en cas d'échec des tâches. Si une tâche dépasse son nombre de tentatives, la tâche échoue.

Par défaut, votre tâche s'exécute avec autant de tâches en parallèle que possible. Ce nombre correspond au nombre de tâches pour votre tâche (jusqu'à 100). Il peut être judicieux de réduire le parallélisme des tâches qui accèdent à un backend avec une évolutivité limitée. Par exemple, une base de données qui accepte un nombre limité de connexions actives. Vous pouvez réduire le parallélisme avec l'indicateur --parallelism.

7. Félicitations

Félicitations, vous avez terminé cet atelier de programmation !

Points abordés

  • Utiliser une application pour effectuer des captures d'écran de pages Web
  • Créer une image de conteneur pour l'application
  • Créer une tâche Cloud Run pour l'application