1. نظرة عامة
في تجربة الترميز الأولى، ستخزّن الصور في حزمة. سيؤدي ذلك إلى إنشاء حدث إنشاء ملف ستتعامل معه خدمة تم نشرها في Cloud Run. ستُجري الخدمة طلبًا إلى Vision API لإجراء تحليل الصور وحفظ النتائج في مخزن البيانات.

أهداف الدورة التعليمية
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
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 Console، انقر على رمز Cloud Shell في شريط الأدوات العلوي على يسار الصفحة:

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

يتم تحميل هذه الآلة الافتراضية مزوّدة بكل أدوات التطوير التي ستحتاج إليها. توفّر هذه الخدمة دليلًا منزليًا ثابتًا بسعة 5 غيغابايت، وتعمل على Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. يمكن إكمال جميع المهام في هذا الدرس العملي ضمن المتصفّح. لست بحاجة إلى تثبيت أي تطبيق.
3- تفعيل واجهات برمجة التطبيقات
في هذا المختبر، ستستخدم Cloud Functions وVision API، ولكن يجب أولاً تفعيلهما إما في Cloud Console أو باستخدام gcloud.
لتفعيل Vision API في Cloud Console، ابحث عن Cloud Vision API في شريط البحث:

ستنتقل إلى صفحة Cloud Vision API:

انقر على الزر ENABLE.
يمكنك أيضًا تفعيلها في Cloud Shell باستخدام أداة سطر أوامر gcloud.
داخل Cloud Shell، شغِّل الأمر التالي:
gcloud services enable vision.googleapis.com
من المفترض أن تظهر لك العملية التي تمّت بنجاح:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
فعِّل Cloud Run وCloud Build أيضًا:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. إنشاء الحزمة (وحدة التحكّم)
أنشئ حزمة تخزين للصور. يمكنك إجراء ذلك من وحدة تحكّم Google Cloud Platform ( console.cloud.google.com) أو باستخدام أداة سطر الأوامر gsutil من Cloud Shell أو بيئة التطوير المحلية.
الانتقال إلى "مساحة التخزين"
من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى صفحة Storage.

تسمية الحزمة
انقر على الزر CREATE BUCKET.

انقر على CONTINUE.
اختيار الموقع الجغرافي

أنشئ حزمة متعددة المناطق في المنطقة التي تختارها (Europe هنا).
انقر على CONTINUE.
اختيار فئة التخزين التلقائية

اختَر Standard فئة التخزين لبياناتك.
انقر على CONTINUE.
ضبط عناصر التحكّم في إمكانية الوصول

بما أنّك ستعمل على صور متاحة للجميع، عليك التأكّد من أنّ جميع الصور المخزّنة في هذا الحزمة تتضمّن عناصر التحكّم نفسها في الوصول الموحّد.
اختَر خيار التحكّم في الوصول Uniform.
انقر على CONTINUE.
ضبط الحماية/التشفير

اترك الخيار التلقائي (Google-managed key))، لأنّك لن تستخدم مفاتيح التشفير الخاصة بك.
انقر على CREATE لإنهاء عملية إنشاء الحزمة.
إضافة allUsers كمشاهد لمساحة التخزين
انتقِل إلى علامة التبويب Permissions:

أضِف عضوًا allUsers إلى الحزمة، مع دور Storage > Storage Object Viewer، على النحو التالي:

انقر على SAVE.
5- إنشاء الحزمة (gsutil)
يمكنك أيضًا استخدام أداة سطر الأوامر gsutil في Cloud Shell لإنشاء حِزم.
في Cloud Shell، اضبط متغيّرًا لاسم الحزمة الفريد. يتم ضبط GOOGLE_CLOUD_PROJECT تلقائيًا على معرّف مشروعك الفريد في Cloud Shell. يمكنك إلحاق ذلك باسم الحزمة.
على سبيل المثال:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
أنشئ منطقة عادية متعددة المناطق في أوروبا:
gsutil mb -l EU gs://${BUCKET_PICTURES}
ضمان الوصول الموحّد على مستوى الحزمة:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
اجعل الحزمة علنية:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
إذا انتقلت إلى القسم Cloud Storage في وحدة التحكّم، من المفترض أن يكون لديك حزمة uploaded-pictures عامة:

اختبِر إمكانية تحميل الصور إلى الحزمة وتأكَّد من أنّ الصور التي تم تحميلها متاحة للجميع، كما هو موضّح في الخطوة السابقة.
6. اختبار إمكانية الوصول العلني إلى الحزمة
بالرجوع إلى متصفّح مساحة التخزين، سترى الحزمة في القائمة، مع إذن الوصول "متاح للجميع" (بما في ذلك علامة تحذير تذكّرك بأنّ بإمكان أي مستخدم الوصول إلى محتوى هذه الحزمة).

أصبح الآن بإمكانك تلقّي الصور في الحزمة.
إذا نقرت على اسم المجموعة، ستظهر لك تفاصيلها.

يمكنك تجربة الزر Upload files للتأكّد من إمكانية إضافة صورة إلى الحزمة. سيطلب منك نافذة منبثقة لاختيار الملف تحديد ملف. بعد اختيار الملف، سيتم تحميله إلى الحزمة، وستظهر لك مرة أخرى إذن الوصول public الذي تم منحه تلقائيًا لهذا الملف الجديد.

بجانب تصنيف الوصول Public، سيظهر لك أيضًا رمز رابط صغير. عند النقر عليه، سينتقِل المتصفّح إلى عنوان URL العلني لتلك الصورة، والذي سيكون بالتنسيق التالي:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
حيث يمثّل BUCKET_NAME الاسم الفريد الذي اخترته للحزمة، ثم اسم ملف الصورة.
من خلال النقر على مربّع الاختيار بجانب اسم الصورة، سيتم تفعيل الزر DELETE، ويمكنك حذف هذه الصورة الأولى.
7. إعداد قاعدة البيانات
ستخزِّن معلومات حول الصورة التي توفّرها Vision API في قاعدة بيانات Cloud Firestore، وهي قاعدة بيانات مستنِدة إلى تنسيق NoSQL، وتتضمّن مستندات، وتتم إدارتها بالكامل، وتعمل بدون خادم، وتتسم بالسرعة، وتستند إلى السحابة الإلكترونية. جهِّز قاعدة البيانات من خلال الانتقال إلى القسم Firestore في Cloud Console:

يتوفّر خياران: Native mode أو Datastore mode. استخدِم الوضع الأصلي الذي يوفّر ميزات إضافية، مثل إمكانية الاستخدام بلا إنترنت والمزامنة في الوقت الفعلي.
انقر على SELECT NATIVE MODE.

اختَر منطقة متعدّدة (في أوروبا هنا، ولكن من الأفضل أن تكون المنطقة نفسها على الأقل التي تتضمّن الدالة وحزمة التخزين).
انقر على الزر CREATE DATABASE.
بعد إنشاء قاعدة البيانات، من المفترض أن يظهر لك ما يلي:

أنشئ مجموعة جديدة بالنقر على الزر + START COLLECTION.
اسم المجموعة pictures.

لست بحاجة إلى إنشاء مستند. ستضيفها آليًا عند تخزين صور جديدة في Cloud Storage وتحليلها باستخدام Vision API.
انقر على Save.
تنشئ Firestore مستندًا تلقائيًا أولاً في المجموعة التي تم إنشاؤها حديثًا، ويمكنك حذف هذا المستند بأمان لأنّه لا يحتوي على أي معلومات مفيدة:

