תמונה יומית: שיעור Lab 2 – יצירת תמונות ממוזערות של תמונות

תמונה יומית:
שיעור Lab 2 – יצירת תמונות ממוזערות של תמונות

מידע על Codelab זה

subjectהעדכון האחרון: נוב׳ 14, 2021
account_circleנכתב על ידי Guillaume Laforge, Mete Atamel

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

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

בזמן העלאת התמונה ל-Cloud Storage, נשלחת התראה דרך Cloud Pub/Sub למאגר אינטרנט של Cloud Run, ואז גודל התמונות משתנה ושומר אותן בחזרה בקטגוריה אחרת ב-Cloud Storage.

31fa4f8a294d90df.png

מה תלמדו

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

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

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

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

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

הפעלת Cloud Shell

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

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

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

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

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

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

מפעילים את שני ממשקי ה-API מ-Cloud Shell:

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

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

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

4.‏ יצירת קטגוריה נוספת

התמונות הממוזערות של התמונות שהועלו יאוחסנו בקטגוריה אחרת. נשתמש ב-gsutil כדי ליצור את הקטגוריה השנייה.

ב-Inside Cloud Shell, מגדירים משתנה לשם הקטגוריה הייחודי. השדה GOOGLE_CLOUD_PROJECT כבר מוגדר ב-Cloud Shell למזהה הפרויקט הייחודי שלכם. אפשר להוסיף אותו לשם הקטגוריה. לאחר מכן, יוצרים קטגוריה ציבורית במספר אזורים באירופה עם גישה אחידה ברמה:

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
gsutil mb -l EU gs://$BUCKET_THUMBNAILS
gsutil uniformbucketlevelaccess set on gs://$BUCKET_THUMBNAILS
gsutil iam ch allUsers:objectViewer gs://$BUCKET_THUMBNAILS

בסוף צריכה להיות לכם קטגוריה ציבורית חדשה:

8e75c8099938e972.png

5.‏ שכפול הקוד

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

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
cd serverless-photosharing-workshop/services/thumbnails/nodejs

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

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

בתיקייה thumbnails/nodejs נמצאים 3 קבצים:

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

6.‏ לבדיקת הקוד

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

3d145fe299dd8b3e.png

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

תלות

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

