1. نظرة عامة
في هذا الدرس العملي، ستستند إلى الدرس العملي السابق وتضيف خدمة صور مصغّرة. خدمة الصور المصغّرة هي حاوية ويب تأخذ صورًا كبيرة وتنشئ منها صورًا مصغّرة.
عند تحميل الصورة إلى Cloud Storage، يتم إرسال إشعار عبر Cloud Pub/Sub إلى حاوية ويب Cloud Run، والتي تعمل بعد ذلك على تغيير حجم الصور وحفظها مرة أخرى في حزمة أخرى في Cloud Storage.

أهداف الدورة التعليمية
- Cloud Run
- Cloud Storage
- خدمة Cloud Pub/Sub
2. الإعداد والمتطلبات
إعداد البيئة بالسرعة التي تناسبك
- سجِّل الدخول إلى Google Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.



- اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة من الأحرف لا تستخدمها Google APIs، ويمكنك تعديلها في أي وقت.
- يجب أن يكون رقم تعريف المشروع فريدًا في جميع مشاريع Google Cloud، كما أنّه غير قابل للتغيير (لا يمكن تغييره بعد ضبطه). تنشئ Cloud Console تلقائيًا سلسلة فريدة، ولا يهمّك عادةً ما هي. في معظم دروس الترميز، عليك الرجوع إلى رقم تعريف المشروع (ويتم تحديده عادةً على أنّه
PROJECT_ID)، لذا إذا لم يعجبك، يمكنك إنشاء رقم آخر عشوائي، أو يمكنك تجربة رقمك الخاص ومعرفة ما إذا كان متاحًا. ثم يتم "تجميده" بعد إنشاء المشروع. - هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات عن كل هذه القيم الثلاث في المستندات.
- بعد ذلك، عليك تفعيل الفوترة في Cloud Console من أجل استخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس التطبيقي حول الترميز الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا الدرس التطبيقي حول الترميز، اتّبِع أي تعليمات "تنظيف" واردة في نهاية الدرس. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.
بدء Cloud Shell
على الرغم من إمكانية تشغيل Google Cloud عن بُعد من الكمبيوتر المحمول، ستستخدم في هذا الدرس العملي Google Cloud Shell، وهي بيئة سطر أوامر تعمل في السحابة الإلكترونية.
من وحدة تحكّم Google Cloud Platform، انقر على رمز Cloud Shell في شريط الأدوات العلوي الأيسر:

لن يستغرق توفير البيئة والاتصال بها سوى بضع لحظات. عند الانتهاء، من المفترض أن يظهر لك ما يلي:

يتم تحميل هذه الآلة الافتراضية مزوّدة بكل أدوات التطوير التي ستحتاج إليها. توفّر هذه الخدمة دليلًا منزليًا ثابتًا بسعة 5 غيغابايت، وتعمل على Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. يمكن إكمال جميع المهام في هذا التمرين المعملي باستخدام متصفّح فقط.
3- تفعيل واجهات برمجة التطبيقات
في هذا التمرين العملي، ستحتاج إلى Cloud Build لإنشاء صور الحاويات وCloud Run لنشر الحاوية.
فعِّل واجهتَي برمجة التطبيقات من Cloud Shell:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
من المفترض أن تظهر لك العملية التي تمّت بنجاح:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. إنشاء حزمة أخرى
سيتم تخزين الصور المصغّرة للصور التي تم تحميلها في حزمة أخرى. لنستخدِم gsutil لإنشاء الحزمة الثانية.
داخل 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
في النهاية، يجب أن يكون لديك حزمة جديدة متاحة للجميع:

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:

يمكنك أيضًا فتح المحرّر في نافذة متصفّح مخصّصة للحصول على مساحة أكبر على الشاشة.
الاعتمادية
يحدّد ملف 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 للويب. يتم استخدام وحدة body-parser لتسهيل تحليل الطلبات الواردة. يتم استخدام Bluebird للتعامل مع الوعود، و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.
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، لأنّ الطلبات الواردة هي في الواقع حمولات 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);
}
});
نتلقّى حمولات البيانات الواردة هذه على عنوان 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، كحمولة 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، وحزمة الوجهة التي سنخزّن فيها الصورة الناتجة. نستخدم واجهة برمجة التطبيقات المضمّنة 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
بعد دقيقة أو دقيقتين، من المفترض أن تنجح عملية الإنشاء:

من المفترض أن يعرض قسم "السجلّ" في Cloud Build عملية الإنشاء الناجحة أيضًا:

عند النقر على رقم تعريف الإصدار لعرض التفاصيل، من المفترض أن تظهر لك صورة الحاوية التي تم تحميلها إلى Cloud Registry (GCR) في علامة التبويب "عناصر الإصدار":

إذا أردت، يمكنك التأكّد من أنّ صورة الحاوية تعمل محليًا في 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 إلى خدمة داخلية لا يتم تشغيلها إلا من خلال حسابات خدمة محدّدة.
في حال نجح النشر، من المفترض أن تظهر لك النتيجة التالية:

إذا انتقلت إلى واجهة مستخدم وحدة التحكّم في السحابة الإلكترونية، من المفترض أن تلاحظ أيضًا أنّه تم نشر الخدمة بنجاح:

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 أو قبل ذلك، امنح حساب خدمة Pub/Sub الدور iam.serviceAccountTokenCreator:
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
قد يستغرق تطبيق تغييرات "إدارة الهوية وإمكانية الوصول" بضع دقائق.
أخيرًا، أنشئ اشتراكًا في 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، وسيظهر لك الاشتراك في أسفل الصفحة:

11. اختبار الخدمة
لاختبار ما إذا كان الإعداد يعمل، حمِّل صورة جديدة إلى حزمة uploaded-pictures وتحقّق في حزمة thumbnails من ظهور الصور الجديدة التي تم تغيير حجمها على النحو المتوقّع.
يمكنك أيضًا التحقّق من السجلّات لمعرفة ما إذا كانت رسائل التسجيل تظهر أثناء تنفيذ الخطوات المختلفة لخدمة Cloud Run:

12. التنظيف (اختياري)
إذا كنت لا تنوي مواصلة استخدام المختبرات الأخرى في السلسلة، يمكنك تنظيف الموارد لتوفير التكاليف ولتكون مواطنًا جيدًا للخدمات السحابية بشكل عام. يمكنك تنظيف الموارد بشكل فردي على النحو التالي.
حذف الحزمة:
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 حول موضوع معيّن عند تحميل صورة جديدة.
- تم تحديد عمليات الربط والحسابات المطلوبة في إدارة الهوية وإمكانية الوصول (على عكس Cloud Functions حيث يتم تنفيذ كل ذلك تلقائيًا، يتم ضبطها يدويًا هنا).
- أنشأنا اشتراكًا لكي تتلقّى خدمة Cloud Run رسائل Pub/Sub.
- عند تحميل صورة جديدة إلى الحزمة، يتم تغيير حجم الصورة بفضل خدمة Cloud Run الجديدة.
المواضيع التي تناولناها
- Cloud Run
- Cloud Storage
- خدمة Cloud Pub/Sub