Premiers pas avec les tâches Cloud Run

1. Présentation

96d07289bb51daa7.png

Présentation

Bien que les services Cloud Run soient adaptés aux conteneurs qui s'exécutent indéfiniment à l'écoute des requêtes HTTP, les tâches Cloud Run peuvent mieux convenir aux conteneurs qui s'exécutent jusqu'à la fin et qui n'envoient pas de requêtes. Par exemple, le traitement des enregistrements depuis une base de données, le traitement d'une liste de fichiers depuis 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âches Cloud Run.

Les tâches ne peuvent pas envoyer de requêtes ni écouter un port. Cela signifie que, contrairement aux services Cloud Run, les tâches ne doivent pas inclure de 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 pertinente si chacune d'elles peut traiter indépendamment un sous-ensemble de vos données. Par exemple, le traitement de 10 000 enregistrements depuis Cloud SQL ou de 10 000 fichiers à partir de Cloud Storage peut être plus rapide si 10 tâches traitent 1 000 enregistrements ou fichiers chacune en parallèle.

Workflow de tâches

L'utilisation des tâches Cloud Run est simple et ne comprend que deux étapes :

  1. Créer une tâche. Cette opération 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écuter la tâche. Cette opération crée une exécution de 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

En phase bêta, les tâches Cloud Run présentent les contraintes suivantes :

  • Vous pouvez effectuer un maximum de 50 exécutions (de tâches identiques ou différentes) simultanément par projet et par région.
  • Vous pouvez consulter vos tâches existantes, lancer des exécutions 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 le permet pas à l'heure actuelle.
  • N'utilisez pas les tâches Cloud Run pour les charges de travail de production, car leur fiabilité et leurs performances ne sont pas garanties. Les tâches Cloud Run peuvent faire l'objet de modifications susceptibles d'affecter la rétrocompatibilité et ce, sans préavis.

Dans cet atelier de programmation, vous allez d'abord explorer une application Node.js pour faire 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, mettre à jour la tâche 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 faire 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 la tâche avec Cloud Scheduler

2. Prérequis

Configuration de l'environnement d'auto-formation

  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

bd84a6d3004737c5.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères qui n'est pas utilisée par les API Google, et que vous pouvez modifier à tout moment.
  • L'ID du projet doit être unique sur l'ensemble des projets Google Cloud et doit être immuable (vous ne pouvez pas le modifier une fois que vous l'avez défini). Cloud Console génère automatiquement une chaîne unique dont la composition importe peu, en général. Dans la plupart des ateliers de programmation, vous devrez référencer l'ID du projet (généralement identifié comme PROJECT_ID), donc s'il ne vous convient pas, générez-en un autre au hasard ou définissez le vôtre, puis vérifiez s'il est disponible. Il est ensuite "gelé" une fois le projet créé.
  • La troisième valeur est le numéro de projet, utilisé par certaines API. 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 sans frais. Pour arrêter les ressources afin d'éviter qu'elles ne vous soient facturées après ce tutoriel, suivez les instructions de nettoyage indiquées à la fin de l'atelier. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai gratuit pour bénéficier d'un crédit de 300 $.

Démarrer Cloud Shell

Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté 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 et la connexion à l'environnement prennent quelques instants seulement. 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. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez réaliser toutes les activités de cet atelier dans 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 en tant que variables PROJECT_ID et REGION. Vous pouvez choisir une région parmi les 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 faire 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 et l'exécuter en tant que tâche sur Cloud Run.

Dans 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

Les fichiers devraient se présenter comme ceci :

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

Voici une brève description de chacun d'eux :

  • 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 description de chaque fichier.

screenshot.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 de faire des captures d'écran de pages Web :

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

La fonction initBrowser permet d'initialiser Puppeteer et la fonction takeScreenshot permet de faire 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);
});

Notez les éléments 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 au niveau mondial, sur l'ensemble de Google Cloud.
  • Une variable d'environnement CLOUD_RUN_TASK_INDEX est transmise par les tâches Cloud Run. Ces tâches 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. Sa 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, fait 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 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

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

62e50ebe805f9a9c.png

6. Créer une tâche

Avant de créer une tâche, vous devez créer le 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 qui inclut la configuration nécessaire à son exécution.

gcloud alpha 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 pertinente si chacune d'elles peut traiter indépendamment un sous-ensemble de vos données. Pour cela, 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. Examinez --tasks=2 dans cet exemple. Cet élément garantit l'exécution de 2 conteneurs pour les 2 URL que nous souhaitons traiter.

