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

1. מבוא

1965fab24c502bd5.png

סקירה כללית

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

ב-מסוף Google Cloud, לוחצים על סמל 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. הקוד שלכם אחראי לקבוע איזה task מטפל באיזה קבוצת משנה של הנתונים. שימו לב לדומיין --tasks=2 בדוגמה הזו. כך מוודאים ש-2 מאגרי תגים יפעלו עבור 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-$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.