תחילת העבודה עם משימות ב-Cloud Run

1. מבוא

1965fab24c502bd5.png

סקירה כללית

Cloud Run services מתאים לקונטיינרים שפועלים ללא הגבלת זמן ומאזינים לבקשות HTTP, בעוד ש-jobs ב-Cloud Run מתאים יותר לקונטיינרים שפועלים עד לסיום (נכון לעכשיו עד 24 שעות) ולא מטפלים בבקשות. לדוגמה, עיבוד רשומות ממסד נתונים, עיבוד רשימת קבצים מקטגוריה של Cloud Storage או פעולה שפועלת לאורך זמן, כמו חישוב של פאי, יתאימו אם יוטמעו כמשימה ב-Cloud Run.

לג'ובים אין אפשרות לטפל בבקשות או להאזין ליציאה. המשמעות היא שבניגוד לשירותים ב-Cloud Run, משימות לא צריכות לכלול שרת אינטרנט. במקום זאת, קונטיינרים של משימות צריכים לצאת כשהם מסיימים.

במשימות של Cloud Run, אפשר להריץ כמה עותקים של הקונטיינר במקביל על ידי ציון מספר המשימות. כל משימה מייצגת עותק אחד של הקונטיינר שפועל. שימוש בכמה משימות יכול להיות שימושי אם כל משימה יכולה לעבד באופן עצמאי קבוצת משנה של הנתונים. לדוגמה, עיבוד של 10,000 רשומות מ-Cloud SQL או של 10,000 קבצים מ-Cloud Storage יכול להתבצע מהר יותר באמצעות 10 משימות לעיבוד של 1,000 רשומות או קבצים, כל אחת במקביל.

השימוש במשימות של Cloud Run כולל שני שלבים:

  1. יצירת משימה: המשימה כוללת את כל ההגדרות שנדרשות להרצת המשימה, כמו קובץ האימג' בקונטיינר, האזור ומשתני הסביבה.
  2. הפעלת העבודה: הפעולה הזו יוצרת הרצה חדשה של העבודה. אפשר גם להגדיר את המשימה להרצה לפי לוח זמנים באמצעות Cloud Scheduler.

ב-codelab הזה, קודם בודקים אפליקציית Node.js כדי לצלם צילומי מסך של דפי אינטרנט ולאחסן אותם ב-Cloud Storage. לאחר מכן יוצרים קובץ אימג' של קונטיינר לאפליקציה, מריצים אותו במשימות של Cloud Run, מעדכנים את המשימה כדי לעבד עוד דפי אינטרנט ומריצים את המשימה לפי לוח זמנים באמצעות Cloud Scheduler.

מה תלמדו

  • איך משתמשים באפליקציה כדי לצלם מסך של דפי אינטרנט.
  • איך ליצור קובץ אימג' של קונטיינר לאפליקציה.
  • איך יוצרים עבודת Cloud Run לאפליקציה.
  • איך מריצים את האפליקציה כמשימה ב-Cloud Run.
  • איך מעדכנים את המשרה.
  • איך מתזמנים את המשימה באמצעות Cloud Scheduler.

2. הגדרה ודרישות

הגדרת סביבה בקצב עצמי

  1. נכנסים ל-Google Cloud Console ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או חשבון Google Workspace, אתם צריכים ליצור חשבון.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • שם הפרויקט הוא השם המוצג של המשתתפים בפרויקט הזה. זו מחרוזת תווים שלא נמצאת בשימוש ב-Google APIs. תמיד אפשר לעדכן את המיקום.
  • מזהה הפרויקט הוא ייחודי לכל הפרויקטים ב-Google Cloud, והוא קבוע (אי אפשר לשנות אותו אחרי שהוא מוגדר). מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית, ובדרך כלל לא צריך לדעת מה היא. ברוב ה-Codelabs, תצטרכו להפנות למזהה הפרויקט (בדרך כלל מסומן כ-PROJECT_ID). אם אתם לא אוהבים את המזהה שנוצר, אתם יכולים ליצור מזהה אקראי אחר. אפשר גם לנסות שם משתמש משלכם ולבדוק אם הוא זמין. אי אפשר לשנות את ההגדרה הזו אחרי השלב הזה, והיא נשארת לאורך הפרויקט.
  • לידיעתכם, יש ערך שלישי, מספר פרויקט, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על שלושת הערכים האלה מופיע במאמרי העזרה.
  1. בשלב הבא, תצטרכו להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבי Cloud או בממשקי API של Cloud. השלמת ה-codelab הזה לא תעלה לכם הרבה, אם בכלל. כדי להשבית את המשאבים ולוודא שלא תחויבו על שימוש בהם אחרי שתסיימו את המדריך הזה, תוכלו למחוק את המשאבים שיצרתם או למחוק את הפרויקט. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.

