Pic-a-daily: храните и анализируйте изображения с помощью клиентских библиотек Google Native Java

1. Обзор

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

427de3100de3a61e.png

Что вы узнаете

  • Облачное хранилище
  • Облачный бег
  • API облачного видения
  • Облачный пожарный магазин

2. Настройка и требования

Самостоятельная настройка среды

  1. Войдите в Google Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

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

Запустить Cloud Shell

Хотя Google Cloud можно управлять удаленно с вашего ноутбука, в этой лаборатории вы будете использовать Google Cloud Shell , среду командной строки, работающую в облаке.

В Google Cloud Console щелкните значок Cloud Shell на верхней правой панели инструментов:

55efc1aaa7a4d3ad.png

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

7ffe5cbb04455448.png

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

3. Включите API

В этом практическом занятии вы будете использовать Cloud Functions и Vision API, но сначала их необходимо включить либо в Cloud Console, либо с помощью gcloud .

Чтобы включить Vision API в Cloud Console, найдите Cloud Vision API в строке поиска:

cf48b1747ba6a6fb.png

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

ba4af419e6086fbb.png

Нажмите кнопку 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 ».

1930e055d138150a.png

Назовите свое ведро

Нажмите кнопку CREATE BUCKET .

34147939358517f8.png

Нажмите CONTINUE .

Выберите местоположение

197817f20be07678.png

Создайте мультирегиональную корзину в выбранном вами регионе (здесь Europe ).

Нажмите CONTINUE .

Выберите класс хранилища по умолчанию

53cd91441c8caf0e.png

Выберите Standard класс хранения для ваших данных.

Нажмите CONTINUE .

Установить контроль доступа

8c2b3b459d934a51.png

Поскольку вы будете работать с общедоступными изображениями, вы хотите, чтобы все наши изображения, хранящиеся в этом сегменте, имели одинаковый единый контроль доступа.

Выберите вариант Uniform контроль доступа».

Нажмите CONTINUE .

Установить защиту/шифрование

d931c24c3e705a68.png

Оставьте значение по умолчанию ( Google-managed key) , так как вы не будете использовать собственные ключи шифрования.

Нажмите CREATE , чтобы окончательно завершить создание корзины.

Добавить allUsers в качестве средства просмотра хранилища

Перейдите на вкладку Permissions :

d0ecfdcff730ea51.png

Добавьте в корзину член allUsers с ролью Storage > Storage Object Viewer , как показано ниже:

e9f25ec1ea0b6cc6.png

Нажмите 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 :

a98ed4ba17873e40.png

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

6. Проверьте публичный доступ к корзине

Вернувшись в браузер хранилища, вы увидите свою корзину в списке с «Общественным» доступом (включая предупреждающий знак, напоминающий вам, что кто-либо имеет доступ к содержимому этой корзины).

89e7a4d2c80a0319.png

Теперь ваше ведро готово к приему фотографий.

Если вы нажмете на имя корзины, вы увидите подробную информацию о корзине.

131387f12d3eb2d3.png

Там вы можете попробовать кнопку Upload files , чтобы проверить, можете ли вы добавить изображение в корзину. Всплывающее окно выбора файла попросит вас выбрать файл. После выбора он будет загружен в вашу корзину, и вы снова увидите public доступ, который был автоматически присвоен этому новому файлу.

e87584471a6e9c6d.png

Рядом с меткой Public доступ» вы также увидите небольшой значок ссылки. При нажатии на него ваш браузер перейдет на общедоступный URL-адрес этого изображения, который будет иметь вид:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

BUCKET_NAME — это глобальное уникальное имя, которое вы выбрали для своего сегмента, а затем имя файла вашего изображения.

Установив флажок рядом с названием изображения, кнопка DELETE станет активной, и вы сможете удалить это первое изображение.

7. Подготовьте базу данных

Вы сохраните информацию об изображении, предоставленном Vision API, в базе данных Cloud Firestore — быстрой, полностью управляемой, бессерверной, облачной базе данных документов NoSQL. Подготовьте свою базу данных, перейдя в раздел Firestore облачной консоли:

9e4708d2257de058.png

