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-функции. Обновите код, добавив зависимость 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:
- Распознавание меток : чтобы понять, что изображено на этих картинках.
- Свойства изображения : для придания изображению интересных характеристик (нас интересует преобладающий цвет).
- Безопасный поиск : чтобы узнать, безопасно ли показывать изображение (оно не должно содержать материалы для взрослых / медицинского характера / откровенные / жестокие).
На этом этапе мы можем обратиться к 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) {
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.
Для запуска сборки необходимо убедиться, что у вас установлены и настроены соответствующий JDK и native-image builder. Доступно несколько вариантов.
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 ../..
Сначала установите переменные среды проекта GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Затем вы можете перейти в каталог, содержащий службу, чтобы начать создание тестовой среды:
cd serverless-photosharing-workshop/services/image-analysis/java
Создайте образ JIT-компилятора (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 .
Сначала установите переменные среды проекта GCP:
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
Добавьте теги и загрузите оба изображения в GCR:
# 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. Развертывание в облаке
Пора развернуть сервис.
Вы развернете сервис дважды: первый раз, используя образ JIT (JVM), а второй раз — образ 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 (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.
Сначала установите переменные среды проекта 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 для образов служб 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 service.
Нажмите на «Журналы» и просмотрите вывод:

И действительно, в списке логов я вижу, что был вызван JIT-сервис (JVM) image-analysis-jvm .
В логах отображаются начало и конец выполнения сервиса. А между этими событиями мы можем увидеть логи, которые мы добавили в нашу функцию с помощью операторов логирования на уровне 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