הפעלת Cloud Shell

אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-codelab הזה תשתמשו ב-Google Cloud Shell, סביבת שורת פקודה שפועלת ב-Cloud.

ב-Google Cloud Console, לוחצים על סמל Cloud Shell בסרגל הכלים שבפינה הימנית העליונה:

הפעלת Cloud Shell

הקצאת המשאבים והחיבור לסביבה יימשכו רק כמה רגעים. בסיום התהליך, אמור להופיע משהו כזה:

צילום מסך של טרמינל Google Cloud Shell שבו מוצג שהסביבה מחוברת

המכונה הווירטואלית הזו טעונה בכל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. אפשר לבצע את כל העבודה ב-Codelab הזה בדפדפן. לא צריך להתקין שום דבר.

הגדרת gcloud

ב-Cloud Shell, מגדירים את מזהה הפרויקט ואת האזור שבו רוצים לפרוס את המשימה של Cloud Run. שומרים אותם כמשתנים PROJECT_ID ו-REGION. בעתיד תוכלו לבחור אזור מתוך המיקומים של Cloud Run.

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

הפעלת ממשקי API

מפעילים את כל השירותים הנדרשים:

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

3. קבל את הקוד

קודם כל, בודקים אפליקציית Node.js כדי לצלם צילומי מסך של דפי אינטרנט ולאחסן אותם ב-Cloud Storage. בהמשך, יוצרים קובץ אימג' של קונטיינר לאפליקציה ומריצים אותו כמשימה ב-Cloud Run.

מריצים את הפקודה הבאה מ-Cloud Shell כדי לשכפל את קוד האפליקציה ממאגר הזה:

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

עוברים לספרייה שמכילה את האפליקציה:

cd jobs-demos/screenshot

פריסת הקובץ אמורה להיראות כך:

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

הנה תיאור קצר של כל קובץ:

  • screenshot.js מכיל את הקוד של Node.js לאפליקציה.
  • package.json מגדיר את יחסי התלות של הפרויקט בספריות.
  • Dockerfile מגדיר את קובץ האימג' של הקונטיינר.

4. הסבר על הקוד

כדי לעיין בקוד, לוחצים על הלחצן Open Editor בחלק העליון של חלון Cloud Shell כדי להשתמש בכלי לעריכת טקסט.

15a2cdc9b7f6dfc6.png

הנה הסבר קצר על כל קובץ.

screenshot.js

screenshot.js מוסיף קודם את Puppeteer ואת Cloud Storage כתלות. ‫Puppeteer היא ספריית Node.js שמשמשת ליצירת צילומי מסך של דפי אינטרנט:

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

יש פונקציה initBrowser לאתחול Puppeteer ופונקציה takeScreenshot לצילום מסך של כתובת URL נתונה:

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

בהמשך יש פונקציה לקבלת קטגוריית Cloud Storage או ליצירת קטגוריה כזו, ועוד פונקציה להעלאת צילום המסך של דף אינטרנט לקטגוריה:

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

לבסוף, הפונקציה main היא נקודת הכניסה:

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

כמה דברים שכדאי לדעת על השיטה main:

  • כתובות ה-URL מועברות כארגומנטים.
  • שם ה-bucket מועבר כמשתנה הסביבה BUCKET_NAME שהוגדר על ידי המשתמש. שם הקטגוריה חייב להיות ייחודי באופן גלובלי בכל Google Cloud.
  • משתנה הסביבה CLOUD_RUN_TASK_INDEX מועבר על ידי משימות Cloud Run. משימות ב-Cloud Run יכולות להריץ כמה עותקים של האפליקציה כמשימות ייחודיות. ‫CLOUD_RUN_TASK_INDEX מייצג את האינדקס של המשימה הפועלת. ערך ברירת המחדל הוא אפס כשמריצים את הקוד מחוץ למשימות של Cloud Run. כשהאפליקציה מופעלת כמספר משימות, כל משימה או קונטיינר בוחרים את כתובת ה-URL שהם אחראים לה, מצלמים צילום מסך ושומרים את התמונה בדלי.

