1. Panoramica
Nel primo lab di codice, archivierai le immagini in un bucket. Verrà generato un evento di creazione di file che verrà gestito da un servizio di cui è stato eseguito il deployment in Cloud Run. Il servizio effettuerà una chiamata all'API Vision per eseguire l'analisi delle immagini e salvare i risultati in un datastore.
Cosa imparerai a fare
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
2. Configurazione e requisiti
Configurazione dell'ambiente da seguire in modo autonomo
- Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.
- Il Nome progetto è il nome visualizzato dei partecipanti del progetto. Si tratta di una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarla in qualsiasi momento.
- L'ID progetto deve essere univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). La console Cloud genera automaticamente una stringa univoca. di solito non ti importa cosa sia. Nella maggior parte dei codelab, dovrai fare riferimento all'ID progetto (in genere è identificato come
PROJECT_ID
). Se l'ID generato non ti soddisfa, puoi generarne un altro casuale. In alternativa, puoi provarne una personalizzata per verificare se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà per tutta la durata del progetto. - Per informazione, c'è un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
- Successivamente, dovrai abilitare la fatturazione nella console Cloud per utilizzare risorse/API Cloud. Eseguire questo codelab non dovrebbe costare molto. Per arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial, puoi eliminare le risorse che hai creato o eliminare l'intero progetto. I nuovi utenti di Google Cloud sono idonei al programma prova senza costi di 300$.
Avvia Cloud Shell
Anche se Google Cloud può essere utilizzato da remoto dal tuo laptop, in questo codelab utilizzerai Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.
Dalla console Google Cloud, fai clic sull'icona di Cloud Shell nella barra degli strumenti in alto a destra:
Dovrebbe richiedere solo qualche istante per eseguire il provisioning e connettersi all'ambiente. Al termine, dovresti vedere una schermata simile al seguente:
Questa macchina virtuale viene caricata con tutti gli strumenti di sviluppo necessari. Offre una home directory permanente da 5 GB e viene eseguita su Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Tutto il lavoro in questo codelab può essere svolto all'interno di un browser. Non occorre installare nulla.
3. Abilita API
Per questo lab utilizzerai Cloud Functions e l'API Vision, ma prima devi abilitarle nella console Cloud o con gcloud
.
Per abilitare l'API Vision nella console Cloud, cerca Cloud Vision API
nella barra di ricerca:
Verrà visualizzata la pagina dell'API Cloud Vision:
Fai clic sul pulsante ENABLE
.
In alternativa, puoi anche abilitarlo Cloud Shell utilizzando lo strumento a riga di comando gcloud.
All'interno di Cloud Shell, esegui questo comando:
gcloud services enable vision.googleapis.com
L'operazione dovrebbe essere completata correttamente:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Abilita anche Cloud Run e Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. Crea il bucket (console)
Creare un bucket di archiviazione per le immagini. Puoi eseguire questa operazione dalla console della piattaforma Google Cloud ( console.cloud.google.com) o con lo strumento a riga di comando gsutil da Cloud Shell o dall'ambiente di sviluppo locale.
Vai ad Storage
Dall'"hamburger" (Segnaposto), vai alla pagina Storage
.
Assegna un nome al bucket
Fai clic sul pulsante CREATE BUCKET
.
Fai clic su CONTINUE
.
Scegli località
Crea un bucket multiregionale nella regione che preferisci (qui Europe
).
Fai clic su CONTINUE
.
Scegli la classe di archiviazione predefinita
Scegli la classe di archiviazione Standard
per i tuoi dati.
Fai clic su CONTINUE
.
Impostare il controllo dell'accesso
Dal momento che lavorerai con immagini accessibili pubblicamente, vuoi che tutte le nostre immagini archiviate in questo bucket abbiano lo stesso controllo dell'accesso uniforme.
Scegli l'opzione di controllo dell'accesso di Uniform
.
Fai clic su CONTINUE
.
Imposta protezione/crittografia
Mantieni l'impostazione predefinita (Google-managed key)
, perché non userai le tue chiavi di crittografia.
Fai clic su CREATE
per finalizzare la creazione del bucket.
Aggiungi allUsers come visualizzatore dello spazio di archiviazione
Vai alla scheda Permissions
:
Aggiungi un membro allUsers
al bucket con il ruolo Storage > Storage Object Viewer
, come segue:
Fai clic su SAVE
.
5. Crea il bucket (gsutil)
Puoi anche utilizzare lo strumento a riga di comando gsutil
in Cloud Shell per creare i bucket.
In Cloud Shell, imposta una variabile per il nome univoco del bucket. In Cloud Shell è già impostato GOOGLE_CLOUD_PROJECT
sul tuo ID progetto univoco. Puoi aggiungerlo al nome del bucket.
Ad esempio:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Crea una zona multiregionale standard in Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Garantisci un accesso uniforme a livello di bucket:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Rendi pubblico il bucket:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Se vai alla sezione Cloud Storage
della console, dovresti avere un bucket uploaded-pictures
pubblico:
Verifica di poter caricare le immagini nel bucket e che le immagini caricate saranno disponibili pubblicamente, come spiegato nel passaggio precedente.
6. Testa l'accesso pubblico al bucket
Se torni al browser di Storage, vedrai il bucket nell'elenco, con "Pubblico" (incluso un segnale di avviso che ti ricorda che chiunque ha accesso ai contenuti del bucket).
Il bucket è ora pronto per ricevere immagini.
Se fai clic sul nome del bucket, vedrai i relativi dettagli.
Qui puoi provare il pulsante Upload files
per verificare di poter aggiungere un'immagine al bucket. Un popup di selezione file ti chiederà di selezionare un file. Una volta selezionato, verrà caricato nel tuo bucket e vedrai di nuovo l'accesso public
che è stato attribuito automaticamente a questo nuovo file.
Lungo l'etichetta di accesso Public
, vedrai anche una piccola icona del link. Facendo clic sull'immagine, il browser viene indirizzato all'URL pubblico dell'immagine, che avrà il seguente formato:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME
è il nome univoco a livello globale che hai scelto per il bucket, seguito dal nome del file della tua immagine.
Se fai clic sulla casella di controllo accanto al nome dell'immagine, il pulsante DELETE
verrà attivato e potrai eliminare la prima immagine.
7. prepara il database
Archivierai le informazioni sull'immagine fornita dall'API Vision nel database Cloud Firestore, un database di documenti NoSQL cloud-native, serverless, veloce e completamente gestito. Prepara il database accedendo alla sezione Firestore
della console Cloud:
Sono disponibili due opzioni: Native mode
o Datastore mode
. Usa la modalità nativa, che offre funzionalità extra come il supporto offline e la sincronizzazione in tempo reale.
Fai clic su SELECT NATIVE MODE
.
Scegli una località multiregionale (qui in Europa, ma idealmente almeno la stessa regione della tua funzione e del tuo bucket di archiviazione).
Fai clic sul pulsante CREATE DATABASE
.
Una volta creato il database, dovresti vedere quanto segue:
Crea una nuova raccolta facendo clic sul pulsante + START COLLECTION
.
Nome raccolta pictures
.
Non è necessario creare un documento. Le aggiungerai in modo programmatico man mano che le nuove immagini vengono archiviate in Cloud Storage e analizzate dall'API Vision.
Fai clic su Save
.
Firestore crea un primo documento predefinito nella raccolta appena creata. Puoi eliminare in tutta sicurezza quel documento poiché non contiene informazioni utili:
I documenti che verranno creati in modo programmatico nella nostra raccolta conterranno quattro campi:
- name (stringa): il nome file dell'immagine caricata, che è anche la chiave del documento.
- labels (array di stringhe): le etichette degli elementi riconosciuti dall'API Vision
- color (stringa): il codice colore esadecimale del colore dominante (ad es. #ab12ef)
- creato (data): il timestamp della data di archiviazione dei metadati di questa immagine
- thumbnail (booleano): un campo facoltativo che sarà presente e che sarà true se è stata generata un'immagine in miniatura per questa immagine
Dato che cercheremo in Firestore per trovare immagini con miniature disponibili, ordinando i contenuti in base alla data di creazione, dovremo creare un indice di ricerca.
Puoi creare l'indice con il comando seguente in Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
In alternativa, puoi farlo dalla console Cloud facendo clic su Indexes
nella colonna di navigazione a sinistra, quindi creando un indice composto come mostrato di seguito:
Fai clic su Create
. La creazione dell'indice può richiedere alcuni minuti.
8. Clona il codice
Clona il codice, se non l'hai già fatto nel codelab precedente:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Potrai quindi passare alla directory che contiene il servizio per iniziare a creare il lab:
cd serverless-photosharing-workshop/services/image-analysis/java
Per il servizio avrai il seguente layout di file:
9. Esplora il codice del servizio
Per iniziare, controlla come vengono abilitate le librerie client Java in pom.xml
utilizzando un BOM:
In primo luogo, modifica il file pom.xml
che elenca le dipendenze della funzione Java. Aggiorna il codice per aggiungere la dipendenza Maven dell'API Cloud Vision:
<?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>
La funzionalità è implementata nella classe EventController
. Ogni volta che viene caricata una nuova immagine nel bucket, il servizio riceverà una notifica per l'elaborazione:
@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 {
...
}
Il codice procede con la convalida delle intestazioni 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);
}
Ora è possibile creare una richiesta e il codice preparerà una richiesta di questo tipo da inviare a 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);
Ti chiediamo 3 funzionalità chiave dell'API Vision:
- Rilevamento etichette: per capire cosa c'è in queste immagini.
- Proprietà immagine: per assegnare attributi interessanti all'immagine (ci interessa il colore predominante dell'immagine).
- SafeSearch: per sapere se l'immagine può essere mostrata in sicurezza (non deve includere contenuti per adulti / medici / per adulti / violenti).
A questo punto, possiamo chiamare l'API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Come riferimento, ecco l'aspetto della risposta dell'API Vision:
{
"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
}
Se non viene restituito alcun errore, possiamo andare avanti. Ecco perché abbiamo questo codice "If block":
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);
}
Raccoglieremo le etichette degli elementi, delle categorie o dei temi riconosciuti nell'immagine:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Ci interessa conoscere il colore dominante dell'immagine:
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);
}
Vediamo se è sicuro mostrare l'immagine:
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);
}
Stiamo controllando le caratteristiche per adulti / spoofing / medici / violenti / per adulti per capire se sono probabili o molto probabili.
Se il risultato della ricerca sicura è accettabile, possiamo archiviare i metadati in 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. Creare immagini dell'app con GraalVM (facoltativo)
In questo passaggio facoltativo, creerai un'entità JIT(JVM) based app image
e poi una AOT(Native) Java app image
utilizzando GraalVM.
Per eseguire la build, dovrai assicurarti di aver installato e configurato un JDK appropriato e il generatore di immagini native. Le opzioni disponibili sono diverse.
To start
, scarica GraalVM 22.2.x Community Edition e segui le istruzioni riportate nella pagina di installazione di GraalVM.
Questo processo può essere notevolmente semplificato con l'aiuto di SDKMAN!
Per installare la distribuzione JDK appropriata con SDKman
, inizia utilizzando il comando di installazione:
sdk install java 22.2.r17-grl
Chiedi a SDKman di utilizzare questa versione per le build JIT e AOT:
sdk use java 22.2.0.r17-grl
Installa native-image utility
per GraalVM:
gu install native-image
In Cloudshell
, per comodità, puoi installare GraalVM e l'utilità per immagini native con questi semplici comandi:
# 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 ../..
Per prima cosa, imposta le variabili di ambiente del progetto Google Cloud:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Potrai quindi passare alla directory che contiene il servizio per iniziare a creare il lab:
cd serverless-photosharing-workshop/services/image-analysis/java
Crea l'immagine dell'applicazione JIT(JVM):
./mvnw package -Pjvm
Osserva il log di build nel terminale:
... [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] ------------------------------------------------------------------------
Crea l'immagine AOT(nativa):
./mvnw package -Pnative -DskipTests
Osserva il log di build nel terminale, inclusi i log di build dell'immagine nativa:
Tieni presente che la build richiede un po' più tempo, a seconda della macchina su cui stai eseguendo il test.
... [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. Crea e pubblica immagini container
Creiamo un'immagine container in due versioni diverse: una come JIT(JVM) image
e l'altra come AOT(Native) Java image
.
Per prima cosa, imposta le variabili di ambiente del progetto Google Cloud:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Crea l'immagine JIT(JVM):.
./mvnw package -Pjvm-image
Osserva il log di build nel terminale:
[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] ------------------------------------------------------------------------
Crea l'immagine AOT(nativa):
./mvnw package -Pnative-image
Osserva il log di build nel terminale, inclusi i log di build delle immagini native e la compressione delle immagini tramite UPX.
Tieni presente che la creazione richiede un po' più di tempo, a seconda della macchina su cui stai eseguendo il test.
... [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] ------------------------------------------------------------------------
Verifica che le immagini siano state create:
docker images | grep image-analysis
Tagga le due immagini ed eseguine il push su 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. Esegui il deployment in Cloud Run
Tempo necessario per il deployment del servizio.
Eseguirai il deployment del servizio due volte, una utilizzando l'immagine JIT(JVM) e la seconda usando l'immagine AOT(native). Entrambi i deployment del servizio elaboreranno la stessa immagine dal bucket in parallelo, a fini di confronto.
Per prima cosa, imposta le variabili di ambiente del progetto Google Cloud:
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
Esegui il deployment dell'immagine JIT(JVM) e osserva il log di deployment nella console:
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
Esegui il deployment dell'immagine AOT(native) e osserva il log di deployment nella console:
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. Configura i trigger Eventarc
Eventarc offre una soluzione standardizzata per la gestione del flusso delle modifiche dello stato, chiamate eventi, tra microservizi disaccoppiati. Se attivato, Eventarc instrada questi eventi tramite sottoscrizioni Pub/Sub a varie destinazioni (in questo documento, vedi Destinazioni evento) e gestisce per te distribuzione, sicurezza, autorizzazione, osservabilità e gestione degli errori.
Puoi creare un trigger Eventarc in modo che il servizio Cloud Run riceva le notifiche di un evento specifico o di un insieme di eventi. Specificando i filtri per il trigger, puoi configurare il routing dell'evento, inclusi l'origine evento e il servizio Cloud Run di destinazione.
Per prima cosa, imposta le variabili di ambiente del progetto Google Cloud:
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
Concedi pubsub.publisher
all'account di servizio 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'
Configura i trigger Eventarc per le immagini di servizio JVM(JIT) e AOT(native) per elaborare l'immagine:
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
Osserva che i due attivatori sono stati creati:
gcloud eventarc triggers list --location=eu
14. Testa le versioni del servizio
Una volta che i deployment del servizio hanno esito positivo, pubblicherai un'immagine su Cloud Storage, controlla se i nostri servizi sono stati richiamati, cosa restituisce l'API Vision e se i metadati sono archiviati in Firestore.
Torna a Cloud Storage
e fai clic sul bucket che abbiamo creato all'inizio del lab:
Nella pagina dei dettagli del bucket, fai clic sul pulsante Upload files
per caricare un'immagine.
Ad esempio, viene fornita un'immagine GeekHour.jpeg
con il tuo codebase in /services/image-analysis/java
. Seleziona un'immagine e premi il Open button
:
Ora puoi controllare l'esecuzione del servizio iniziando con image-analysis-jvm
, seguito da image-analysis-native
.
Dall'"hamburger" (Segnaposto), vai al servizio Cloud Run > image-analysis-jvm
.
Fai clic su Log e osserva l'output:
In effetti, nell'elenco dei log, posso notare che il servizio JIT(JVM) image-analysis-jvm
è stato richiamato.
I log indicano l'inizio e la fine dell'esecuzione del servizio. Nel frattempo, possiamo vedere i log inseriti nella funzione con le istruzioni di log a livello di INFO. Vediamo:
- I dettagli dell'evento che attiva la nostra funzione,
- I risultati non elaborati della chiamata API Vision,
- Le etichette trovate nell'immagine caricata,
- Le informazioni sui colori predominanti
- se è sicuro mostrare l'immagine,
- Alla fine i metadati sull'immagine sono stati archiviati in Firestore.
Ripetirai la procedura per il servizio image-analysis-native
.
Dall'"hamburger" (Segnaposto), vai al servizio Cloud Run > image-analysis-native
.
Fai clic su Log e osserva l'output:
Ora vorrai controllare se i metadati dell'immagine sono stati archiviati in Fiorestore.
Di nuovo dall'"hamburger" (Segnaposto), vai alla sezione Firestore
. Nella sottosezione Data
(visualizzata per impostazione predefinita), dovresti vedere la raccolta pictures
con un nuovo documento aggiunto, corrispondente all'immagine che hai appena caricato:
15. Libera spazio (facoltativo)
Se non intendi continuare con gli altri lab della serie, puoi eseguire la pulizia delle risorse per risparmiare sui costi ed essere nel complesso un buon cittadino del cloud. Puoi eseguire la pulizia delle risorse singolarmente come descritto di seguito.
Elimina il bucket:
gsutil rb gs://${BUCKET_PICTURES}
Elimina la funzione:
gcloud functions delete picture-uploaded --region europe-west1 -q
Elimina la raccolta Firestore selezionando Elimina la raccolta dalla raccolta:
In alternativa, puoi eliminare l'intero progetto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Complimenti!
Complimenti! Hai implementato il primo servizio chiavi del progetto.
Argomenti trattati
- Cloud Storage
- Cloud Run
- API Cloud Vision
- Cloud Firestore
- Immagini Java native