1. Обзор
В первой лаборатории кода вы будете хранить изображения в корзине. Это создаст событие создания файла, которое будет обрабатываться службой, развернутой в Cloud Run. Служба выполнит вызов Vision API для анализа изображения и сохранения результатов в хранилище данных.
Что вы узнаете
- Облачное хранилище
- Облачный бег
- API облачного видения
- Облачный пожарный магазин
2. Настройка и требования
Самостоятельная настройка среды
- Войдите в Google Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .
- Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google. Вы всегда можете обновить его.
- Идентификатор проекта уникален для всех проектов Google Cloud и является неизменяемым (невозможно изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно идентифицируемый как
PROJECT_ID
). Если вам не нравится сгенерированный идентификатор, вы можете создать другой случайный идентификатор. Альтернативно, вы можете попробовать свой собственный и посмотреть, доступен ли он. Его нельзя изменить после этого шага и он сохраняется на протяжении всего проекта. - К вашему сведению, есть третье значение — номер проекта , которое используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
- Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не будет стоить много, если вообще что-то стоить. Чтобы отключить ресурсы и избежать выставления счетов за пределами этого руководства, вы можете удалить созданные вами ресурсы или удалить проект. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .
Запустить Cloud Shell
Хотя Google Cloud можно управлять удаленно с вашего ноутбука, в этой лаборатории вы будете использовать Google Cloud Shell , среду командной строки, работающую в облаке.
В Google Cloud Console щелкните значок Cloud Shell на верхней правой панели инструментов:
Подготовка и подключение к среде займет всего несколько минут. Когда все будет готово, вы должны увидеть что-то вроде этого:
Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Всю работу в этой лаборатории кода можно выполнять в браузере. Вам не нужно ничего устанавливать.
3. Включите API
В этом практическом занятии вы будете использовать 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 задайте переменную для уникального имени сегмента. В 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
облачной консоли:
Предлагаются два варианта: Native mode
или Datastore mode
. Используйте собственный режим, который предлагает дополнительные функции, такие как автономная поддержка и синхронизация в реальном времени.
Нажмите SELECT NATIVE MODE
.
Выберите мультирегион (здесь, в Европе, но в идеале по крайней мере тот же регион, в котором находятся ваша функция и сегмент хранилища).
Нажмите кнопку CREATE DATABASE
.
После создания базы данных вы должны увидеть следующее:
Создайте новую коллекцию , нажав кнопку + START COLLECTION
.
Назовите pictures
из коллекции.
Вам не нужно создавать документ. Вы будете добавлять их программно по мере того, как новые изображения сохраняются в облачном хранилище и анализируются Vision API.
Нажмите Save
.
Firestore создает первый документ по умолчанию во вновь созданной коллекции. Вы можете безопасно удалить этот документ, поскольку он не содержит никакой полезной информации:
Документы, которые будут созданы программно в нашей коллекции, будут содержать 4 поля:
- имя (строка): имя файла загруженного изображения, которое также является ключом документа.
- labels (массив строк): метки элементов, распознанных 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
Или вы также можете сделать это из облачной консоли, нажав Indexes
в столбце навигации слева, а затем создав составной индекс, как показано ниже:
Нажмите Create
. Создание индекса может занять несколько минут.
8. Клонируйте код
Клонируйте код, если вы еще этого не сделали в предыдущей лаборатории кода:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Затем вы можете перейти в каталог, содержащий сервис, чтобы начать создание лаборатории:
cd serverless-photosharing-workshop/services/image-analysis/java
У вас будет следующий макет файла для сервиса:
9. Изучите сервисный код
Вы начинаете с рассмотрения того, как клиентские библиотеки Java включаются в pom.xml
с помощью спецификации:
Сначала откройте файл 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);
Мы просим предоставить три ключевые возможности 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) {
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 и сборщик собственных образов. Доступно несколько вариантов.
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 и утилиту собственного образа с помощью этих простых команд:
# 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] ------------------------------------------------------------------------
Создайте образ Native (использует 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. Развертывание в Cloud Run
Пришло время развернуть службу.
Вы развернете службу дважды: один раз с использованием образа JIT, а второй раз с использованием образа AOT (Native). Оба развертывания службы будут обрабатывать один и тот же образ из корзины параллельно для целей сравнения.
Сначала установите переменные среды проекта 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
Разверните собственный образ и просмотрите журнал развертывания в консоли:
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
Предоставьте 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 для образов JIT-сервиса и Native-сервиса для обработки образа:
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
.
Нажмите «Журналы» и посмотрите результат:
И действительно, в списке журналов я вижу, что был вызван JIT-сервис image-analysis-jit
.
В журналах указывается начало и окончание выполнения службы. А между ними мы можем видеть журналы, которые мы помещаем в нашу функцию, с операторами журнала на уровне INFO. Мы видим:
- Подробности события, запускающего нашу функцию,
- Необработанные результаты вызова Vision API,
- Этикетки, которые были найдены на загруженной нами картинке,
- Информация о доминирующих цветах,
- Безопасно ли показывать изображение,
- И в конечном итоге эти метаданные об изображении были сохранены в Firestore.
Вы повторите процесс для image-analysis-native
.
В меню «гамбургер» (☰) перейдите к Cloud Run > image-analysis-native
.
Нажмите «Журналы» и посмотрите результат:
Теперь вам нужно будет проверить, сохранены ли метаданные изображения в 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. Поздравляем!
Поздравляем! Вы успешно реализовали первый ключевой сервис проекта!
Что мы рассмотрели
- Облачное хранилище
- Облачный бег
- API облачного видения
- Облачный пожарный магазин
- Собственные изображения Java