תמונה יומית: שיעור Lab 3 – יצירת קולאז' של התמונות האחרונות

1. סקירה כללית

בשיעור ה-Lab הזה תיצרו שירות חדש של Cloud Run, שירות קולאז', שיופעל על ידי Cloud Scheduler במרווחי זמן קבועים. השירות מאחזר את התמונות האחרונות שהועלו ויוצר מהן קולאז': הוא מוצא את רשימת התמונות האחרונות ב-Cloud Firestore, ואז מוריד את קובצי התמונות בפועל מ-Cloud Storage.

df20f5d0402b54b4.png

מה תלמדו

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

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

הגדרת סביבה בקצב אישי

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

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

מפעילים את Cloud Shell

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

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

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

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

‫3. הפעלת ממשקי ה-API

תצטרכו Cloud Scheduler כדי להפעיל את שירות Cloud Run במרווחי זמן קבועים. מוודאים שהיא מופעלת:

gcloud services enable cloudscheduler.googleapis.com

הפעולה אמורה להסתיים בהצלחה:

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. שיבוט הקוד

משכפלים את הקוד, אם עדיין לא עשיתם את זה בסדנת הקוד הקודמת:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

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

cd serverless-photosharing-workshop/services/collage/nodejs

הפריסה של הקובץ בשירות תהיה כזו:

services
 |
 ├── collage
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

בתוך התיקייה יש 3 קבצים:

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

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

תלות

בקובץ package.json מוגדרים יחסי התלות בספריות הנדרשות:

{
  "name": "collage_service",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "bluebird": "^3.7.2",
    "express": "^4.17.1",
    "imagemagick": "^0.1.3",
    "@google-cloud/firestore": "^4.9.9",
    "@google-cloud/storage": "^5.8.3"
  }
}

אנחנו מסתמכים על ספריית Cloud Storage כדי לקרוא ולשמור קובצי תמונה ב-Cloud Storage. אנחנו מגדירים תלות ב-Cloud Firestore כדי לאחזר מטא-נתונים של תמונות ששמרנו קודם. ‫Express היא מסגרת אינטרנט של JavaScript / Node. ‫Bluebird משמש לטיפול בהבטחות, ו-imagemagick היא ספריה לעריכת תמונות.

קובץ Docker

Dockerfile מגדיר את קובץ האימג' של קונטיינר עבור האפליקציה:

FROM node:14-slim

# installing Imagemagick
RUN set -ex; \
  apt-get -y update; \
  apt-get -y install imagemagick; \
  rm -rf /var/lib/apt/lists/*

WORKDIR /picadaily/services/collage
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]

אנחנו משתמשים בתמונת בסיס קלה של Node 14. אנחנו מתקינים את ספריית imagemagick. לאחר מכן אנחנו מתקינים את מודולי ה-NPM שנדרשים לקוד שלנו, ומריצים את קוד הצומת באמצעות npm start.

index.js

בואו נבחן את הקוד index.js:

const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');

אנחנו צריכים את התלות השונות שנדרשות להפעלת התוכנית שלנו: Express הוא מסגרת האינטרנט של Node שבה נשתמש, ImageMagick היא הספרייה לביצוע מניפולציות על תמונות, Bluebird היא ספרייה לטיפול בהבטחות של JavaScript, ‏ Path משמשת לטיפול בנתיבים של קבצים וספריות, ואז Storage ו-Firestore משמשות לעבודה עם Google Cloud Storage (המאגרי תמונות שלנו) ומאגר הנתונים של Cloud Firestore, בהתאמה.

const app = express();

app.get('/', async (req, res) => {
    try {
        console.log('Collage request');

        /* ... */

    } catch (err) {
        console.log(`Error: creating the collage: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

למעלה אפשר לראות את המבנה של ה-handler של Node: האפליקציה שלנו מגיבה לבקשות HTTP GET. אנחנו גם מבצעים טיפול בשגיאות למקרה שמשהו ישתבש. עכשיו נבדוק מה יש בתוך המבנה הזה.

const thumbnailFiles = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
    .where('thumbnail', '==', true)
    .orderBy('created', 'desc')
    .limit(4).get();

if (snapshot.empty) {
    console.log('Empty collection, no collage to make');
    res.status(204).send("No collage created.");
} else {

    /* ... */

}

כדי ליצור קולאז', צריך להעלות לפחות 4 תמונות (שנוצרו להן תמונות ממוזערות).

אנחנו מאחזרים את 4 התמונות האחרונות שהמשתמשים העלו, מתוך המטא-נתונים שמאוחסנים ב-Cloud Firestore. אנחנו בודקים אם האוסף שנוצר ריק או לא, ואז ממשיכים הלאה בענף else של הקוד.

בואו נאסוף את רשימת שמות הקבצים:

snapshot.forEach(doc => {
    thumbnailFiles.push(doc.id);
});
console.log(`Picture file names: ${JSON.stringify(thumbnailFiles)}`);

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

const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);

await Promise.all(thumbnailFiles.map(async fileName => {
    const filePath = path.resolve('/tmp', fileName);
    await thumbBucket.file(fileName).download({
        destination: filePath
    });
}));
console.log('Downloaded all thumbnails');

אחרי שהתמונות הממוזערות האחרונות יועלו, נשתמש בספריית ImageMagick כדי ליצור רשת 4x4 של התמונות הממוזערות האלה. אנחנו משתמשים בספריית Bluebird וביישום Promise שלה כדי להפוך את הקוד מבוסס-callback לקוד ידידותי ל-async / await, ואז אנחנו ממתינים להבטחה שיוצרת את קולאז' התמונות:

const collagePath = path.resolve('/tmp', 'collage.png');

const thumbnailPaths = thumbnailFiles.map(f => path.resolve('/tmp', f));
const convert = Promise.promisify(im.convert);
await convert([
    '(', ...thumbnailPaths.slice(0, 2), '+append', ')',
    '(', ...thumbnailPaths.slice(2), '+append', ')',
    '-size', '400x400', 'xc:none', '-background', 'none',  '-append',
    collagePath]);
console.log("Created local collage picture");

תמונת הקולאז' נשמרה בדיסק באופן מקומי בתיקייה הזמנית, ועכשיו צריך להעלות אותה ל-Cloud Storage ולהחזיר תגובה של הצלחה (קוד סטטוס 2xx):

await thumbBucket.upload(collagePath);
console.log("Uploaded collage to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}");

res.status(204).send("Collage created.");

עכשיו צריך לגרום לסקריפט Node להאזין לבקשות נכנסות:

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started collage service on port ${PORT}`);
});