package.json

קובץ package.json מגדיר את האפליקציה ומציין את יחסי התלות של Cloud Storage ו-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 מגדיר את קובץ האימג' בקונטיינר של האפליקציה עם כל הספריות והתלויות הנדרשות:

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

5. פריסת משימה

לפני שיוצרים משימה, צריך ליצור חשבון שירות שישמש להרצת המשימה.

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

מקצים לחשבון השירות את התפקיד storage.admin כדי שיהיה אפשר להשתמש בו ליצירת מאגרי מידע ואובייקטים.

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

עכשיו אפשר לפרוס משימת Cloud Run שכוללת את ההגדרות שנדרשות להרצת המשימה.

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-$RANDOM \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

הפקודה הזו משתמשת בפריסה מבוססת-מקור ויוצרת משימת Cloud Run בלי להריץ אותה.

שימו לב איך דפי האינטרנט מועברים כארגומנטים. שם הקטגוריה לשמירת צילומי המסך מועבר כמשתנה סביבה.

אפשר להריץ כמה עותקים של הקונטיינר במקביל על ידי ציון מספר המשימות להרצה באמצעות הדגל --tasks. כל משימה מייצגת עותק אחד של הקונטיינר שפועל. שימוש בכמה משימות יכול להיות שימושי אם כל משימה יכולה לעבד באופן עצמאי קבוצת משנה של הנתונים. כדי להקל על כך, כל משימה מודעת לאינדקס שלה, שמאוחסן במשתנה הסביבה CLOUD_RUN_TASK_INDEX. הקוד שלכם אחראי לקבוע איזה משימה מטפלת באיזה קבוצת משנה של הנתונים. שימו לב לדומיין --tasks=2 בדוגמה הזו. כך מוודאים ש-2 מאגרי תגים יפעלו עבור 2 כתובות ה-URL שאנחנו רוצים לעבד.

כל משימה יכולה לפעול עד 24 שעות. אפשר לקצר את משך הזמן הזה באמצעות הדגל --task-timeout, כמו בדוגמה הזו. כדי שהעבודה תושלם בהצלחה, כל המשימות צריכות להצליח. כברירת מחדל, לא מתבצע ניסיון חוזר של משימות שנכשלו. אפשר להגדיר ניסיון חוזר של משימות שנכשלו. אם מספר הניסיונות החוזרים של משימה מסוימת חורג מהמספר המקסימלי, העבודה כולה נכשלת.

כברירת מחדל, העבודה תופעל עם כמה שיותר משימות במקביל. הערך הזה יהיה שווה למספר המשימות בעבודה, עד למקסימום של 100. יכול להיות שתרצו להגדיר רמת מקביליות נמוכה יותר למשימות שמתבצעת בהן גישה לחלק האחורי של האתר עם יכולת הרחבה מוגבלת. לדוגמה, מסד נתונים שתומך במספר מוגבל של חיבורים פעילים. אפשר להקטין את רמת ההקביליות באמצעות הדגל --parallelism.

6. הרצת משימה

לפני שמריצים את העבודה, מריצים את הפקודה list כדי לוודא שהיא נוצרה:

gcloud run jobs list

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

מריצים את העבודה באמצעות הפקודה הבאה:

gcloud run jobs execute screenshot --region=$REGION

המשימה תתבצע. אפשר לראות רשימה של ההרצות הנוכחיות והקודמות:

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

מתארים את הביצוע. אמורים להופיע סימן וי ירוק וההודעה 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-$RANDOM

אפשר גם לבדוק את הסטטוס בדף Cloud Run jobs ב-Cloud Console:

1afde14d65f0d9ce.png

אם בודקים את הקטגוריה של Cloud Storage, אמורים לראות את שני קובצי צילומי המסך שנוצרו:

7c4d355f6f65106.png

לפעמים צריך להפסיק הרצה לפני שהיא מסתיימת – למשל, אם מבינים שצריך להריץ את העבודה עם פרמטרים שונים או שיש שגיאה בקוד, ולא רוצים לבזבז זמן מיותר על חישובים.

כדי להפסיק את הביצוע של עבודה, צריך למחוק את הביצוע:

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