{
 
"name": "thumbnail_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. Firestore כדי לעדכן את המטא-נתונים של התמונה. Express היא מסגרת אינטרנט מסוג JavaScript / Node. מודול הכלי לניתוח הגוף משמש לניתוח בקשות נכנסות בקלות. כחול-בירד משמש לטיפול בהבטחות, ו-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/*; \
  mkdir /tmp/original; \
  mkdir /tmp/thumbnail;

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

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

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

const app = express();
app
.use(express.json());

קודם כל אנחנו דורשים את יחסי התלות הנדרשים, ויוצרים את אפליקציית האינטרנט Express שלנו. אנחנו מציינים שאנחנו רוצים להשתמש במנתח הגוף של JSON, כי בקשות נכנסות הן למעשה מטענים ייעודיים (payloads) של JSON שנשלחים לאפליקציה שלנו דרך בקשת POST.

app.post('/', async (req, res) => {
   
try {
       
// ...
   
} catch (err) {
        console
.log(`Error: creating the thumbnail: ${err}`);
        console
.error(err);
        res
.status(500).send(err);
   
}
});

אנחנו מקבלים את המטענים הייעודיים (payloads) הנכנסים בכתובת ה-URL הבסיסית או בכתובת ה-URL הבסיסית, ואריזוף את הקוד עם טיפול לוגי של שגיאות. כך יהיה לנו מידע טוב יותר על הסיבה לכך שמשהו נכשל בקוד. לשם כך אנחנו בוחנים את היומנים שיהיו גלויים בממשק Stackdriver Logging במסוף האינטרנט של Google Cloud.

const pubSubMessage = req.body;
console
.log(`PubSub message: ${JSON.stringify(pubSubMessage)}`);

const fileEvent = JSON.parse(Buffer.from(pubSubMessage.message.data, 'base64').toString().trim());
console
.log(`Received thumbnail request for file ${fileEvent.name} from bucket ${fileEvent.bucket}`);

בפלטפורמת Cloud Run, הודעות Pub/Sub נשלחות באמצעות בקשות HTTP POST, כמטענים ייעודיים (payloads) של JSON בפורמט:

{
 
"message": {
   
"attributes": {
     
"bucketId": "uploaded-pictures",
     
"eventTime": "2020-02-27T09:22:43.255225Z",
     
"eventType": "OBJECT_FINALIZE",
     
"notificationConfig": "projects/_/buckets/uploaded-pictures/notificationConfigs/28",
     
"objectGeneration": "1582795363255481",
     
"objectId": "IMG_20200213_181159.jpg",
     
"payloadFormat": "JSON_API_V1"
   
},
   
"data": "ewogICJraW5kIjogInN0b3JhZ2Ujb2JqZWN...FQUU9Igp9Cg==",
   
"messageId": "1014308302773399",
   
"message_id": "1014308302773399",
   
"publishTime": "2020-02-27T09:22:43.973Z",
   
"publish_time": "2020-02-27T09:22:43.973Z"
 
},
 
"subscription": "projects/serverless-picadaily/subscriptions/gcs-events-subscription"
}

מה שבאמת מעניין במסמך ה-JSON הזה הוא מה שמופיע במאפיין message.data, שהוא רק מחרוזת, אבל הוא מקודד ב-Base 64 את המטען הייעודי (payload) עצמו. לכן הקוד שלנו שלמעלה מפענח את תוכן Base 64 של המאפיין הזה. אחרי הפענוח, המאפיין data הזה מכיל מסמך JSON נוסף שמייצג את פרטי האירוע ב-Cloud Storage. המטא-נתונים האלה מציינים את שם הקובץ ושם הקטגוריה.

{
 
"kind": "storage#object",
 
"id": "uploaded-pictures/IMG_20200213_181159.jpg/1582795363255481",
 
"selfLink": "https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg",
 
"name": "IMG_20200213_181159.jpg",
 
"bucket": "uploaded-pictures",
 
"generation": "1582795363255481",
 
"metageneration": "1",
 
"contentType": "image/jpeg",
 
"timeCreated": "2020-02-27T09:22:43.255Z",
 
"updated": "2020-02-27T09:22:43.255Z",
 
"storageClass": "STANDARD",
 
"timeStorageClassUpdated": "2020-02-27T09:22:43.255Z",
 
"size": "4944335",
 
"md5Hash": "QzBIoPJBV2EvqB1EVk1riw==",
 
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg?generation=1582795363255481&alt=media",
 
"crc32c": "hQ3uHg==",
 
"etag": "CLmJhJu08ecCEAE="
}

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

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

const originalFile = path.resolve('/tmp/original', fileEvent.name);
const thumbFile = path.resolve('/tmp/thumbnail', fileEvent.name);

await bucket
.file(fileEvent.name).download({
    destination
: originalFile
});
console
.log(`Downloaded picture into ${originalFile}`);

אנחנו מאחזרים את השם של קטגוריית אחסון הפלט ממשתנה סביבה.

יש לנו את קטגוריית המקור שיצירת הקובץ שלה הפעילה את שירות Cloud Run, ואת קטגוריית היעד שבה נאחסן את התמונה שמתקבלת. אנחנו משתמשים ב-API המובנה path כדי לבצע טיפול מקומי בקבצים, כי ספריית Imagemagick תיצור את התמונה הממוזערת באופן מקומי בספרייה הזמנית /tmp. אנחנו await לשיחה אסינכרונית כדי להוריד את קובץ התמונה שהועלה.

const resizeCrop = Promise.promisify(im.crop);
await resizeCrop
({
        srcPath
: originalFile,
        dstPath
: thumbFile,
        width
: 400,
        height
: 400        
});
console
.log(`Created local thumbnail in ${thumbFile}`);

המודול imagemagick לא כל כך ידידותי ל-async / await, לכן אנחנו כוללים אותו בהבטחה של JavaScript (מסופקת על ידי מודול Bluebird). לאחר מכן קוראים לפונקציה האסינכרונית של שינוי הגודל / חיתוך שיצרנו, עם הפרמטרים של קובצי המקור והיעד, ועם המידות של התמונה הממוזערת שאנחנו רוצים ליצור.

await thumbBucket.upload(thumbFile);
console
.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);

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

const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(fileEvent.name);
await doc
.set({
    thumbnail
: true
}, {merge: true});
console
.log(`Updated Firestore about thumbnail creation for ${fileEvent.name}`);

res
.status(204).send(`${fileEvent.name} processed`);

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

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

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

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

7.‏ בדיקה מקומית

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

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

npm install; npm start

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

Started thumbnail generator on port 8080

כדי לצאת מהמצב הזה, אפשר להשתמש בCTRL-C.

8.‏ יצירה ופרסום של קובץ האימג' בקונטיינר

