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. Обновите код, чтобы добавить зависимость 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);
Мы просим предоставить три ключевые возможности Vision API:
- Обнаружение этикеток : чтобы понять, что на этих картинках
- Свойства изображения : придать интересные атрибуты изображению (нас интересует доминирующий цвет изображения).
- Безопасный поиск : узнать, безопасно ли показывать изображение (оно не должно содержать материалы для взрослых, медицинские, пикантные или жестокие материалы).
На этом этапе мы можем выполнить вызов Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Для справки, вот как выглядит ответ Vision API:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
Если ошибка не возвращается, мы можем двигаться дальше, поэтому у нас есть этот блок if:
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
Мы собираемся получить названия вещей, категорий или тем, распознанных на картинке:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Нас интересует доминирующий цвет изображения:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
Давайте проверим, можно ли показывать изображение:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
Мы проверяем характеристики «взрослый», «пародия», «медицинский», «насилие», «расовый», чтобы определить, являются ли они маловероятными или очень вероятными .
Если результат безопасного поиска в порядке, мы можем сохранить метаданные в Firestore:
// Saving result to Firestore
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
10. Создайте образы приложений с помощью GraalVM (необязательно)
На этом необязательном этапе вы создадите JIT(JVM) based app image
, а затем AOT(Native) Java app image
с использованием GraalVM.
Чтобы запустить сборку, вам необходимо убедиться, что у вас установлен и настроен соответствующий JDK и сборщик собственных образов. Доступно несколько вариантов.
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 и утилиту собственного образа с помощью этих простых команд:
# 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. Развертывание в Cloud Run
Пришло время развернуть службу.
Вы развернете службу дважды: один раз с использованием образа JIT (JVM), а второй раз с использованием образа 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(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
Предоставьте pubsub.publisher
учетной записи службы Cloud Storage:
SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})" gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:${SERVICE_ACCOUNT}" \ --role='roles/pubsub.publisher'
Настройте триггеры Eventarc для образов служб JVM(JIT) и AOT(Native) для обработки изображения:
gcloud eventarc triggers list --location=eu gcloud eventarc triggers create image-analysis-jvm-trigger \ --destination-run-service=image-analysis-jvm \ --destination-run-region=europe-west1 \ --location=eu \ --event-filters="type=google.cloud.storage.object.v1.finalized" \ --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \ --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com gcloud eventarc triggers create image-analysis-native-trigger \ --destination-run-service=image-analysis-native \ --destination-run-region=europe-west1 \ --location=eu \ --event-filters="type=google.cloud.storage.object.v1.finalized" \ --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \ --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
Обратите внимание, что были созданы два триггера:
gcloud eventarc triggers list --location=eu
14. Тестовые версии сервиса
После успешного развертывания службы вы опубликуете изображение в Cloud Storage и увидите, были ли вызваны наши службы, что возвращает Vision API и хранятся ли метаданные в Firestore.
Вернитесь в Cloud Storage
и щелкните корзину, которую мы создали в начале лабораторной работы:
На странице сведений о сегменте нажмите кнопку Upload files
, чтобы загрузить изображение.
Например, изображение GeekHour.jpeg
предоставляется вместе с вашей кодовой базой в /services/image-analysis/java
. Выберите изображение и нажмите Open button
:
Теперь вы можете проверить выполнение службы, начиная с image-analysis-jvm
, а затем image-analysis-native
.
В меню «гамбургер» (☰) перейдите к Cloud Run > image-analysis-jvm
.
Нажмите «Журналы» и посмотрите результат:
И действительно, в списке журналов я вижу, что был вызван сервис JIT(JVM) image-analysis-jvm
.
В журналах указывается начало и окончание выполнения службы. А между ними мы можем видеть журналы, которые мы помещаем в нашу функцию, с операторами журнала на уровне 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