בסוף קובץ המקור, יש לנו את ההוראות להפעלה של אפליקציית האינטרנט ב-Express ביציאת ברירת המחדל 8080.

6. בדיקה מקומית

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

בתיקייה collage/nodejs, מתקינים את יחסי התלות של npm ומפעילים את השרת:

npm install; npm start

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

Started collage service on port 8080

כדי לצאת, לוחצים על CTRL-C.

7. פיתוח ופריסה ב-Cloud Run

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

gcloud config set run/region europe-west1
gcloud config set run/platform managed

כדי לבדוק שההגדרה מוגדרת:

gcloud config list

...
[run]
platform = managed
region = europe-west1

במקום ליצור ולפרסם את קובץ האימג' של הקונטיינר באופן ידני באמצעות Cloud Build, אפשר גם להסתמך על Cloud Run כדי ליצור את קובץ האימג' של הקונטיינר באמצעות Google Cloud Buildpacks.

מריצים את הפקודה הבאה כדי ליצור את קובץ האימג' של הקונטיינר:

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
SERVICE_NAME=collage-service
gcloud run deploy $SERVICE_NAME \
    --source . \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

שימו לב לדגל –-source. זוהי פריסה מבוססת-מקור ב-Cloud Run. אם יש קובץ Dockerfile בספריית קוד המקור, קוד המקור שהועלה ייבנה באמצעות קובץ Dockerfile הזה. אם אין Dockerfile בספריית קוד המקור, חבילות ה-Buildpack של Google Cloud מזהות באופן אוטומטי את השפה שבה אתם משתמשים ומאחזרות את התלות של הקוד כדי ליצור קובץ אימג' של קונטיינר שמוכן לייצור, באמצעות קובץ אימג' בסיסי מאובטח שמנוהל על ידי Google. הפעולה הזו מסמנת ל-Cloud Run להשתמש ב-Google Cloud Buildpacks כדי ליצור את קובץ האימג' של הקונטיינר שמוגדר ב-Dockerfile.

חשוב גם לציין שפריסה שמבוססת על מקור משתמשת ב-Artifact Registry כדי לאחסן קונטיינרים שנבנו. ‫Artifact Registry היא גרסה מודרנית של Google Container Registry. אם ה-API לא מופעל בפרויקט, ה-CLI יציג בקשה להפעיל אותו, וייצור מאגר בשם cloud-run-source-deploy באזור שבו אתם מבצעים את הפריסה.

הדגל --no-allow-unauthenticated הופך את שירות Cloud Run לשירות פנימי שיופעל רק על ידי חשבונות שירות ספציפיים.

8. הגדרת Cloud Scheduler

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

יוצרים חשבון שירות:

SERVICE_ACCOUNT=collage-scheduler-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
   --display-name "Collage Scheduler Service Account"

נותנים לחשבון השירות הרשאה להפעיל את שירות Cloud Run:

gcloud run services add-iam-policy-binding $SERVICE_NAME \
   --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --role=roles/run.invoker

יוצרים משימה של Cloud Scheduler שתופעל כל דקה:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud scheduler jobs create http $SERVICE_NAME-job --schedule "* * * * *" \
   --http-method=GET \
   --location=europe-west1 \
   --uri=$SERVICE_URL \
   --oidc-service-account-email=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --oidc-token-audience=$SERVICE_URL

אפשר לעבור לקטע Cloud Scheduler ב-Cloud Console כדי לראות שהוא מוגדר ומפנה לכתובת ה-URL של שירות Cloud Run:

35119e28c1da53f3.png

9. בדיקת השירות

כדי לבדוק אם ההגדרה פועלת, בודקים בדלי thumbnails אם יש תמונה של קולאז' (בשם collage.png). אפשר גם לבדוק את היומנים של השירות:

93922335a384be2e.png

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

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

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

gcloud run services delete $SERVICE_NAME -q

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

gcloud scheduler jobs delete $SERVICE_NAME-job -q

אפשר גם למחוק את כל הפרויקט:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

‫11. מעולה!

מעולה! יצרתם שירות מתוזמן: הודות ל-Cloud Scheduler, שדוחף הודעה כל דקה בנושא Pub/Sub, שירות הקולאז' של Cloud Run מופעל ויכול לצרף תמונות יחד כדי ליצור את התמונה שמתקבלת.

מה נכלל

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

השלבים הבאים