Cloud Run מפעיל קונטיינרים, אבל קודם צריך לפתח את קובץ האימג' של הקונטיינר (מוגדר ב-Dockerfile). אפשר להשתמש ב-Google Cloud Build כדי ליצור קובצי אימג' של קונטיינרים ולארח אותם ב-Google Container Registry.

בתוך תיקייה thumbnails/nodejs שבה נמצא Dockerfile, מריצים את הפקודה הבאה כדי ליצור את קובץ האימג' בקונטיינר:

gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service

לאחר דקה או שתיים, ה-build אמור להצליח:

b354b3a9a3631097.png

ה'היסטוריה' של Cloud Build צריך להציג גם את ה-build המוצלח:

df00f198dd2bf6bf.png

לחיצה על מזהה ה-build כדי לגשת לתצוגת הפרטים בקטע build Artifacts אתם אמורים לראות שקובץ האימג' בקונטיינר הועלתה ל-Cloud Registry (GCR):

a4577ce0744f73e2.png

אם רוצים, אפשר לוודא שוב שקובץ האימג' בקונטיינר פועל באופן מקומי ב-Cloud Shell:

docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service

הוא צריך להפעיל את השרת ביציאה 8080 בקונטיינר:

Started thumbnail generator on port 8080

כדי לצאת מהמצב הזה, אפשר להשתמש בCTRL-C.

9.‏ פריסה ב-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 Run:

SERVICE_NAME=thumbnail-service
gcloud run deploy $SERVICE_NAME \
    --image gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

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

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

c0f28e7d6de0024.png

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

9bfe48e3c8b597e5.png

10.‏ אירועי Cloud Storage ל-Cloud Run דרך Pub/Sub

השירות מוכן, אבל עדיין תצטרכו להגדיר אירועים של Cloud Storage לשירות Cloud Run החדש שנוצר. Cloud Storage יכול לשלוח אירועים של יצירת קבצים דרך Cloud Pub/Sub, אבל יש כמה שלבים לביצוע.

יוצרים נושא Pub/Sub בתור צינור עיבוד הנתונים לתקשורת:

TOPIC_NAME=cloudstorage-cloudrun-topic
gcloud pubsub topics create $TOPIC_NAME

יצירת התראות Pub/Sub כשקבצים מאוחסנים בקטגוריה:

BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT
gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES

יוצרים חשבון שירות למינוי Pub/Sub שניצור בהמשך:

SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
     --display-name "Cloud Run Pub/Sub Invoker"

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

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

אם הפעלת את חשבון השירות של Pub/Sub ב-8 באפריל 2021 או לפני כן, מקצים את התפקיד iam.serviceAccountTokenCreator לחשבון השירות של Pub/Sub:

PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
     --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
     --role=roles/iam.serviceAccountTokenCreator

הפצת השינויים ב-IAM תימשך כמה דקות.

לבסוף, יוצרים מינוי Pub/Sub באמצעות חשבון השירות:

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud pubsub subscriptions create $TOPIC_NAME-subscription --topic $TOPIC_NAME \
   --push-endpoint=$SERVICE_URL \
   --push-auth-service-account=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com

אתם יכולים לבדוק שנוצר מינוי. עוברים ל-Pub/Sub במסוף, בוחרים בנושא gcs-events ובתחתית המינוי אמור להופיע:

e8ab86dccb8d890.png

11.‏ בדיקת השירות

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

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

42c025e2d7d6ca3a.png

12.‏ הסרת המשאבים (אופציונלי)

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

מוחקים את הקטגוריה:

gsutil rb gs://$BUCKET_THUMBNAILS

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

gcloud run services delete $SERVICE_NAME -q

מוחקים את הנושא Pub/Sub:

gcloud pubsub topics delete $TOPIC_NAME

לחלופין, אפשר למחוק את הפרויקט כולו:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

13.‏ מעולה!

עכשיו הכול מוכן:

  • נוצרה התראה ב-Cloud Storage ששולחת הודעות Pub/Sub בנושא מסוים כשמעלים תמונה חדשה.
  • הגדרת קישורי ה-IAM והחשבונות הנדרשים (בניגוד ל-Cloud Functions, כאשר כל הפעולות אוטומטיות הן אוטומטיות, הן מוגדרות כאן באופן ידני).
  • יצירת מינוי כדי ששירות Cloud Run יקבל את הודעות Pub/Sub.
  • בכל פעם שמעלים תמונה חדשה לקטגוריה, גודל התמונה משתנה הודות לשירות Cloud Run החדש.

אילו נושאים דיברנו?

  • Cloud Run
  • Cloud Storage
  • Cloud Pub/Sub

השלבים הבאים