۱. مرور کلی
در این آزمایشگاه کد، شما بر اساس آزمایش قبلی، یک سرویس thumbnail اضافه میکنید. سرویس thumbnail یک کانتینر وب است که تصاویر بزرگ را میگیرد و از آنها thumbnail ایجاد میکند.
همزمان با آپلود تصویر در فضای ابری، یک اعلان از طریق Cloud Pub/Sub به یک کانتینر وب Cloud Run ارسال میشود که سپس تصاویر را تغییر اندازه داده و آنها را در یک سطل دیگر در فضای ابری ذخیره میکند.

آنچه یاد خواهید گرفت
- اجرای ابری
- فضای ذخیرهسازی ابری
- میخانه/زیرشبکه ابری
۲. تنظیمات و الزامات
تنظیم محیط خودتنظیم
- وارد کنسول گوگل کلود شوید و یک پروژه جدید ایجاد کنید یا از یک پروژه موجود دوباره استفاده کنید. اگر از قبل حساب جیمیل یا گوگل ورک اسپیس ندارید، باید یکی ایجاد کنید .



- نام پروژه ، نام نمایشی برای شرکتکنندگان این پروژه است. این یک رشته کاراکتری است که توسط APIهای گوگل استفاده نمیشود و شما میتوانید آن را در هر زمانی بهروزرسانی کنید.
- شناسه پروژه باید در تمام پروژههای گوگل کلود منحصر به فرد باشد و تغییرناپذیر است (پس از تنظیم، قابل تغییر نیست). کنسول کلود به طور خودکار یک رشته منحصر به فرد تولید میکند؛ معمولاً برای شما مهم نیست که چیست. در اکثر آزمایشگاههای کد، باید به شناسه پروژه ارجاع دهید (و معمولاً با نام
PROJECT_IDشناخته میشود)، بنابراین اگر آن را دوست ندارید، یک شناسه تصادفی دیگر ایجاد کنید، یا میتوانید شناسه خودتان را امتحان کنید و ببینید آیا در دسترس است یا خیر. سپس پس از ایجاد پروژه، آن "منجمد" میشود. - یک مقدار سوم هم وجود دارد، شماره پروژه که برخی از APIها از آن استفاده میکنند. برای اطلاعات بیشتر در مورد هر سه این مقادیر به مستندات مراجعه کنید.
- در مرحله بعد، برای استفاده از منابع/APIهای ابری، باید پرداخت صورتحساب را در کنسول ابری فعال کنید . اجرای این آزمایشگاه کد، اگر اصلاً هزینهای نداشته باشد، هزینه زیادی نخواهد داشت. برای خاموش کردن منابع به طوری که پس از این آموزش متحمل پرداخت صورتحساب نشوید، دستورالعملهای «پاکسازی» موجود در انتهای آزمایشگاه کد را دنبال کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان ۳۰۰ دلاری هستند.
شروع پوسته ابری
اگرچه میتوان از راه دور و از طریق لپتاپ، گوگل کلود را مدیریت کرد، اما در این آزمایشگاه کد، از گوگل کلود شل ، یک محیط خط فرمان که در فضای ابری اجرا میشود، استفاده خواهید کرد.
از کنسول GCP روی آیکون Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:

آمادهسازی و اتصال به محیط فقط چند لحظه طول میکشد. وقتی تمام شد، باید چیزی شبیه به این را ببینید:

این ماشین مجازی مجهز به تمام ابزارهای توسعه مورد نیاز شماست. این ماشین یک دایرکتوری خانگی دائمی ۵ گیگابایتی ارائه میدهد و روی فضای ابری گوگل اجرا میشود که عملکرد شبکه و احراز هویت را تا حد زیادی بهبود میبخشد. تمام کارهای شما در این آزمایشگاه را میتوان به سادگی با یک مرورگر انجام داد.
۳. فعال کردن APIها
در این آزمایش، برای ساخت تصاویر کانتینر به 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.
۴. یک سطل دیگر ایجاد کنید
شما تصاویر کوچک (thumbnail) عکسهای آپلود شده را در یک سطل دیگر ذخیره خواهید کرد. بیایید از gsutil برای ایجاد سطل دوم استفاده کنیم.
درون Cloud Shell، یک متغیر برای نام منحصر به فرد سطل تنظیم کنید. Cloud Shell از قبل GOOGLE_CLOUD_PROJECT را برای شناسه منحصر به فرد پروژه شما تنظیم کرده است. میتوانید آن را به نام سطل اضافه کنید. سپس، یک سطل عمومی چند منطقهای در اروپا با دسترسی سطح یکنواخت ایجاد کنید:
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
در نهایت، شما باید یک سطل عمومی جدید داشته باشید:

۵. کد را کپی کنید
کد را کپی کنید و به دایرکتوری حاوی سرویس بروید:
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 ، سه فایل دارید:
-
index.jsشامل کد Node.js است. -
package.jsonوابستگیهای کتابخانه را تعریف میکند. -
Dockerfileتصویر کانتینر را تعریف میکند.
۶. کد را بررسی کنید
برای بررسی کد، میتوانید از ویرایشگر متن داخلی، با کلیک بر روی دکمهی Open Editor در بالای پنجرهی Cloud Shell، استفاده کنید:

همچنین میتوانید ویرایشگر را در یک پنجره مرورگر اختصاصی باز کنید تا فضای بیشتری روی صفحه نمایش داشته باشید.
وابستگیها
فایل 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 یک چارچوب وب جاوا اسکریپت/Node است. ماژول body-parser برای تجزیه آسان درخواستهای ورودی استفاده میشود. Bluebird برای مدیریت promiseها استفاده میشود و Imagemagick کتابخانهای برای دستکاری تصاویر است.
داکرفایل
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" ]
ایمیج پایه Node 14 است و از کتابخانه imagemagick برای دستکاری تصویر استفاده میشود. برخی دایرکتوریهای موقت برای نگهداری فایلهای تصویر اصلی و تصاویر بندانگشتی ایجاد میشوند. سپس ماژولهای NPM مورد نیاز کد ما قبل از شروع کد با npm start نصب میشوند.
ایندکس.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 استفاده کنیم، زیرا درخواستهای ورودی در واقع فقط بارهای 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);
}
});
ما این payloadهای ورودی را در URL /base دریافت میکنیم و کد خود را با برخی از روشهای مدیریت منطق خطا پوشش میدهیم تا با بررسی گزارشهایی که از رابط 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، به عنوان فایلهای 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 کدگذاری میکند. به همین دلیل است که کد بالا محتوای 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 سازگار نیست، بنابراین ما آن را درون یک promise جاوا اسکریپت (که توسط ماژول Bluebird ارائه میشود) قرار میدهیم. سپس تابع تغییر اندازه/برش ناهمزمان را که ایجاد کردهایم با پارامترهای فایلهای منبع و مقصد و همچنین ابعاد تصویر کوچکی که میخواهیم ایجاد کنیم، فراخوانی میکنیم.
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
پس از آپلود فایل تصویر بندانگشتی در فضای ذخیرهسازی ابری، متادیتا را در فضای ذخیرهسازی ابری ابری بهروزرسانی خواهیم کرد تا یک پرچم بولی اضافه شود که نشان میدهد تصویر بندانگشتی برای این تصویر واقعاً ایجاد شده است:
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}`);
});
در انتهای فایل منبع، دستورالعملهایی داریم که اکسپرس را وادار میکند برنامه وب ما را روی پورت پیشفرض ۸۰۸۰ اجرا کند.
۷. به صورت محلی آزمایش کنید
قبل از انتشار در فضای ابری، کد را به صورت محلی آزمایش کنید تا از عملکرد آن مطمئن شوید.
درون پوشه thumbnails/nodejs ، وابستگیهای npm را نصب کرده و سرور را راهاندازی کنید:
npm install; npm start
اگر همه چیز خوب پیش رفته باشد، باید سرور روی پورت ۸۰۸۰ شروع به کار کند:
Started thumbnail generator on port 8080
برای خروج از CTRL-C استفاده کنید.
۸. ساخت و انتشار ایمیج کانتینر
Cloud Run کانتینرها را اجرا میکند، اما ابتدا باید تصویر کانتینر (تعریف شده در Dockerfile ) را بسازید. Google Cloud Build میتواند برای ساخت تصاویر کانتینر و سپس میزبانی در Google Container Registry استفاده شود.
داخل پوشه thumbnails/nodejs که Dockerfile در آن قرار دارد، دستور زیر را برای ساخت تصویر کانتینر اجرا کنید:
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
بعد از یک یا دو دقیقه، ساخت باید با موفقیت انجام شود:

بخش «تاریخچه» ساخت ابری باید ساخت موفقیتآمیز را نیز نشان دهد:

با کلیک بر روی شناسه ساخت برای مشاهده جزئیات، در تب "ساخت مصنوعات" باید ببینید که تصویر کانتینر در رجیستری ابر (GCR) آپلود شده است:

در صورت تمایل، میتوانید دوباره بررسی کنید که آیا تصویر کانتینر به صورت محلی در Cloud Shell اجرا میشود یا خیر:
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
باید سرور روی پورت ۸۰۸۰ در کانتینر شروع به کار کند:
Started thumbnail generator on port 8080
برای خروج از CTRL-C استفاده کنید.
۹. استقرار در 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 به یک سرویس داخلی تبدیل شود که فقط توسط حسابهای سرویس خاص فعال میشود.
اگر استقرار موفقیتآمیز باشد، باید خروجی زیر را مشاهده کنید:

اگر به رابط کاربری کنسول ابری بروید، باید ببینید که سرویس با موفقیت مستقر شده است:

۱۰. رویدادهای ذخیرهسازی ابری به 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 را در تاریخ ۸ آوریل ۲۰۲۱ یا قبل از آن فعال کردهاید، نقش 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 را انتخاب کنید و در پایین، باید اشتراک را ببینید:

۱۱. سرویس را آزمایش کنید
برای آزمایش اینکه آیا تنظیمات کار میکند، یک تصویر جدید را در بخش uploaded-pictures آپلود کنید و در بخش thumbnails بررسی کنید که تصاویر جدید با اندازه تغییر یافته مطابق انتظار ظاهر میشوند.
همچنین میتوانید گزارشها را دوباره بررسی کنید تا پیامهای گزارشگیری را ببینید، زیرا مراحل مختلف سرویس Cloud Run در حال انجام است:

۱۲. تمیز کردن (اختیاری)
اگر قصد ندارید با سایر آزمایشگاههای این مجموعه ادامه دهید، میتوانید منابع را پاکسازی کنید تا در هزینهها صرفهجویی کنید و در کل شهروند ابری خوبی باشید. میتوانید منابع را به صورت جداگانه و به شرح زیر پاکسازی کنید.
سطل را حذف کنید:
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
۱۳. تبریک میگویم!
حالا همه چیز سر جای خودش است:
- یک اعلان در فضای ذخیرهسازی ابری ایجاد شد که هنگام آپلود تصویر جدید، پیامهای Pub/Sub را در مورد یک موضوع ارسال میکند.
- اتصالات و حسابهای IAM مورد نیاز تعریف شدهاند (برخلاف توابع ابری که همه چیز خودکار است، در اینجا به صورت دستی پیکربندی میشود).
- یک اشتراک ایجاد کردیم تا سرویس Cloud Run ما پیامهای Pub/Sub را دریافت کند.
- هر زمان که تصویر جدیدی در سطل آپلود میشود، به لطف سرویس جدید Cloud Run، اندازه تصویر تغییر میکند.
آنچه ما پوشش دادهایم
- اجرای ابری
- فضای ذخیرهسازی ابری
- میخانه/زیرشبکه ابری