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

1. מבוא

1965fab24c502bd5.png

סקירה כללית

שירותים של Cloud Run מתאימים לקונטיינרים שפועלים ללא הגבלת זמן להאזנה לבקשות HTTP, ואילו משימות ב-Cloud Run מתאימות יותר לקונטיינרים שמסיימים לפעול (כרגע עד 24 שעות) ולא שולחים בקשות. לדוגמה, עיבוד רשומות ממסד נתונים, עיבוד רשימת קבצים מקטגוריה של Cloud Storage או פעולה ממושכת, כמו חישוב Pi, יהיו אפשרויות טובות אם הן מיושמות כמשימה ב-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 ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא משמשת את Google APIs. תמיד אפשר לעדכן.
  • Project ID הוא ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-codelabs תצטרכו להפנות למזהה הפרויקט שלכם (בדרך כלל מזוהה כ-PROJECT_ID). אם המזהה שנוצר לא מוצא חן בעיניכם, אתם יכולים ליצור מזהה אקראי אחר. לחלופין, אפשר לנסות שם משלך ולראות אם הוא זמין. לא ניתן לשנות אותו אחרי השלב הזה, והוא נשאר למשך הפרויקט.
  • לידיעתך, יש ערך שלישי, Project Number, שבו משתמשים בחלק מממשקי ה-API. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא יעלה הרבה כסף, אם בכלל. כדי להשבית משאבים ולא לצבור חיובים מעבר למדריך הזה, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט. משתמשים חדשים ב-Google Cloud זכאים להשתתף בתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

הפעלת Cloud Shell

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

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

84688a223b1c3a2.png

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

320e18ifiedb7fbe0.png

למכונה הווירטואלית הזו נטען כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 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 כדי לאתחל את 'יצירת בוב', ואת הפונקציה 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);
});

שימו לב לדברים הבאים לגבי ה-method main:

  • כתובות ה-URL מועברות כארגומנטים.
  • שם הקטגוריה מועבר כמשתנה הסביבה 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 \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

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

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

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

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

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

6. הפעלת משימה

לפני שמריצים את המשימה, צריך לרשום את המשימה כדי לראות שהיא נוצרה:

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

אתם יכולים גם להיכנס לדף המשימות ב-Cloud Run במסוף Cloud כדי לראות את הסטטוס:

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 ולוחצים על הקטע Triggers:

3ae456368905472f.png

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

48cbba777f75e1eb.png

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

81fd098be0db216.png

בדף הבא, בוחרים את חשבון שירות המחשוב שמוגדר כברירת מחדל ואז בוחרים באפשרות 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.