ستحتوي المستندات التي سيتم إنشاؤها آليًا في مجموعتنا على 4 حقول:
- name (سلسلة): اسم ملف الصورة التي تم تحميلها، وهو أيضًا مفتاح المستند
- labels (مصفوفة من السلاسل): تصنيفات العناصر التي تتعرّف عليها Vision API
- color (سلسلة): رمز اللون السداسي العشري للون السائد (مثلاً #ab12ef)
- created (التاريخ): الطابع الزمني لوقت تخزين البيانات الوصفية لهذه الصورة
- thumbnail (قيمة منطقية): حقل اختياري سيظهر وتكون قيمته صحيحة إذا تم إنشاء صورة مصغّرة لهذه الصورة
بما أنّنا سنبحث في Firestore للعثور على صور تتضمّن صورًا مصغّرة متاحة، وسنرتبها حسب تاريخ الإنشاء، علينا إنشاء فهرس بحث.
يمكنك إنشاء الفهرس باستخدام الأمر التالي في Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
يمكنك أيضًا إجراء ذلك من Cloud Console، من خلال النقر على Indexes في عمود التنقّل على اليمين، ثم إنشاء فهرس مركّب كما هو موضّح أدناه:

انقر على Create. قد يستغرق إنشاء الفهرس بضع دقائق.
8. استنساخ الرمز
استنسِخ الرمز البرمجي، إذا لم يسبق لك ذلك في تجربة الترميز السابقة:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
يمكنك بعد ذلك الانتقال إلى الدليل الذي يحتوي على الخدمة لبدء إنشاء المختبر:
cd serverless-photosharing-workshop/services/image-analysis/java
سيكون لديك تنسيق الملف التالي للخدمة:

9- استكشاف رمز الخدمة
يمكنك البدء بالاطّلاع على كيفية تفعيل مكتبات عملاء Java في pom.xml باستخدام قائمة المواد (BOM):
أولاً، عدِّل ملف pom.xml الذي يسرد التبعيات الخاصة بدالة Java. عدِّل الرمز البرمجي لإضافة اعتمادية Cloud Vision API Maven:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfunctions</groupId>
<artifactId>gcs-function</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
</dependency>
</dependencies>
يتم تنفيذ الوظيفة في فئة EventController. في كل مرة يتم فيها تحميل صورة جديدة إلى الحزمة، ستتلقّى الخدمة إشعارًا لإجراء ما يلي:
@RestController
public class EventController {
private static final Logger logger = Logger.getLogger(EventController.class.getName());
private static final List<String> requiredFields = Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");
@RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<String> receiveMessage(
@RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) throws IOException, InterruptedException, ExecutionException {
...
}
سيتم التحقّق من صحة عناوين Cloud Events على النحو التالي:
System.out.println("Header elements");
for (String field : requiredFields) {
if (headers.get(field) == null) {
String msg = String.format("Missing expected header: %s.", field);
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
} else {
System.out.println(field + " : " + headers.get(field));
}
}
System.out.println("Body elements");
for (String bodyField : body.keySet()) {
System.out.println(bodyField + " : " + body.get(bodyField));
}
if (headers.get("ce-subject") == null) {
String msg = "Missing expected header: ce-subject.";
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
يمكن الآن إنشاء طلب وسيعدّ الرمز طلبًا واحدًا من هذا النوع لإرساله إلى Vision API:
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
requests.add(request);
نطلب توفير 3 إمكانات رئيسية في Vision API:
- التعرّف على التصنيفات: لفهم محتوى تلك الصور
- خصائص الصورة: لتقديم سمات مثيرة للاهتمام حول الصورة (نحن مهتمون باللون السائد في الصورة)
- البحث الآمن: لمعرفة ما إذا كانت الصورة آمنة للعرض (يجب ألا تحتوي على محتوى للبالغين أو محتوى طبي أو محتوى فاضح أو محتوى يتضمن عنفًا)
في هذه المرحلة، يمكننا إجراء طلب إلى Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
للعلم، إليك شكل الردّ من Vision API:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
إذا لم يتم عرض أي خطأ، يمكننا المتابعة، وهذا هو سبب توفّر كتلة if هذه:
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
سنحصل على تصنيفات الأشياء أو الفئات أو المواضيع التي تم التعرّف عليها في الصورة:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
يهمّنا معرفة اللون السائد في الصورة:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
لنتحقّق مما إذا كانت الصورة آمنة للعرض:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
نتحقّق من خصائص المحتوى المخصّص للبالغين أو المحتوى الزائف أو الطبي أو العنيف أو الجنسي لمعرفة ما إذا كان من المحتمل أو من المحتمل جدًا أن يكون كذلك.
إذا كانت نتيجة البحث الآمن مقبولة، يمكننا تخزين البيانات الوصفية في Firestore:
// Saving result to Firestore
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
10. إنشاء صور التطبيقات باستخدام GraalVM (اختياري)
في هذه الخطوة الاختيارية، ستنشئ JIT(JVM) based app image، ثم AOT(Native) Java app image، باستخدام GraalVM.
لتشغيل الإصدار، عليك التأكّد من توفّر حزمة تطوير Java المناسبة وتثبيت أداة إنشاء native-image وإعدادها. تتوفّر عدة خيارات.
To start، نزِّل إصدار GraalVM 22.2.x Community Edition واتّبِع التعليمات الواردة في صفحة تثبيت GraalVM.
يمكن تبسيط هذه العملية إلى حدّ كبير بمساعدة SDKMAN!
لتثبيت توزيعة JDK المناسبة باستخدام SDKman، ابدأ باستخدام أمر التثبيت:
sdk install java 22.2.r17-grl
أدخِل الأمر التالي في SDKman لاستخدام هذا الإصدار لكل من عمليات الإنشاء JIT وAOT:
sdk use java 22.2.0.r17-grl
ثبِّت native-image utility لـ GraalVM:
gu install native-image
في Cloudshell، يمكنك تثبيت GraalVM وأداة native-image باستخدام الأوامر البسيطة التالية:
# install GraalVM in your home directory cd ~ # download GraalVM wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.2.0/graalvm-ce-java17-linux-amd64-22.2.0.tar.gz ls tar -xzvf graalvm-ce-java17-linux-amd64-22.2.0.tar.gz # configure Java 17 and GraalVM 22.2 echo Existing JVM: $JAVA_HOME cd graalvm-ce-java17-22.2.0 export JAVA_HOME=$PWD cd bin export PATH=$PWD:$PATH echo JAVA HOME: $JAVA_HOME echo PATH: $PATH # install the native image utility java -version gu install native-image cd ../..
أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
يمكنك بعد ذلك الانتقال إلى الدليل الذي يحتوي على الخدمة لبدء إنشاء المختبر:
cd serverless-photosharing-workshop/services/image-analysis/java
أنشئ صورة تطبيق "التجميع أثناء التنفيذ" (JVM):
./mvnw package -Pjvm
اطّلِع على سجلّ الإنشاء في الوحدة الطرفية:
... [INFO] --- spring-boot-maven-plugin:2.7.3:repackage (repackage) @ image-analysis --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 24.009 s [INFO] Finished at: 2022-09-26T22:17:32-04:00 [INFO] ------------------------------------------------------------------------
أنشئ صورة AOT(أصلية):.
./mvnw package -Pnative -DskipTests
اطّلِع على سجلّ الإنشاء في الوحدة الطرفية، بما في ذلك سجلّات إنشاء الصور الأصلية:
يُرجى العِلم أنّ عملية الإنشاء تستغرق وقتًا أطول بكثير، وذلك حسب الجهاز الذي تجري الاختبار عليه.
...
[2/7] Performing analysis... [**********] (95.4s @ 3.57GB)
23,346 (94.42%) of 24,725 classes reachable
44,625 (68.71%) of 64,945 fields reachable
163,759 (70.79%) of 231,322 methods reachable
989 classes, 1,402 fields, and 11,032 methods registered for reflection
63 classes, 69 fields, and 55 methods registered for JNI access
5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
[3/7] Building universe... (10.0s @ 5.35GB)
[4/7] Parsing methods... [***] (9.7s @ 3.13GB)
[5/7] Inlining methods... [***] (4.5s @ 3.29GB)
[6/7] Compiling methods... [[6/7] Compiling methods... [********] (67.6s @ 5.72GB)
[7/7] Creating image... (8.7s @ 4.59GB)
62.21MB (54.80%) for code area: 100,371 compilation units
50.98MB (44.91%) for image heap: 465,035 objects and 365 resources
337.09KB ( 0.29%) for other data
113.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area: Top 10 object types in image heap:
2.36MB com.google.protobuf 12.70MB byte[] for code metadata
1.90MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3 6.66MB java.lang.Class
1.73MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3 6.47MB byte[] for embedded resources
1.67MB sun.security.ssl 4.61MB byte[] for java.lang.String
1.54MB com.google.cloud.vision.v1 4.37MB java.lang.String
1.46MB com.google.firestore.v1 3.38MB byte[] for general heap data
1.37MB io.grpc.xds.shaded.io.envoyproxy.envoy.api.v2.core 1.96MB com.oracle.svm.core.hub.DynamicHubCompanion
1.32MB i.g.xds.shaded.io.envoyproxy.envoy.api.v2.route 1.80MB byte[] for reflection metadata
1.09MB java.util 911.80KB java.lang.String[]
1.08MB com.google.re2j 826.48KB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
45.91MB for 772 more packages 6.45MB for 3913 more object types
------------------------------------------------------------------------------------------------------------------------
15.1s (6.8% of total time) in 56 GCs | Peak RSS: 7.72GB | CPU load: 4.37
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
/Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
/Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'image-analysis' in 3m 41s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:56 min
[INFO] Finished at: 2022-09-26T22:22:29-04:00
[INFO] ------------------------------------------------------------------------
11. إنشاء صور الحاويات ونشرها
لننشئ صورة حاوية بنسختَين مختلفتَين: إحداهما JIT(JVM) image والأخرى AOT(Native) Java image.
أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
أنشئ صورة JIT(JVM):.
./mvnw package -Pjvm-image
اطّلِع على سجلّ الإنشاء في الوحدة الطرفية:
[INFO] [creator] Adding layer 'process-types' [INFO] [creator] Adding label 'io.buildpacks.lifecycle.metadata' [INFO] [creator] Adding label 'io.buildpacks.build.metadata' [INFO] [creator] Adding label 'io.buildpacks.project.metadata' [INFO] [creator] Adding label 'org.opencontainers.image.title' [INFO] [creator] Adding label 'org.opencontainers.image.version' [INFO] [creator] Adding label 'org.springframework.boot.version' [INFO] [creator] Setting default process type 'web' [INFO] [creator] Saving docker.io/library/image-analysis-jvm:r17... [INFO] [creator] *** Images (03a44112456e): [INFO] [creator] docker.io/library/image-analysis-jvm:r17 [INFO] [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-jvm:r17' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 02:11 min [INFO] Finished at: 2022-09-26T13:09:34-04:00 [INFO] ------------------------------------------------------------------------
أنشئ صورة AOT(أصلية):.
./mvnw package -Pnative-image
اطّلِع على سجل الإنشاء في نافذة الأوامر، بما في ذلك سجلات إنشاء الصور الأصلية وضغط الصور باستخدام UPX.
يُرجى العِلم أنّ عملية الإنشاء تستغرق وقتًا أطول بكثير، وذلك حسب الجهاز الذي تجري الاختبار عليه.
... [INFO] [creator] [2/7] Performing analysis... [***********] (147.6s @ 3.10GB) [INFO] [creator] 23,362 (94.34%) of 24,763 classes reachable [INFO] [creator] 44,657 (68.67%) of 65,029 fields reachable [INFO] [creator] 163,926 (70.76%) of 231,656 methods reachable [INFO] [creator] 981 classes, 1,402 fields, and 11,026 methods registered for reflection [INFO] [creator] 63 classes, 68 fields, and 55 methods registered for JNI access [INFO] [creator] 4 native libraries: dl, pthread, rt, z [INFO] [creator] [3/7] Building universe... (21.1s @ 2.66GB) [INFO] [creator] [4/7] Parsing methods... [****] (13.7s @ 4.16GB) [INFO] [creator] [5/7] Inlining methods... [***] (9.6s @ 4.20GB) [INFO] [creator] [6/7] Compiling methods... [**********] (107.6s @ 3.36GB) [INFO] [creator] [7/7] Creating image... (14.7s @ 4.87GB) [INFO] [creator] 62.24MB (51.35%) for code area: 100,499 compilation units [INFO] [creator] 51.99MB (42.89%) for image heap: 473,948 objects and 473 resources [INFO] [creator] 6.98MB ( 5.76%) for other data [INFO] [creator] 121.21MB in total [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] Top 10 packages in code area: Top 10 object types in image heap: [INFO] [creator] 2.36MB com.google.protobuf 12.71MB byte[] for code metadata [INFO] [creator] 1.90MB i.g.x.s.i.e.e.config.core.v3 7.59MB byte[] for embedded resources [INFO] [creator] 1.73MB i.g.x.s.i.e.e.config.route.v3 6.66MB java.lang.Class [INFO] [creator] 1.67MB sun.security.ssl 4.62MB byte[] for java.lang.String [INFO] [creator] 1.54MB com.google.cloud.vision.v1 4.39MB java.lang.String [INFO] [creator] 1.46MB com.google.firestore.v1 3.66MB byte[] for general heap data [INFO] [creator] 1.37MB i.g.x.s.i.e.envoy.api.v2.core 1.96MB c.o.s.c.h.DynamicHubCompanion [INFO] [creator] 1.32MB i.g.x.s.i.e.e.api.v2.route 1.80MB byte[] for reflection metadata [INFO] [creator] 1.09MB java.util 910.41KB java.lang.String[] [INFO] [creator] 1.08MB com.google.re2j 826.95KB c.o.s.c.h.DynamicHu~onMetadata [INFO] [creator] 45.94MB for 776 more packages 6.69MB for 3916 more object types [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] 20.4s (5.6% of total time) in 81 GCs | Peak RSS: 6.75GB | CPU load: 4.53 [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] Produced artifacts: [INFO] [creator] /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication (executable) [INFO] [creator] /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication.build_artifacts.txt (txt) [INFO] [creator] ================================================================================ [INFO] [creator] Finished generating '/layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication' in 5m 59s. [INFO] [creator] Executing upx to compress native image [INFO] [creator] Ultimate Packer for eXecutables [INFO] [creator] Copyright (C) 1996 - 2020 [INFO] [creator] UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 [INFO] [creator] [INFO] [creator] File size Ratio Format Name [INFO] [creator] -------------------- ------ ----------- ----------- 127099880 -> 32416676 25.50% linux/amd64 services.ImageAnalysisApplication ... [INFO] [creator] ===> EXPORTING ... [INFO] [creator] Adding cache layer 'paketo-buildpacks/native-image:native-image' [INFO] [creator] Adding cache layer 'cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-native:r17' ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 05:28 min [INFO] Finished at: 2022-09-26T13:19:53-04:00 [INFO] ------------------------------------------------------------------------
تأكَّد من إنشاء الصور:
docker images | grep image-analysis
وضع علامة على الصورتَين وإرسالهما إلى "آراء العملاء عبر Google":
# JIT(JVM) image
docker tag image-analysis-jvm:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
# AOT(Native) image
docker tag image-analysis-native:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
12. النشر على Cloud Run
حان وقت تفعيل الخدمة.
ستنفّذ الخدمة مرّتين، مرة باستخدام صورة JIT(JVM) ومرة ثانية باستخدام صورة AOT(Native). ستعالج كلتا عمليتَي نشر الخدمة الصورة نفسها من الحزمة بشكلٍ متوازٍ، وذلك لأغراض المقارنة.
أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1
انشر صورة JIT(JVM) واطّلِع على سجلّ النشر في وحدة التحكّم:
gcloud run deploy image-analysis-jvm \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17 \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-jvm] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-jvm] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jvm-...-ew.a.run.app
انشر صورة AOT(Native) واطّلِع على سجل النشر في وحدة التحكّم:
gcloud run deploy image-analysis-native \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app
13. إعداد مشغّلات Eventarc
تقدّم Eventarc حلاً موحّدًا لإدارة تدفّق تغييرات الحالة، المعروفة باسم الأحداث، بين الخدمات المصغّرة المنفصلة. عندما يتم تشغيلها، توجّه Eventarc هذه الأحداث من خلال اشتراكات Pub/Sub إلى وجهات مختلفة (في هذا المستند، اطّلِع على وجهات الأحداث) مع إدارة التسليم والأمان والتفويض وإمكانية تتبّع البيانات ومعالجة الأخطاء نيابةً عنك.
يمكنك إنشاء مشغّل Eventarc لكي تتلقّى خدمة Cloud Run إشعارات بحدث محدّد أو مجموعة أحداث. من خلال تحديد فلاتر للمشغّل، يمكنك ضبط توجيه الحدث، بما في ذلك مصدر الحدث وخدمة Cloud Run المستهدَفة.
أولاً، اضبط متغيّرات بيئة مشروع Google Cloud Platform:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1
امنح pubsub.publisher لحساب خدمة Cloud Storage:
SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})"
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role='roles/pubsub.publisher'
اضبط مشغّلات Eventarc لكل من صور خدمة JVM(JIT) وAOT(Native) لمعالجة الصورة:
gcloud eventarc triggers list --location=eu
gcloud eventarc triggers create image-analysis-jvm-trigger \
--destination-run-service=image-analysis-jvm \
--destination-run-region=europe-west1 \
--location=eu \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
--service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
gcloud eventarc triggers create image-analysis-native-trigger \
--destination-run-service=image-analysis-native \
--destination-run-region=europe-west1 \
--location=eu \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
--service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
لاحظ أنّه تم إنشاء المشغّلين التاليين:
gcloud eventarc triggers list --location=eu
14. إصدارات الخدمة التجريبية
بعد اكتمال عمليات نشر الخدمات بنجاح، ستنشر صورة في Cloud Storage، وستتحقّق مما إذا تم استدعاء خدماتنا، وما الذي تعرضه Vision API، وما إذا كانت البيانات الوصفية مخزّنة في Firestore.
ارجع إلى Cloud Storage، وانقر على الحزمة التي أنشأناها في بداية الدرس التطبيقي:

بعد الانتقال إلى صفحة تفاصيل الحزمة، انقر على الزر Upload files لتحميل صورة.
على سبيل المثال، يتم توفير صورة GeekHour.jpeg مع قاعدة الرموز البرمجية ضمن /services/image-analysis/java. اختَر صورة واضغط على Open button:

يمكنك الآن التحقّق من تنفيذ الخدمة، بدءًا من image-analysis-jvm، ثم image-analysis-native.
من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى خدمة Cloud Run > image-analysis-jvm.
انقر على "السجلات" (Logs) ولاحظ الناتج:

في قائمة السجلات، يمكنني أن أرى أنّه تم استدعاء خدمة JIT(JVM) image-analysis-jvm.
تشير السجلات إلى بداية تنفيذ الخدمة ونهايته. وفي ما بينهما، يمكننا الاطّلاع على السجلات التي وضعناها في الدالة باستخدام عبارات السجل بمستوى INFO. نلاحظ ما يلي:
- تفاصيل الحدث الذي يؤدي إلى تشغيل الدالة
- النتائج الأولية من طلب بيانات من واجهة برمجة التطبيقات Vision API
- التصنيفات التي تم العثور عليها في الصورة التي حمّلناها
- معلومات الألوان السائدة
- تحدّد هذه السمة ما إذا كانت الصورة آمنة للعرض.
- وفي النهاية، تم تخزين البيانات الوصفية الخاصة بالصورة في Firestore.
ستكرّر العملية لخدمة image-analysis-native.
من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى خدمة Cloud Run > image-analysis-native.
انقر على "السجلات" (Logs) ولاحظ الناتج:

عليك الآن مراقبة ما إذا كانت البيانات الوصفية للصورة قد تم تخزينها في Fiorestore.
مرّة أخرى من قائمة "الخطوط الثلاثة" (☰)، انتقِل إلى قسم Firestore. في القسم الفرعي Data (يظهر تلقائيًا)، من المفترض أن تظهر لك المجموعة pictures مع إضافة مستند جديد، بما يتوافق مع الصورة التي حمّلتها للتو:

15. التنظيف (اختياري)
إذا كنت لا تنوي مواصلة استخدام المختبرات الأخرى في السلسلة، يمكنك تنظيف الموارد لتوفير التكاليف ولتكون مواطنًا جيدًا للخدمات السحابية بشكل عام. يمكنك تنظيف الموارد بشكل فردي على النحو التالي.
حذف الحزمة:
gsutil rb gs://${BUCKET_PICTURES}
احذف الدالة:
gcloud functions delete picture-uploaded --region europe-west1 -q
احذف مجموعة Firestore من خلال اختيار "حذف المجموعة" (Delete collection) من المجموعة:

بدلاً من ذلك، يمكنك حذف المشروع بأكمله باتّباع الخطوات التالية:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. تهانينا!
تهانينا! لقد نفّذت خدمة إدارة مفاتيح التشفير الأولى للمشروع بنجاح.
المواضيع التي تناولناها
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
- صور Java الأصلية