7. עדכון משרה

גרסאות חדשות של הקונטיינר לא נאספות אוטומטית על ידי משימות Cloud Run בהרצה הבאה. אם משנים את הקוד של העבודה, צריך לבנות מחדש את מאגר התגים ולעדכן את העבודה. שימוש בתמונות מתויגות יעזור לכם לזהות איזו גרסה של התמונה נמצאת בשימוש כרגע.

באופן דומה, תצטרכו לעדכן את הגדרות העבודה אם תרצו לעדכן חלק מהמשתנים של ההגדרה. ההרצות הבאות של העבודה יתבססו על מאגר התגים החדש ועל הגדרות התצורה החדשות.

מעדכנים את המשימה ומשנים את הדפים שהאפליקציה מצלמת מהם צילומי מסך בדגל --args. צריך לעדכן גם את הדגל --tasks כדי לשקף את מספר הדפים.

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

מריצים את העבודה שוב. הזמן הזה מועבר בדגל --wait כדי להמתין לסיום ההרצות:

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

אחרי כמה שניות, אמורים להתווסף עוד 3 צילומי מסך למאגר:

ed0cbe0b5a5f9144.png

8. תזמון משימה

עד עכשיו הפעלתם את העבודות באופן ידני. בתרחיש מהעולם האמיתי, סביר להניח שתרצו להריץ משימות בתגובה לאירוע או לפי לוח זמנים. נראה עכשיו איך מריצים את משימת צילום המסך לפי לוח זמנים באמצעות Cloud Scheduler.

קודם צריך לוודא ש-Cloud Scheduler API מופעל:

gcloud services enable cloudscheduler.googleapis.com

עוברים לדף הפרטים של Cloud Run jobs ולוחצים על הקטע Triggers:

3ae456368905472f.png

לוחצים על הלחצן Add Scheduler Trigger:

48cbba777f75e1eb.png

חלונית תיפתח בצד שמאל. יוצרים עבודה ב-Scheduler שתפעל כל יום בשעה 9:00 עם ההגדרה הזו ובוחרים באפשרות Continue:

81fd098be0db216.png

בדף הבא, בוחרים את חשבון השירות שמוגדר כברירת מחדל ב-Compute ולוחצים על Create:

fe479501dfb91f9f.png

עכשיו אמור להופיע טריגר חדש של Cloud Scheduler שיצרתם:

5a7bc6d96b970b92.png

לוחצים על View Details כדי לעבור לדף Cloud Scheduler.

אפשר לחכות עד השעה 9:00 כדי שהכלי לתזמון יתחיל לפעול, או להפעיל את Cloud Scheduler באופן ידני על ידי בחירת Force Run:

959525f2c8041a6a.png

אחרי כמה שניות, אמורה להופיע הודעה שהמשימה של Cloud Scheduler בוצעה בהצלחה:

d64e03fc84d61145.png

בנוסף, אמורים להופיע עוד 3 צילומי מסך שנוספו לשיחה מ-Cloud Scheduler:

56398a0e827de8b0.png

9. מזל טוב

כל הכבוד, סיימתם את ה-Codelab!

ניקוי (אופציונלי)

כדי להימנע מחיובים, מומלץ למחוק את המשאבים.

אם לא צריך את הפרויקט, אפשר פשוט למחוק אותו:

gcloud projects delete $PROJECT_ID

אם אתם צריכים את הפרויקט, אתם יכולים למחוק משאבים בנפרד.

מחיקת קוד המקור:

rm -rf ~/jobs-demos/

מחיקת מאגר Artifact Registry:

gcloud artifacts repositories delete containers --location=$REGION

מוחקים את חשבון השירות:

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

מחיקת משימת Cloud Run:

gcloud run jobs delete screenshot --region=$REGION

מחיקת המשימה ב-Cloud Scheduler:

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

מוחקים את הקטגוריה של Cloud Storage:

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

מה נכלל

  • איך משתמשים באפליקציה כדי לצלם מסך של דפי אינטרנט.
  • איך ליצור קובץ אימג' של קונטיינר לאפליקציה.
  • איך יוצרים עבודת Cloud Run לאפליקציה.
  • איך מריצים את האפליקציה כמשימה ב-Cloud Run.
  • איך מעדכנים את המשרה.
  • איך מתזמנים את המשימה באמצעות Cloud Scheduler.