صورة يومية: التمرين المعملي 2 — إنشاء صور مصغرة للصور

1. نظرة عامة

في هذا التمرين المعملي حول الترميز، يمكنك الاستفادة من التمرين المعملي السابق وإضافة خدمة صور مصغرة. خدمة الصور المصغرة هي عبارة عن حاوية ويب تلتقط صورًا كبيرة منها وتنشئ صورًا مصغرة منها.

أثناء تحميل الصورة إلى Cloud Storage، يتم إرسال إشعار عبر Cloud Pub/Sub إلى حاوية ويب في Cloud Run، تُغيّر حجم الصور وتحفظها مرة أخرى في حزمة أخرى على Cloud Storage.

31fa4f8a294d90df.png

المُعطيات

  • Cloud Run
  • تخزين في السحابة الإلكترونية
  • خدمة Cloud Pub/Sub

2. الإعداد والمتطلبات

إعداد بيئة ذاتية

  1. سجِّل الدخول إلى Google Cloud Console وأنشئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

بدء Cloud Shell

مع أنّه يمكن إدارة Google Cloud عن بُعد من الكمبيوتر المحمول، ستستخدم في هذا الدرس التطبيقي Google Cloud Shell، وهي بيئة سطر أوامر يتم تشغيلها في السحابة الإلكترونية.

من وحدة تحكّم Google Cloud Platform، انقر على رمز Cloud Shell في شريط الأدوات العلوي الأيسر:

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

يتم تحميل هذه الآلة الافتراضية مزوّدة بكل أدوات التطوير التي ستحتاج إليها. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 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

في النهاية، يُفترض أن تكون لديك حزمة عامة جديدة:

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 أو عُقدة. تُستخدم وحدة تحليل النص الأساسي لتحليل الطلبات الواردة بسهولة. يتم استخدام 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" ]

الصورة الأساسية هي العقدة 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 الخاص بـ / 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، وحزمة الوجهة التي سنخزِّن فيها الصورة الناتجة. نستخدم واجهة برمجة التطبيقات 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

بعد دقيقة أو دقيقتين، من المفترض أن تنجح عملية الإنشاء:

b354b3a9a3631097.png

"سجلّ" إنشاء Cloud Build الإصدار الناجح أيضًا:

df00f198dd2bf6bf.png

النقر على رقم تعريف الإصدار للحصول على عرض التفاصيل في "إنشاء العناصر" من المفترض أن ترى أنّه تم تحميل صورة الحاوية إلى 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 على إحدى المناطق والنظام الأساسي المتوافقَين على managed:

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

يمكنك التحقّق من أنّه تم ضبط الإعدادات:

gcloud config list

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

شغّل الأمر التالي لنشر صورة الحاوية على تشغيل السحابة:

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. وهذا يجعل خدمة تشغيل السحابة الإلكترونية خدمة داخلية يتم تشغيلها فقط من خلال حسابات خدمة محددة.

إذا تم النشر بنجاح، من المفترض أن يظهر لك الناتج التالي:

c0f28e7d6de0024.png

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

9bfe48e3c8b597e5.png

10. أحداث Cloud Storage المتاحة للتشغيل السحابي من خلال نشر/اشتراك

الخدمة جاهزة، ولكن لا يزال عليك إنشاء أحداث Cloud Storage في خدمة Cloud Run التي تم إنشاؤها حديثًا. يمكن لخدمة Cloud Storage إرسال أحداث إنشاء الملفات من خلال Cloud Pub/Sub، ولكن هناك بعض الخطوات لتنفيذ هذا الإجراء.

أنشئ موضوع النشر/الاشتراك كمسار التواصل:

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

إنشاء إشعارات نشر/اشتراك عند تخزين الملفات في الحزمة:

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

أنشئ حساب خدمة لاشتراك النشر/الاشتراك الذي سننشئه لاحقًا:

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

امنح إذن حساب الخدمة لاستدعاء خدمة تشغيل في السحابة الإلكترونية:

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

في حال تفعيل حساب خدمة النشر/الاشتراك في 8 نيسان (أبريل) 2021 أو قبل ذلك، يمكنك منح الدور 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

قد يستغرق نشر تغييرات "إدارة الهوية وإمكانية الوصول" بضع دقائق.

أخيرًا، أنشئ اشتراك النشر/الاشتراك باستخدام حساب الخدمة:

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

يمكنك التحقُّق من إنشاء اشتراك. انتقِل إلى نشر/اشتراك في وحدة التحكّم، واختَر موضوع "gcs-events"، ومن المفترض أن يظهر لك الاشتراك في أسفل الصفحة:

e8ab86dccb8d890.png

11. اختبار الخدمة

لاختبار ما إذا كانت الإعدادات تعمل بشكل سليم، حمِّل صورة جديدة إلى حزمة uploaded-pictures وتأكَّد في الحزمة thumbnails من أنّ الصور الجديدة التي تم تغيير حجمها تظهر على النحو المتوقّع.

يمكنك أيضًا التحقق مرة أخرى من السجلات لمشاهدة رسائل التسجيل، حيث تمر الخطوات المختلفة لخدمة التشغيل في السحابة الإلكترونية:

42c025e2d7d6ca3a.png

12. إخلاء مساحة تخزين (اختياري)

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

حذف الحزمة:

gsutil rb gs://$BUCKET_THUMBNAILS

حذف الخدمة:

gcloud run services delete $SERVICE_NAME -q

حذف موضوع النشر/الاشتراك:

gcloud pubsub topics delete $TOPIC_NAME

بدلاً من ذلك، يمكنك حذف المشروع بالكامل:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

13. تهانينا!

وقد تم تنفيذ كل شيء بنجاح:

  • تم إنشاء إشعار في Cloud Storage يرسل رسائل النشر/الاشتراك حول موضوع ما، عند تحميل صورة جديدة.
  • تم تحديد عمليات ربط إدارة الهوية وإمكانية الوصول (IAM) والحسابات المطلوبة (على عكس دوال Cloud التي يتم فيها تشغيلها آليًا بالكامل، يتم ضبطها يدويًا هنا).
  • تم إنشاء اشتراك لكي تتلقّى خدمة Cloud Run رسائل Pub/Sub.
  • عند تحميل صورة جديدة إلى الحزمة، يتم تغيير حجم الصورة بفضل خدمة Cloud Run الجديدة.

المواضيع التي تناولناها

  • Cloud Run
  • تخزين في السحابة الإلكترونية
  • خدمة Cloud Pub/Sub

الخطوات التالية