1. Обзор
В первой лабораторной работе вы будете хранить изображения в хранилище данных. Это сгенерирует событие создания файла, которое будет обработано службой, развернутой в Cloud Run. Служба будет вызывать API Vision для анализа изображений и сохранения результатов в хранилище данных.

Что вы узнаете
- Облачное хранилище
- Cloud Run
- API Cloud Vision
- Облачный Firestore
2. Настройка и требования
Настройка среды для самостоятельного обучения
- Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .



- Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google. Вы всегда можете его изменить.
- Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор вашего проекта (обычно обозначается как
PROJECT_ID). Если сгенерированный идентификатор вас не устраивает, вы можете сгенерировать другой случайный идентификатор. В качестве альтернативы вы можете попробовать свой собственный и посмотреть, доступен ли он. После этого шага его нельзя изменить, и он сохраняется на протяжении всего проекта. - К вашему сведению, существует третье значение — номер проекта , которое используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
- Далее вам потребуется включить оплату в консоли Cloud для использования ресурсов/API Cloud. Выполнение этого практического задания не потребует больших затрат, если вообще потребует. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, вы можете удалить созданные ресурсы или удалить проект. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .
Запустить Cloud Shell
Хотя Google Cloud можно управлять удаленно с ноутбука, в этом практическом занятии вы будете использовать Google Cloud Shell — среду командной строки, работающую в облаке.
В консоли Google Cloud нажмите на значок Cloud Shell на панели инструментов в правом верхнем углу:

Подготовка и подключение к среде займут всего несколько минут. После завершения вы должны увидеть что-то подобное:

Эта виртуальная машина содержит все необходимые инструменты разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Вся работа в этом практическом задании может выполняться в браузере. Вам не нужно ничего устанавливать.
3. Включите API.
Для этой лабораторной работы вы будете использовать Cloud Functions и Vision API, но сначала их необходимо включить либо в Cloud Console, либо с помощью gcloud .
Чтобы включить Vision API в Cloud Console, введите Cloud Vision API в строку поиска:

Вы попадете на страницу API Cloud Vision:

Нажмите кнопку 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)
Для создания сегментов (buckets) в Cloud Shell также можно использовать инструмент командной строки gsutil .
В Cloud Shell задайте переменную для уникального имени корзины. В Cloud Shell уже установлена переменная GOOGLE_CLOUD_PROJECT , содержащая уникальный идентификатор вашего проекта. Вы можете добавить его к имени корзины.
Например:
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 из коллекции имен.

Вам не нужно создавать документ. Вы будете добавлять их программно по мере того, как новые изображения будут сохраняться в облачном хранилище и анализироваться с помощью Vision API.
Нажмите « Save ».
Firestore создает первый документ по умолчанию в только что созданной коллекции; вы можете смело удалить этот документ, поскольку он не содержит никакой полезной информации.

