1. Omówienie
W pierwszym module do tworzenia kodu umieścisz zdjęcia w zasobniku. Spowoduje to wygenerowanie zdarzenia utworzenia pliku, które będzie obsługiwane przez usługę wdrożoną w Cloud Run. Usługa wywoła interfejs Vision API, aby przeprowadzić analizę obrazu i zapisać wyniki w magazynie danych.
Czego się nauczysz
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.
- Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. To ciąg znaków, który nie jest używany przez interfejsy API Google. W każdej chwili możesz ją zmienić.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i nie można go zmienić (nie można go zmienić po ustawieniu). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń z programowania konieczne jest odwołanie się do identyfikatora projektu (zwykle nazywa się on
PROJECT_ID
). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować kolejny losowy. Możesz też spróbować własnych sił i sprawdzić, czy jest dostępna. Potem nie będzie można go zmienić. Pozostanie ono przez czas trwania projektu. - Dostępna jest trzecia wartość, numer projektu, z którego korzystają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Cloud/interfejsów API. Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Aby wyłączyć zasoby, aby nie naliczać opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.
Uruchamianie Cloud Shell
Google Cloud można obsługiwać zdalnie z laptopa, ale w ramach tego ćwiczenia z programowania wykorzystasz Google Cloud Shell – środowisko wiersza poleceń działające w Cloud.
W konsoli Google Cloud kliknij ikonę Cloud Shell na górnym pasku narzędzi:
Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno pojawić się coś takiego:
Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, znacząco zwiększając wydajność sieci i uwierzytelnianie. Wszystkie zadania w ramach tego ćwiczenia z programowania można wykonywać w przeglądarce. Nie musisz niczego instalować.
3. Włącz interfejsy API
W tym module będziesz używać Cloud Functions i Vision API, ale najpierw musisz włączyć te funkcje w konsoli Cloud lub za pomocą usługi gcloud
.
Aby włączyć Vision API w konsoli Cloud, wyszukaj Cloud Vision API
na pasku wyszukiwania:
Wyświetli się strona Cloud Vision API:
Kliknij przycisk ENABLE
.
Możesz też włączyć Cloud Shell za pomocą narzędzia wiersza poleceń gcloud.
W Cloud Shell uruchom to polecenie:
gcloud services enable vision.googleapis.com
Powinna pojawić się operacja dokończenia:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Włącz też Cloud Run i Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. Tworzenie zasobnika (konsola)
Utwórz zasobnik na zdjęcia. Możesz to zrobić w konsoli Google Cloud Platform ( console.cloud.google.com) lub za pomocą narzędzia wiersza poleceń gsutil z Cloud Shell albo lokalnego środowiska programistycznego.
Otwórz Pamięć
Z „hamburgera” (<!--) przejdź na stronę Storage
.
Nazwij zasobnik
Kliknij przycisk CREATE BUCKET
.
Kliknij CONTINUE
.
Wybierz lokalizację
Utwórz zasobnik z wieloma regionami w wybranym regionie (tutaj Europe
).
Kliknij CONTINUE
.
Wybierz domyślną klasę pamięci
Wybierz klasę pamięci Standard
dla swoich danych.
Kliknij CONTINUE
.
Ustaw kontrolę dostępu
Ponieważ pracujesz z obrazami dostępnymi publicznie, chcesz, aby wszystkie zdjęcia przechowywane w tym zasobniku miały taką samą jednolitą kontrolę dostępu.
Wybierz opcję kontroli dostępu w usłudze Uniform
.
Kliknij CONTINUE
.
Ustaw zabezpieczenia/szyfrowanie
Zachowaj domyślne (Google-managed key)
, ponieważ nie będziesz używać własnych kluczy szyfrowania.
Kliknij CREATE
, aby zakończyć tworzenie zasobnika.
Dodawanie użytkownika allUsers jako wyświetlającego miejsce na dane
Otwórz kartę Permissions
:
Dodaj do zasobnika użytkownika allUsers
z rolą Storage > Storage Object Viewer
w następujący sposób:
Kliknij SAVE
.
5. Tworzenie zasobnika (gsutil)
Zasobniki możesz też tworzyć za pomocą narzędzia wiersza poleceń gsutil
w Cloud Shell.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. W Cloud Shell wartość GOOGLE_CLOUD_PROJECT
jest już ustawiona na Twój unikalny identyfikator projektu. Możesz to dodać do nazwy zasobnika.
Na przykład:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Tworzenie standardowej strefy obejmującej wiele regionów w Europie:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Sprawdź, czy jednolity dostęp na poziomie zasobnika:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Ustaw zasobnik jako publiczny:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Jeśli otworzysz sekcję Cloud Storage
w konsoli, powinien być już publiczny zasobnik uploaded-pictures
:
Sprawdź, czy możesz przesyłać zdjęcia do zasobnika i czy są one publicznie dostępne, jak wyjaśniliśmy w poprzednim kroku.
6. Testowanie dostępu publicznego do zasobnika
Gdy wrócisz do przeglądarki, zobaczysz zasobnik na liście z oznaczeniem „Publiczny”. dostępu (w tym znak ostrzegawczy przypominający o tym, że każdy ma dostęp do zawartości danego zasobnika).
Twój zasobnik jest teraz gotowy na otrzymywanie zdjęć.
Po kliknięciu nazwy zasobnika zobaczysz jego szczegóły.
Tam możesz wypróbować przycisk Upload files
, aby sprawdzić, czy możesz dodać zdjęcie do zasobnika. Pojawi się wyskakujące okienko wyboru plików z prośbą o wybranie pliku. Po wybraniu plik zostanie przesłany do zasobnika i ponownie zobaczysz uprawnienia dostępu na poziomie public
, które zostały automatycznie przypisane do tego nowego pliku.
Na etykiecie dostępu Public
zobaczysz też małą ikonę linku. Po kliknięciu obrazu przeglądarka otworzy jego publiczny adres URL, który będzie miał postać:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME
to globalnie unikalna nazwa nadana przez Ciebie dla zasobnika oraz nazwa pliku zdjęcia.
Gdy klikniesz pole wyboru obok nazwy zdjęcia, przycisk DELETE
stanie się aktywny i będzie można usunąć pierwszy obraz.
7. Przygotowywanie bazy danych
Informacje o zdjęciu wyświetlanym przez interfejs Vision API będziesz przechowywać w bazie danych Cloud Firestore – szybkiej, w pełni zarządzanej, bezserwerowej, chmurowej bazie dokumentów NoSQL. Przygotuj bazę danych w sekcji Firestore
w konsoli Cloud:
Dostępne są 2 opcje: Native mode
i Datastore mode
. Korzystaj z trybu natywnego, który oferuje dodatkowe funkcje, takie jak obsługa offline i synchronizacja w czasie rzeczywistym.
Kliknij SELECT NATIVE MODE
.
Wybierz wiele regionów (w Europie, ale najlepiej co najmniej taki sam region, w jakim są Twoje funkcja i zasobnik na dane).
Kliknij przycisk CREATE DATABASE
.
Po utworzeniu bazy danych powinien wyświetlić się ten komunikat:
Utwórz nową kolekcję, klikając przycisk + START COLLECTION
.
Nazwij kolekcję pictures
.
Nie musisz tworzyć dokumentu. Będziesz dodawać je automatycznie w miarę zapisywania nowych zdjęć w Cloud Storage i analizowania ich przez interfejs Vision API.
Kliknij Save
.
Firestore tworzy w nowo utworzonej kolekcji pierwszy dokument domyślny. Dokument możesz bezpiecznie usunąć, ponieważ nie zawiera on żadnych przydatnych informacji.
Dokumenty, które zostaną utworzone automatycznie w naszej kolekcji, będą zawierać 4 pola:
- name (ciąg znaków): nazwa pliku przesłanego zdjęcia, który jest również kluczem dokumentu.
- labels (tablica ciągów znaków): etykiety rozpoznawane przez interfejs Vision API
- color (ciąg znaków): szesnastkowy kod koloru dominującego (np. #ab12ef)
- created (data): sygnatura czasowa przechowywania metadanych obrazu.
- thumbnail (wartość logiczna): opcjonalne pole, które będzie obecne i będzie mieć wartość true (prawda), jeśli dla danego zdjęcia wygenerowano obraz miniatury
Będziemy szukać w Firestore zdjęć, które mają dostępne miniatury, i sortujemy według daty utworzenia, więc konieczne będzie utworzenie indeksu wyszukiwania.
Indeks możesz utworzyć za pomocą tego polecenia w Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Możesz to też zrobić w konsoli Cloud. Aby to zrobić, w kolumnie nawigacji po lewej stronie kliknij Indexes
, a potem utwórz indeks złożony w ten sposób:
Kliknij Create
. Tworzenie indeksu może potrwać kilka minut.
8. Klonowanie kodu
Skopiuj kod, jeśli jeszcze nie udało Ci się tego zrobić w poprzednim module:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Następnie możesz przejść do katalogu zawierającego usługę, aby rozpocząć tworzenie modułu:
cd serverless-photosharing-workshop/services/image-analysis/java
Masz do dyspozycji taki układ plików dla usługi:
9. Zapoznaj się z kodem usługi
Najpierw sprawdź, jak biblioteki klienta Java są włączone w pom.xml
przy użyciu BOM:
Najpierw edytuj plik pom.xml
, który zawiera listę zależności naszej funkcji w Javie. Zaktualizuj kod, aby dodać zależność Maven interfejsu Cloud Vision API:
<?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>
Funkcja jest wdrożona w klasie EventController
. Za każdym razem, gdy do zasobnika jest przesyłany nowy obraz, usługa otrzymuje powiadomienie, które musi przetworzyć:
@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 {
...
}
Rozpocznie się kod sprawdzania nagłówków 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);
}
Teraz można skompilować żądanie, a kod przygotuje jedno z nich, które zostanie wysłane do 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);
Prosimy o 3 kluczowe funkcje interfejsu Vision API:
- Wykrywanie etykiet: sprawdzanie, co znajduje się na zdjęciach.
- Właściwości obrazu: w celu nadania nam interesujących atrybutów zdjęcia (chcemy zwrócić uwagę na dominujący kolor zdjęcia).
- Bezpieczne wyszukiwanie: pozwala określić, czy obraz jest bezpieczny do wyświetlenia (nie może zawierać treści dla dorosłych, medycyny, treści dla dorosłych ani przemocy).
W tym momencie możemy wywołać interfejs Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Oto, jak wygląda odpowiedź z interfejsu 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
}
Jeśli nie zostanie zwrócony żaden błąd, możemy przejść dalej, z tego powodu w przypadku blokady:
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);
}
Poznamy etykiety elementów, kategorii i tematów rozpoznawane na ilustracji:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Interesuje nas dominujący kolor zdjęcia:
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);
}
Sprawdźmy, czy zdjęcie można bezpiecznie wyświetlać:
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);
}
Sprawdzamy treści dla dorosłych / parodie / medycyna / przemoc / treści dla dorosłych, aby sprawdzić, czy są one prawdopodobne lub bardzo prawdopodobne.
Jeśli wynik bezpiecznego wyszukiwania jest prawidłowy, możemy przechowywać metadane w 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. Kompilowanie obrazów aplikacji przy użyciu GraalVM (opcjonalnie)
W tym opcjonalnym kroku utworzysz JIT(JVM) based app image
, a następnie AOT(Native) Java app image
, przy użyciu GraalVM.
Aby uruchomić kompilację, musisz mieć zainstalowany i skonfigurowany odpowiedni pakiet JDK oraz narzędzie do tworzenia obrazów natywnych. Dostępnych jest kilka opcji.
To start
, pobierz GraalVM 22.2.x Community Edition i postępuj zgodnie z instrukcjami na stronie instalacji GraalVM.
Dzięki SDKMAN proces ten można znacznie uprościć.
Aby zainstalować odpowiednią dystrybucję JDK za pomocą dodatku SDKman
, zacznij od polecenia instalacji:
sdk install java 22.2.r17-grl
Poproś SDKmana o używanie tej wersji zarówno w przypadku kompilacji JIT, jak i AOT:
sdk use java 22.2.0.r17-grl
Zainstaluj native-image utility
dla GraalVM:
gu install native-image
W Cloudshell
możesz dla Twojej wygody zainstalować GraalVM i narzędzie natywne do tworzenia obrazów za pomocą tych prostych poleceń:
# 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 ../..
Najpierw skonfiguruj zmienne środowiskowe projektu GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Następnie możesz przejść do katalogu zawierającego usługę, aby rozpocząć tworzenie modułu:
cd serverless-photosharing-workshop/services/image-analysis/java
Skompiluj obraz aplikacji JIT(JVM):
./mvnw package -Pjvm
Obserwuj log kompilacji w terminalu:
... [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] ------------------------------------------------------------------------
Utwórz obraz AOT(natywny).
./mvnw package -Pnative -DskipTests
Obserwuj log kompilacji w terminalu, w tym logi kompilacji obrazu natywnego:
Pamiętaj, że w zależności od maszyny, na której testujesz, kompilacja może zająć nieco więcej czasu.
... [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. Kompilowanie i publikowanie obrazów kontenerów
Stwórzmy obraz kontenera w 2 różnych wersjach: jedną jako JIT(JVM) image
, a drugą jako element AOT(Native) Java image
.
Najpierw skonfiguruj zmienne środowiskowe projektu GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Utwórz obraz JIT(JVM):
./mvnw package -Pjvm-image
Obserwuj log kompilacji w terminalu:
[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] ------------------------------------------------------------------------
Utwórz obraz AOT(natywny).
./mvnw package -Pnative-image
Obserwuj log kompilacji w terminalu, w tym logi kompilacji obrazu natywnego oraz kompresję obrazu za pomocą UPX.
Pamiętaj, że kompilacja może zająć nieco więcej czasu, w zależności od maszyny, na której testujesz.
... [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] ------------------------------------------------------------------------
Sprawdź, czy obrazy zostały utworzone:
docker images | grep image-analysis
Oznacz oba obrazy tagami i przekaż je do 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. Wdrożenie w Cloud Run
Czas wdrożyć usługę.
Usługę wdrożysz dwukrotnie: raz z użyciem obrazu JIT(JVM) i drugi z obrazem AOT(natywnym). Oba wdrożenia usług będą równolegle przetwarzać ten sam obraz z zasobnika w celu porównania.
Najpierw skonfiguruj zmienne środowiskowe projektu 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
Wdróż obraz JIT(JVM) i obserwuj dziennik wdrożenia w konsoli:
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
Wdróż obraz AOT(Native) i obserwuj dziennik wdrożenia w konsoli:
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. Skonfiguruj aktywatory Eventarc
Eventarc oferuje ujednolicone rozwiązanie do zarządzania przepływem zmian stanu zwanych zdarzeniami między odłączonych mikroserwisami. Po uruchomieniu Eventarc kieruje te zdarzenia przez subskrypcje Pub/Sub do różnych miejsc docelowych (w tym dokumencie znajdziesz informacje o miejscach docelowych zdarzeń), jednocześnie zarządzając dostarczaniem, zabezpieczeniami, autoryzacją, dostrzegalnością i obsługą błędów.
Możesz utworzyć aktywator Eventarc, aby usługa Cloud Run otrzymywała powiadomienia o określonym zdarzeniu lub zbiorze zdarzeń. Określając filtry aktywatora, możesz skonfigurować routing zdarzenia, w tym do źródła zdarzeń i docelowej usługi Cloud Run.
Najpierw skonfiguruj zmienne środowiskowe projektu 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
Przyznaj uprawnienia pubsub.publisher
do konta usługi 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'
Aby przetworzyć obraz, skonfiguruj aktywatory Eventarc dla obrazów usługi JVM(JIT) i AOT(natywne):
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
Zwróć uwagę, że 2 aktywatory zostały utworzone:
gcloud eventarc triggers list --location=eu
14. Testuj wersje usługi
Po pomyślnym wdrożeniu usługi opublikujesz zdjęcie w Cloud Storage. Sprawdzisz, czy nasze usługi zostały wywołane, co zwraca interfejs Vision API i czy metadane są przechowywane w Firestore.
Wróć do Cloud Storage
i kliknij zasobnik utworzony na początku modułu:
Na stronie szczegółów zasobnika kliknij przycisk Upload files
, aby przesłać zdjęcie.
Na przykład obraz GeekHour.jpeg
jest udostępniany z bazą kodu w sekcji /services/image-analysis/java
. Wybierz obraz i naciśnij Open button
:
Możesz teraz sprawdzić wykonanie usługi, zaczynając od image-analysis-jvm
, a następnie image-analysis-native
.
Z „hamburgera” (<!--) przejdź do usługi Cloud Run > image-analysis-jvm
.
Kliknij Logi i sprawdź dane wyjściowe:
Na liście logów widać, że usługa JIT(JVM) image-analysis-jvm
została wywołana.
Logi wskazują początek i koniec wykonania usługi. Pomiędzy nimi widoczne są logi umieszczone w funkcji z instrukcją logu na poziomie INFO. Widzimy:
- szczegóły zdarzenia aktywującego naszą funkcję,
- Nieprzetworzone wyniki z wywołania interfejsu Vision API
- Etykiety znalezione na przesłanym zdjęciu,
- informacje o kolorach dominujących,
- Whether the picture is safe to show,
- Metadane obrazu zostały zapisane w Firestore.
Powtórz ten proces dla usługi image-analysis-native
.
Z „hamburgera” (<!--) przejdź do usługi Cloud Run > image-analysis-native
.
Kliknij Logi i sprawdź dane wyjściowe:
Teraz warto sprawdzić, czy metadane obrazu zostały zapisane w Fiorestore.
Ponownie od „hamburgera” (<!--) przejdź do sekcji Firestore
. W podsekcji Data
(wyświetlanej domyślnie) powinna być widoczna kolekcja pictures
z nowym dokumentem odpowiadającym przesłanym właśnie zdjęciu:
15. Czyszczenie (opcjonalnie)
Jeśli nie chcesz przechodzić do innych modułów z tej serii, możesz zwolnić zasoby, aby zmniejszyć koszty i zachować dobre praktyki związane z chmurą. Zasoby możesz wyczyścić pojedynczo w ten sposób.
Usuń zasobnik:
gsutil rb gs://${BUCKET_PICTURES}
Usuń tę funkcję:
gcloud functions delete picture-uploaded --region europe-west1 -q
Usuń kolekcję Firestore, wybierając z niej Usuń kolekcję:
Możesz też usunąć cały projekt:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Gratulacje!
Gratulacje! Udało Ci się wdrożyć pierwszą usługę kluczy w projekcie.
Omówione zagadnienia
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
- Natywne obrazy Java