Предлагаются два варианта: Native mode или Datastore mode . Используйте собственный режим, который предлагает дополнительные функции, такие как автономная поддержка и синхронизация в реальном времени.

Нажмите SELECT NATIVE MODE .

9449ace8cc84de43.png

Выберите мультирегион (здесь, в Европе, но в идеале по крайней мере тот же регион, в котором находятся ваша функция и сегмент хранилища).

Нажмите кнопку CREATE DATABASE .

После создания базы данных вы должны увидеть следующее:

56265949a124819e.png

Создайте новую коллекцию , нажав кнопку + START COLLECTION .

Назовите pictures из коллекции.

75806ee24c4e13a7.png

Вам не нужно создавать документ. Вы будете добавлять их программно по мере того, как новые изображения сохраняются в облачном хранилище и анализируются Vision API.

Нажмите Save .

Firestore создает первый документ по умолчанию во вновь созданной коллекции. Вы можете безопасно удалить этот документ, поскольку он не содержит никакой полезной информации:

5c2f1e17ea47f48f.png

Документы, которые будут созданы программно в нашей коллекции, будут содержать 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 в столбце навигации слева, а затем создав составной индекс, как показано ниже:

ecb8b95e3c791272.png

Нажмите Create . Создание индекса может занять несколько минут.

8. Клонируйте код

Клонируйте код, если вы еще этого не сделали в предыдущей лаборатории кода:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

Затем вы можете перейти в каталог, содержащий сервис, чтобы начать создание лаборатории:

cd serverless-photosharing-workshop/services/image-analysis/java

У вас будет следующий макет файла для сервиса:

f79613aff479d8ad.png

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 и щелкните корзину, которую мы создали в начале лабораторной работы:

ff8a6567afc76235.png

На странице сведений о сегменте нажмите кнопку Upload files , чтобы загрузить изображение.

Например, изображение GeekHour.jpeg предоставляется вместе с вашей кодовой базой в /services/image-analysis/java . Выберите изображение и нажмите Open button :

347b76e8b775f2f5.png

Теперь вы можете проверить выполнение службы, начиная с image-analysis-jvm , а затем image-analysis-native .

В меню «гамбургер» (☰) перейдите к Cloud Run > image-analysis-jvm .

Нажмите «Журналы» и посмотрите результат:

810a8684414ceafa.png

И действительно, в списке журналов я вижу, что был вызван сервис JIT(JVM) image-analysis-jvm .

В журналах указывается начало и окончание выполнения службы. А между ними мы можем видеть журналы, которые мы помещаем в нашу функцию, с операторами журнала на уровне INFO. Мы видим:

  • Подробности события, запускающего нашу функцию,
  • Необработанные результаты вызова Vision API,
  • Этикетки, которые были найдены на загруженной нами картинке,
  • Информация о доминирующих цветах,
  • Безопасно ли показывать изображение,
  • И в конечном итоге эти метаданные об изображении были сохранены в Firestore.

Вы повторите процесс для image-analysis-native .

В меню «гамбургер» (☰) перейдите к Cloud Run > image-analysis-native .

Нажмите «Журналы» и посмотрите результат:

b80308c7d0f55a3.png

Теперь вам нужно будет проверить, сохранены ли метаданные изображения в Fiorestore.

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

933a20a9709cb006.png

15. Очистка (необязательно)

Если вы не собираетесь продолжать другие лабораторные работы из этой серии, вы можете очистить ресурсы, чтобы сэкономить средства и стать в целом хорошим гражданином облака. Вы можете очистить ресурсы по отдельности следующим образом.

Удалить корзину:

gsutil rb gs://${BUCKET_PICTURES}

Удалить функцию:

gcloud functions delete picture-uploaded --region europe-west1 -q

Удалите коллекцию Firestore, выбрав Удалить коллекцию из коллекции:

410b551c3264f70a.png

Альтернативно, вы можете удалить весь проект:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Поздравляем!

Поздравляем! Вы успешно реализовали первый ключевой сервис проекта!

Что мы рассмотрели

  • Облачное хранилище
  • Облачный бег
  • API облачного видения
  • Облачный пожарный магазин
  • Собственные изображения Java

Следующие шаги