Документы, которые будут созданы программным способом в нашей коллекции, будут содержать 4 поля:
- имя (строка): имя файла загруженного изображения, которое также является ключом документа.
- метки (массив строк): метки распознанных элементов в Vision API.
- цвет (строка): шестнадцатеричный код доминирующего цвета (например, #ab12ef)
- дата создания : метка времени сохранения метаданных этого изображения.
- миниатюра (логическое значение): необязательное поле, которое будет присутствовать и иметь значение true, если для этого изображения было сгенерировано миниатюрное изображение.
Поскольку мы будем искать в 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. Изучите код сервиса.
Для начала нужно разобраться, как в файле pom.xml включаются клиентские библиотеки Java с помощью BOM:
Сначала откройте файл pom.xml , в котором перечислены зависимости нашего Java-приложения; основное внимание уделяется использованию API Vision, Cloud Storage и Firestore.
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-M3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>services</groupId>
<artifactId>image-analysis</artifactId>
<version>0.0.1</version>
<name>image-analysis</name>
<description>Spring App for Image Analysis</description>
<properties>
<java.version>17</java.version>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<spring-cloud.version>2023.0.0-M2</spring-cloud.version>
<testcontainers.version>1.19.1</testcontainers.version>
</properties>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.24.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
—
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.1.0</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>
Функциональность реализована в классе 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:
- Распознавание меток : чтобы понять, что изображено на этих картинках.
- Свойства изображения : для придания изображению интересных характеристик (нас интересует преобладающий цвет).
- Безопасный поиск : чтобы узнать, безопасно ли показывать изображение (оно не должно содержать материалы для взрослых / медицинского характера / откровенные / жестокие).
На этом этапе мы можем обратиться к API Vision:
...
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) {
ApiFuture<WriteResult> writeResult =
eventService.storeImage(fileName, labels,
mainColor);
logger.info("Picture metadata saved in Firestore at " +
writeResult.get().getUpdateTime());
}
...
public ApiFuture<WriteResult> storeImage(String fileName,
List<String> labels,
String mainColor) {
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());
return doc.set(data, SetOptions.merge());
}
10. Создание образов приложений с помощью GraalVM
На этом необязательном этапе вы создадите JIT based app image , а затем Native Java app image , используя GraalVM.
Для запуска сборки необходимо убедиться, что у вас установлены и настроены соответствующий JDK и native-image builder. Доступно несколько вариантов.
To start загрузите GraalVM 22.3.x Community Edition и следуйте инструкциям на странице установки GraalVM .
Этот процесс можно значительно упростить с помощью SDKMAN!
Для установки соответствующего дистрибутива JDK с помощью SDKman начните с выполнения команды установки:
sdk install java 17.0.8-graal
Укажите SDKman использовать эту версию как для JIT-, так и для AOT-сборок:
sdk use java 17.0.8-graal
В Cloudshell для вашего удобства вы можете установить GraalVM и утилиту native-image с помощью следующих простых команд:
# download GraalVM wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz ls -lart # configure Java 17 and GraalVM for Java 17 # note the name of the latest GraalVM version, as unpacked by the tar command echo Existing JVM: $JAVA_HOME cd graalvm-jdk-17.0.8+9.1 export JAVA_HOME=$PWD cd bin export PATH=$PWD:$PATH echo JAVA HOME: $JAVA_HOME echo PATH: $PATH cd ../.. # validate the version with java -version # observe Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14) Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)
Сначала установите переменные среды проекта GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Затем вы можете перейти в каталог, содержащий службу, чтобы начать создание тестовой среды:
cd serverless-photosharing-workshop/services/image-analysis/java
Создайте образ JIT-приложения:
./mvnw package
Просмотрите журнал сборки в терминале:
... [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis --- [INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar [INFO] [INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis --- [INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/. [INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.335 s [INFO] Finished at: 2023-10-10T19:33:25Z [INFO] ------------------------------------------------------------------------
Создайте нативный образ (использующий AOT):.
./mvnw native:compile -Pnative
Просмотрите журнал сборки в терминале, включая журналы сборки исходного образа:
Обратите внимание, что сборка занимает значительно больше времени в зависимости от используемой вами тестовой машины.
...
[2/7] Performing analysis... [*********] (124.5s @ 4.53GB)
29,732 (93.19%) of 31,905 classes reachable
60,161 (70.30%) of 85,577 fields reachable
261,973 (67.29%) of 389,319 methods reachable
2,940 classes, 2,297 fields, and 97,421 methods registered for reflection
81 classes, 90 fields, and 62 methods registered for JNI access
4 native libraries: dl, pthread, rt, z
[3/7] Building universe... (11.7s @ 4.67GB)
[4/7] Parsing methods... [***] (6.1s @ 5.91GB)
[5/7] Inlining methods... [****] (4.5s @ 4.39GB)
[6/7] Compiling methods... [******] (35.3s @ 4.60GB)
[7/7] Creating image... (12.9s @ 4.61GB)
80.08MB (47.43%) for code area: 190,483 compilation units
73.81MB (43.72%) for image heap: 660,125 objects and 189 resources
14.95MB ( 8.86%) for other data
168.84MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area: Top 10 object types in image heap:
2.66MB com.google.cloud.vision.v1p4beta1 18.51MB byte[] for code metadata
2.60MB com.google.cloud.vision.v1 9.27MB java.lang.Class
2.49MB com.google.protobuf 7.34MB byte[] for reflection metadata
2.40MB com.google.cloud.vision.v1p3beta1 6.35MB byte[] for java.lang.String
2.17MB com.google.storage.v2 5.72MB java.lang.String
2.12MB com.google.firestore.v1 4.46MB byte[] for embedded resources
1.64MB sun.security.ssl 4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor
1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3 4.27MB byte[] for general heap data
1.47MB com.google.cloud.vision.v1p2beta1 2.50MB com.oracle.svm.core.hub.DynamicHubCompanion
1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3 1.17MB java.lang.Object[]
58.34MB for 977 more packages 9.19MB for 4667 more object types
------------------------------------------------------------------------------------------------------------------------
13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 04:28 min
[INFO] Finished at: 2023-10-10T19:53:30Z
[INFO] ------------------------------------------------------------------------
11. Создание и публикация образов контейнеров
Давайте создадим образ контейнера в двух разных версиях: одна как JIT image , а другая как Native Java image .
Сначала установите переменные среды проекта GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Создайте образ JIT:.
./mvnw spring-boot:build-image -Pji
Просмотрите журнал сборки в терминале:
[INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z [INFO] [creator] *** Images (4c84122a1826): [INFO] [creator] docker.io/library/image-analysis-maven-jit:latest [INFO] [creator] Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z [INFO] [creator] Timer: Cache started at 2023-10-10T20:00:38Z [INFO] [creator] Reusing cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [creator] Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 43.887 s [INFO] Finished at: 2023-10-10T20:00:39Z [INFO] ------------------------------------------------------------------------
Создайте образ AOT (собственный):
./mvnw spring-boot:build-image -Pnative
Просмотрите журнал сборки в терминале, включая журналы сборки исходного образа.
Примечание:
- Сборка занимает значительно больше времени, в зависимости от используемой машины для тестирования.
- Изображения можно дополнительно сжать с помощью UPX, однако это немного негативно скажется на производительности при запуске, поэтому в этой сборке UPX не используется — это всегда небольшой компромисс.
... [INFO] [creator] Saving docker.io/library/image-analysis-maven-native:latest... [INFO] [creator] *** Images (13167702674e): [INFO] [creator] docker.io/library/image-analysis-maven-native:latest [INFO] [creator] Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm' [INFO] [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'paketo-buildpacks/native-image:native-image' [INFO] [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:37 min [INFO] Finished at: 2023-10-10T20:05:16Z [INFO] ------------------------------------------------------------------------
Убедитесь, что образы созданы:
docker images | grep image-analysis
Добавьте теги и загрузите оба изображения в GCR:
# JIT image
docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
# Native(AOT) image
docker tag image-analysis-maven-native gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
12. Развертывание в облаке
Пора развернуть сервис.
Вы развернете сервис дважды: первый раз, используя JIT-образ, а второй раз — AOT-образ (нативный). Оба развертывания сервиса будут обрабатывать один и тот же образ из хранилища параллельно для целей сравнения.
Сначала установите переменные среды проекта GCP:
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-образ и просмотрите журнал развертывания в консоли:
gcloud run deploy image-analysis-jit \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-jit] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-jit] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jit-...-ew.a.run.app
Разверните образ Native и просмотрите журнал развертывания в консоли:
gcloud run deploy image-analysis-native \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \
--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.
Сначала установите переменные среды проекта GCP:
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
Предоставьте учетной записи службы Cloud Storage права доступа pubsub.publisher :
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 для обработки образов как JIT-компилятора, так и нативных сервисов:
gcloud eventarc triggers list --location=eu
gcloud eventarc triggers create image-analysis-jit-trigger \
--destination-run-service=image-analysis-jit \
--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-jit , а затем image-analysis-native .
В меню «гамбургер» (☰) перейдите в раздел Cloud Run > image-analysis-jit service.
Нажмите на «Журналы» и просмотрите вывод:

И действительно, в списке логов я вижу, что был вызван JIT-сервис image-analysis-jit .
В логах отображаются начало и конец выполнения сервиса. А между этими событиями мы можем увидеть логи, которые мы добавили в нашу функцию с помощью операторов логирования на уровне INFO. Мы видим:
- Подробности события, запустившего нашу функцию:
- Исходные результаты вызова API Vision.
- Этикетки, которые были обнаружены на загруженном нами изображении,
- Информация о доминирующих цветах.
- Можно ли показывать это изображение?
- В конечном итоге эти метаданные об изображении были сохранены в Firestore.
Вам потребуется повторить этот процесс для image-analysis-native .
В меню «гамбургер» (☰) перейдите в раздел Cloud Run > image-analysis-native service.
Нажмите на «Журналы» и просмотрите вывод:

Теперь вам следует проверить, сохранены ли метаданные изображения в Fiorestore.
Снова из меню-гамбургера (☰) перейдите в раздел Firestore . В подразделе Data » (отображается по умолчанию) вы должны увидеть коллекцию pictures с добавленным новым документом, соответствующим только что загруженному изображению:

15. Уборка (необязательно)
Если вы не планируете продолжать выполнение остальных лабораторных работ из этой серии, вы можете освободить ресурсы, чтобы сэкономить средства и в целом ответственно относиться к облачным технологиям. Освободить ресурсы можно по отдельности следующим образом.
Удалите корзину:
gsutil rb gs://${BUCKET_PICTURES}
Удалите функцию:
gcloud functions delete picture-uploaded --region europe-west1 -q
Удалите коллекцию Firestore, выбрав пункт «Удалить коллекцию» в списке коллекций:

В качестве альтернативы вы можете удалить весь проект:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Поздравляем!
Поздравляем! Вы успешно внедрили первый ключевой сервис проекта!
Что мы рассмотрели
- Облачное хранилище
- Cloud Run
- API Cloud Vision
- Облачный Firestore
- Исходные изображения Java