Chaque tâche peut durer jusqu'à 1 heure. Vous pouvez réduire ce délai à l'aide de l'indicateur --task-timeout, comme dans cet exemple. Toutes les tâches doivent aboutir pour que l'intégralité de la tâche s'effectue correctement. Par défaut, les tâches ayant échoué ne font pas l'objet de nouvelles tentatives. Vous pouvez configurer de nouvelles tentatives en cas d'échec des tâches. Si une tâche dépasse le nombre de tentatives spécifié, l'ensemble de 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). Si le backend auquel accèdent les tâches à exécuter en parallèle dispose d'une évolutivité limitée, il peut être utile de réduire le nombre d'exécutions en parallèle. Par exemple : une base de données qui accepte un nombre limité de connexions actives. Vous pouvez réduire le nombre d'exécutions en parallèle à l'aide de l'indicateur --parallelism.

7. Exécuter une tâche

Avant d'exécuter la tâche, répertoriez-la pour vérifier qu'elle a bien été créée :

gcloud alpha run jobs list

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

Exécutez la tâche à l'aide de la commande suivante :

gcloud alpha run jobs execute screenshot

La tâche est exécutée. Vous pouvez répertorier les exécutions actuelles et passées :

gcloud alpha 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

Décrivez l'exécution. La coche verte et le message tasks completed successfully devraient s'afficher :

gcloud alpha 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

Vous pouvez également accéder à la page Tâches Cloud Run de Cloud Console pour connaître l'état :

e59ed4e532b974b1.png

Si vous consultez le bucket Cloud Storage, vous devriez voir les deux fichiers de captures d'écran créés :

f2f86e60b94ba47c.png

Vous devez parfois arrêter une exécution avant la fin (par exemple pour modifier certains paramètres ou en cas d'erreur dans le code, afin d'éviter de consommer inutilement du temps de calcul).

Pour arrêter l'exécution de la tâche, vous devez la supprimer :

gcloud alpha run jobs executions delete screenshot-znkmm

8. Mettre à jour une tâche

Lors de la prochaine exécution, les tâches Cloud Run ne récupéreront pas automatiquement les nouvelles versions de votre conteneur. Si vous modifiez le code de votre tâche, vous devez recréer le conteneur et mettre à jour la tâche. Utilisez des images taguées pour identifier la version de l'image actuellement employée.

De même, vous devez mettre à jour la tâche si vous souhaitez modifier certaines des variables de configuration. Les nouveaux paramètres de conteneur et de configuration s'appliqueront aux exécutions ultérieures.

Mettez à jour la tâche et modifiez les pages pour lesquelles l'application fait des captures d'écran dans l'indicateur --args. Mettez également à jour l'indicateur --tasks pour refléter le nombre de pages.

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

Exécutez à nouveau la tâche. Transmettez cette fois l'indicateur --wait pour attendre la fin des exécutions :

gcloud alpha run jobs execute screenshot --wait

Après quelques secondes, 3 captures d'écran supplémentaires apparaissent dans le bucket :

ce91c96dcfd271bb.png

9. Planifier une tâche

Cet atelier de programmation vous a montré jusqu'ici comment exécuter des tâches manuellement. Dans un scénario réel, vous devrez probablement exécuter des tâches en réponse à un événement ou selon un calendrier. Vous pouvez le faire via l'API REST de Cloud Run. Voyons comment exécuter la tâche de capture d'écran selon un calendrier à l'aide de Cloud Scheduler.

Commencez par vous assurer que l'API Cloud Scheduler est activée :

gcloud services enable cloudscheduler.googleapis.com

Créez une tâche Cloud Scheduler pour exécuter la tâche Cloud Run tous les jours à 9h :

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

Vérifiez que la tâche Cloud Scheduler est créée et prête à appeler la tâche Cloud Run :

gcloud scheduler jobs list

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

Pour effectuer un test, déclenchez manuellement Cloud Scheduler :

gcloud scheduler jobs run screenshot-scheduled

Après quelques secondes, vous devriez voir 3 captures d'écran supplémentaires, ajoutées par l'appel de Cloud Scheduler :

971ea598020cf9ba.png

10. Félicitations

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

Points abordés

  • Utiliser une application pour faire 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 la tâche avec Cloud Scheduler