Pic-a-daily: Lab 1 – Bilder speichern und analysieren (Native Java)

Pic-a-daily:
Lab 1 – Bilder speichern und analysieren (Native Java)

Informationen zu diesem Codelab

subjectZuletzt aktualisiert: Okt. 11, 2023
account_circleVerfasst von Dan Dobrin

1. Übersicht

Im ersten Code-Lab speichern Sie Bilder in einem Bucket. Dadurch wird ein Ereignis zur Dateierstellung generiert, das von einem in Cloud Run bereitgestellten Dienst verarbeitet wird. Der Dienst ruft die Vision API auf, um eine Bildanalyse durchzuführen und die Ergebnisse in einem Datenspeicher zu speichern.

c0650ee4a76db35e.png

Lerninhalte

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore

2. Einrichtung und Anforderungen

Umgebung für das selbstbestimmte Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

295004821bab6a87.png

37d264871000675d.png

96d86d3d5655cdbe.png

  • Der Projektname ist der Anzeigename für die Projektteilnehmer. Es handelt sich um eine Zeichenfolge, die von Google APIs nicht verwendet wird. Sie können sie jederzeit aktualisieren.
  • Die Projekt-ID ist für alle Google Cloud-Projekte eindeutig und unveränderlich. Sie kann nach dem Festlegen nicht mehr geändert werden. Die Cloud Console generiert automatisch einen eindeutigen String. ist Ihnen meist egal, was es ist. In den meisten Codelabs musst du auf deine Projekt-ID verweisen, die üblicherweise als PROJECT_ID bezeichnet wird. Wenn Ihnen die generierte ID nicht gefällt, können Sie eine weitere zufällige ID generieren. Alternativ können Sie einen eigenen verwenden und nachsehen, ob er verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts erhalten.
  • Zur Information gibt es noch einen dritten Wert, die Projektnummer, die von manchen APIs verwendet wird. Weitere Informationen zu allen drei Werten finden Sie in der Dokumentation.
  1. Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/APIs verwenden zu können. Dieses Codelab ist kostengünstig. Sie können die von Ihnen erstellten Ressourcen oder das Projekt löschen, um Ressourcen herunterzufahren, um zu vermeiden, dass über diese Anleitung hinaus Kosten anfallen. Neue Google Cloud-Nutzer haben Anspruch auf das kostenlose Testprogramm mit 300$Guthaben.

Cloud Shell starten

Sie können Google Cloud zwar von Ihrem Laptop aus aus der Ferne bedienen, in diesem Codelab verwenden Sie jedoch Google Cloud Shell, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.

Klicken Sie in der Google Cloud Console rechts oben in der Symbolleiste auf das Cloud Shell-Symbol:

84688aa223b1c3a2.png

Die Bereitstellung und Verbindung mit der Umgebung dauert nur einen Moment. Wenn er abgeschlossen ist, sollten Sie in etwa Folgendes sehen:

320e18fedb7fbe0.png

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft auf Google Cloud, wodurch die Netzwerkleistung und Authentifizierung erheblich verbessert werden. Alle Arbeiten in diesem Codelab können in einem Browser erledigt werden. Sie müssen nichts installieren.

3. APIs aktivieren

Für dieses Lab verwenden Sie Cloud Functions und die Vision API. Die Funktionen müssen jedoch zuerst entweder in der Cloud Console oder mit gcloud aktiviert werden.

Suchen Sie in der Suchleiste nach Cloud Vision API, um die Vision API in der Cloud Console zu aktivieren:

8f3522d790bb026c.png

Daraufhin gelangen Sie zur Seite mit der Cloud Vision API:

d785572fa14c87c2.png

Klicken Sie auf ENABLE.

Alternativ können Sie es auch in Cloud Shell mit dem gcloud-Befehlszeilentool aktivieren.

Führen Sie in Cloud Shell den folgenden Befehl aus:

gcloud services enable vision.googleapis.com

Der Vorgang sollte jetzt erfolgreich abgeschlossen werden:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

Aktivieren Sie auch Cloud Run und Cloud Build:

gcloud services enable cloudbuild.googleapis.com \
  run.googleapis.com

4. Bucket erstellen (Console)

Erstellen Sie einen Storage-Bucket für die Bilder. Sie können dies über die Google Cloud Platform Console ( console.cloud.google.com) oder mit dem gsutil-Befehlszeilentool in Cloud Shell oder in Ihrer lokalen Entwicklungsumgebung erledigen.

Über das Feld „Hamburger“ (♢) öffnen Sie die Seite "Storage".

d08ecb0ae29330a1.png

Bucket benennen

Klicken Sie auf die Schaltfläche CREATE BUCKET.

8951851554a430d2.png

Klicken Sie auf CONTINUE.

Standort auswählen

24b24625157ab467.png

Erstellen Sie einen multiregionalen Bucket in der Region Ihrer Wahl (hier Europe).

Klicken Sie auf CONTINUE.

Standardspeicherklasse auswählen

9e7bd365fa94a2e0.png

Wählen Sie die Speicherklasse Standard für Ihre Daten aus.

Klicken Sie auf CONTINUE.

Zugriffssteuerung festlegen

1ff4a1f6e57045f5.png

Da Sie mit öffentlich zugänglichen Bildern arbeiten, möchten Sie, dass alle in diesem Bucket gespeicherten Bilder dieselbe einheitliche Zugriffssteuerung haben.

Wählen Sie die Zugriffssteuerungsoption Uniform aus.

Klicken Sie auf CONTINUE.

Schutz/Verschlüsselung festlegen

2d469b076029d365.png

Behalten Sie die Standardeinstellung bei (Google-managed key), da Sie keine eigenen Verschlüsselungsschlüssel verwenden.

Klicken Sie auf CREATE, um die Bucket-Erstellung abzuschließen.

allUsers als Storage Viewer hinzufügen

Rufen Sie den Tab Permissions auf:

19564b3ad8688ae8.png

Fügen Sie dem Bucket ein allUsers-Mitglied mit der Rolle Storage > Storage Object Viewer hinzu:

d655e760c76d62c1.png

Klicken Sie auf SAVE.

5. Bucket erstellen (gsutil)

Sie können Buckets auch mit dem gsutil-Befehlszeilentool in Cloud Shell erstellen.

Legen Sie in Cloud Shell eine Variable für den eindeutigen Bucket-Namen fest. In Cloud Shell ist GOOGLE_CLOUD_PROJECT bereits auf Ihre eindeutige Projekt-ID festgelegt. Sie können dies an den Bucket-Namen anhängen.

Beispiel:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Erstellen Sie eine multiregionale Standardzone in Europa:

gsutil mb -l EU gs://${BUCKET_PICTURES}

Achten Sie auf einheitlichen Zugriff auf Bucket-Ebene:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

Veröffentlichen Sie den Bucket:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

Wenn Sie in der Console zum Abschnitt Cloud Storage gehen, sollten Sie einen öffentlichen uploaded-pictures-Bucket haben:

65c63ef4a6eb30ad.png

Testen Sie, ob Sie Bilder in den Bucket hochladen können und die hochgeladenen Bilder öffentlich zugänglich sind, wie im vorherigen Schritt erläutert.

6. Öffentlichen Zugriff auf den Bucket testen

Wenn Sie zum Storage-Browser zurückkehren, sehen Sie Ihren Bucket in der Liste mit „Öffentlich“ -Zugriff (einschließlich eines Warnsymbols, das Sie daran erinnert, dass jede Person Zugriff auf den Inhalt des Buckets hat).

e639a9ba625b71a6.png

Ihr Bucket ist jetzt bereit zum Empfangen von Bildern.

Wenn Sie auf den Bucket-Namen klicken, werden die Bucket-Details angezeigt.

1f88a2290290aba8.png

Dort können Sie mit der Schaltfläche Upload files testen, ob Sie dem Bucket ein Bild hinzufügen können. Sie werden in einem Pop-up-Fenster zur Dateiauswahl aufgefordert, eine Datei auszuwählen. Nach der Auswahl wird sie in Ihren Bucket hochgeladen und Sie sehen wieder den public-Zugriff, der dieser neuen Datei automatisch zugeordnet wurde.

1209e7ebe1f63b10.png

Neben dem Public-Zugriffslabel siehst du auch ein kleines Linksymbol. Wenn Sie darauf klicken, ruft Ihr Browser die öffentliche URL dieses Bildes auf, die folgendes Format hat:

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

Dabei ist BUCKET_NAME der global eindeutige Name, den Sie für Ihren Bucket ausgewählt haben, und dann der Dateiname Ihres Bildes.

Durch Klicken auf das Kästchen neben dem Bildnamen wird die Schaltfläche DELETE aktiviert und Sie können dieses erste Bild löschen.

7. Datenbank vorbereiten

Sie speichern Informationen über das Bild der Vision API in der Cloud Firestore-Datenbank, einer schnellen, vollständig verwalteten, serverlosen, cloudnativen NoSQL-Dokumentendatenbank. Bereiten Sie Ihre Datenbank vor. Rufen Sie dazu in der Cloud Console den Bereich Firestore auf:

e57a673537b5deca.png

Es werden zwei Optionen angeboten: Native mode oder Datastore mode. Verwenden Sie den nativen Modus, der zusätzliche Funktionen wie Offlinesupport und Echtzeitsynchronisierung bietet.

Klicken Sie auf SELECT NATIVE MODE.

1a2e363fae5c7e96.png

Wählen Sie einen multiregionalen Standort aus (hier in Europa, idealerweise aber in derselben Region wie Ihre Funktion und Ihr Storage-Bucket).

Klicken Sie auf CREATE DATABASE.

Nachdem die Datenbank erstellt wurde, sollten Sie Folgendes sehen:

7dcc82751ed483fb.png

Erstellen Sie eine neue Sammlung, indem Sie auf die Schaltfläche + START COLLECTION klicken.

Sammlung „pictures“ benennen.

dce3d73884ac8c83.png

Sie müssen kein Dokument erstellen. Sie fügen sie programmatisch hinzu, wenn neue Bilder in Cloud Storage gespeichert und von der Vision API analysiert werden.

Klicken Sie auf Save.

Firestore erstellt ein erstes Standarddokument in der neu erstellten Sammlung. Sie können dieses Dokument problemlos löschen, da es keine nützlichen Informationen enthält:

63e95c844b3f79d3.png

Die Dokumente, die in unserer Sammlung programmatisch erstellt werden, enthalten vier Felder:

  • name (String): der Dateiname des hochgeladenen Bildes, der auch der Schlüssel für das Dokument ist
  • labels (Array von Strings): die Labels erkannter Elemente von der Vision API
  • color (String): der hexadezimale Farbcode der dominanten Farbe (d. h. #ab12ef).
  • created (Datum): der Zeitstempel, der angibt, wann die Metadaten dieses Images gespeichert wurden
  • thumbnail (boolesch): ein optionales Feld, das angezeigt wird und „true“ ist, wenn eine Miniaturansicht für dieses Bild generiert wurde

Da wir in Firestore nach Bildern suchen, für die Miniaturansichten verfügbar sind, und nach dem Erstellungsdatum sortieren, müssen wir einen Suchindex erstellen.

Sie können den Index mit dem folgenden Befehl in Cloud Shell erstellen:

gcloud firestore indexes composite create \
 
--collection-group=pictures \
 
--field-config field-path=thumbnail,order=descending \
 
--field-config field-path=created,order=descending

Alternativ können Sie auch in der Cloud Console links in der Navigationsspalte auf Indexes klicken und dann wie unten gezeigt einen zusammengesetzten Index erstellen:

2236d3a024a59232.png

Klicken Sie auf Create. Die Indexerstellung kann einige Minuten dauern.

8. Code klonen

Klonen Sie den Code, falls Sie dies noch nicht im vorherigen Code-Lab getan haben:

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

Wechseln Sie dann zum Verzeichnis mit dem Dienst, um mit dem Erstellen des Labs zu beginnen:

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

Sie haben das folgende Dateilayout für den Dienst:

4c2a18a2c8b69dc5.png

9. Dienstcode ansehen

Sehen Sie sich zuerst an, wie die Java-Clientbibliotheken in pom.xml mithilfe einer BOM aktiviert werden:

Öffnen Sie zuerst die Datei pom.xml, in der die Abhängigkeiten der Java-Anwendung aufgelistet sind. der Fokus auf der Nutzung der Vision, Cloud Storage und Firestore API liegt.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
       
<modelVersion>4.0.0</modelVersion>
   
<parent>
       
<groupId>org.springframework.boot</groupId>
       
<artifactId>spring-boot-starter-parent</artifactId>
       
<version>3.2.0-M3</version>
       
<relativePath/> <!-- lookup parent from repository -->
   
</parent>
   
<groupId>services</groupId>
       
<artifactId>image-analysis</artifactId>
       
<version>0.0.1</version>
       
<name>image-analysis</name>
       
<description>Spring App for Image Analysis</description>
   
<properties>
       
<java.version>17</java.version>
       
<maven.compiler.target>17</maven.compiler.target>
       
<maven.compiler.source>17</maven.compiler.source>        
       
<spring-cloud.version>2023.0.0-M2</spring-cloud.version>
       
<testcontainers.version>1.19.1</testcontainers.version>
   
</properties>
...
 
<dependencyManagement>
   
<dependencies>
       
<dependency>
           
<groupId>com.google.cloud</groupId>
           
<artifactId>libraries-bom</artifactId>
           
<version>26.24.0</version>
           
<type>pom</type>
           
<scope>import</scope>
       
</dependency>
   
</dependencies>
 
</dependencyManagement>

   
<dependencies>
       
<dependency>
           
<groupId>org.springframework.boot</groupId>
           
<artifactId>spring-boot-starter-web</artifactId>
       
</dependency>
               
<dependency>
                       
<groupId>org.springframework.cloud</groupId>
                       
<artifactId>spring-cloud-function-web</artifactId>
               
</dependency>
       
<dependency>
           
<groupId>com.google.cloud.functions</groupId>
           
<artifactId>functions-framework-api</artifactId>
           
<version>1.1.0</version>
           
<type>jar</type>
       
</dependency>
       
<dependency>
           
<groupId>com.google.cloud</groupId>
           
<artifactId>google-cloud-firestore</artifactId>
       
</dependency>
       
<dependency>
           
<groupId>com.google.cloud</groupId>
           
<artifactId>google-cloud-vision</artifactId>
       
</dependency>
       
<dependency>
           
<groupId>com.google.cloud</groupId>
           
<artifactId>google-cloud-storage</artifactId>
       
</dependency>        

Die Funktion wird in der Klasse EventController implementiert. Jedes Mal, wenn ein neues Bild in den Bucket hochgeladen wird, erhält der Dienst eine Benachrichtigung zur Verarbeitung:

@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 {
...
}

Der Code validiert die Cloud Events-Header:

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);
}

Eine Anfrage kann jetzt erstellt werden und der Code bereitet eine solche Anfrage vor, die an Vision API gesendet werden soll:

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);

Sie benötigen drei wichtige Funktionen der Vision API:

  • Labelerkennung: Sie verstehen, was auf den Bildern zu sehen ist.
  • Bildeigenschaften: Hiermit können Sie interessante Attribute des Bildes angeben, da wir uns für die Hauptfarbe des Bildes interessieren.
  • SafeSearch: Herausfinden, ob das Bild sicher angezeigt werden kann (d. h., es darf keine Inhalte nur für Erwachsene / medizinische / nicht jugendfreie Inhalte / gewalttätige Inhalte enthalten)

Jetzt können Sie die Vision API aufrufen:

...
logger
.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

So sieht die Antwort der Vision API aus:

{
 
"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
}

Wenn kein Fehler zurückgegeben wird, können wir fortfahren. Daher haben wir diese Meldung, wenn sie blockieren:

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);
}

Wir bekommen die Beschriftungen der Dinge, Kategorien oder Themen, die auf dem Bild erkannt werden:

List<String> labels = response.getLabelAnnotationsList().stream()
   
.map(annotation -> annotation.getDescription())
   
.collect(Collectors.toList());
logger
.info("Annotations found:");
for (String label: labels) {
    logger
.info("- " + label);
}

Wir möchten die vorherrschende Farbe des Bildes herausfinden:

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);
}

So überprüfen Sie, ob das Bild sicher angezeigt wird:

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);
}

Wir prüfen die Merkmale Erwachsene / Parodien / Medizin / Gewalt / nicht jugendfreie Inhalte, um festzustellen, ob sie unwahrscheinlich wahrscheinlich oder sehr wahrscheinlich sind.

Wenn das Ergebnis der sicheren Suche in Ordnung ist, können wir Metadaten in Firestore speichern:

// Saving result to Firestore
if (isSafe) {
         
ApiFuture<WriteResult> writeResult =
               eventService
.storeImage(fileName, labels,
                                       mainColor
);
          logger
.info("Picture metadata saved in Firestore at " +
               writeResult
.get().getUpdateTime());
}
...
 
public ApiFuture<WriteResult> storeImage(String fileName,
                                           
List<String> labels,
                                           
String mainColor) {
   
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
   
Firestore pictureStore = firestoreOptions.getService();

   
DocumentReference doc = pictureStore.collection("pictures").document(fileName);

   
Map<String, Object> data = new HashMap<>();
    data
.put("labels", labels);
    data
.put("color", mainColor);
    data
.put("created", new Date());

   
return doc.set(data, SetOptions.merge());
 
}

10. App-Images mit GraalVM erstellen

In diesem optionalen Schritt erstellen Sie mithilfe von GraalVM eine JIT based app image und dann eine Native Java app image.

Zum Ausführen des Builds müssen Sie ein geeignetes JDK und den nativen Image-Builder installiert und konfiguriert haben. Es stehen mehrere Optionen zur Verfügung.

To start, laden Sie die GraalVM 22.3.x Community Edition herunter und folgen Sie der Anleitung auf der GraalVM-Installationsseite.

Dieser Vorgang lässt sich mithilfe von SDKMAN!

Um die entsprechende JDK-Distribution mit SDKman zu installieren, starten Sie mit dem Installationsbefehl:

sdk install java 17.0.8-graal

Weisen Sie SDKman an, diese Version sowohl für JIT- als auch für AOT-Builds zu verwenden:

sdk use java 17.0.8-graal

In Cloudshell können Sie GraalVM und das Dienstprogramm für native Images mit diesen einfachen Befehlen installieren:

# download GraalVM
wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz 
tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz

ls -lart

# configure Java 17 and GraalVM for Java 17
# note the name of the latest GraalVM version, as unpacked by the tar command
echo Existing JVM: $JAVA_HOME
cd graalvm-jdk-17.0.8+9.1

export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

cd ../..

# validate the version with
java -version 

# observe
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)

Legen Sie zuerst die Umgebungsvariablen des GCP-Projekts fest:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

Wechseln Sie dann zum Verzeichnis mit dem Dienst, um mit dem Erstellen des Labs zu beginnen:

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

Erstellen Sie das JIT-Anwendungs-Image:

./mvnw package

Sehen Sie sich das Build-Log im Terminal an:

...
[INFO] Results:
[INFO] 
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis ---
[INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.335 s
[INFO] Finished at: 2023-10-10T19:33:25Z
[INFO] ------------------------------------------------------------------------

Natives(mit AOT verwendetes) Image erstellen:

./mvnw native:compile -Pnative

Sehen Sie sich das Build-Log im Terminal an, einschließlich der Build-Logs des nativen Images:

Beachten Sie, dass der Build abhängig von der Maschine, auf der Sie testen, etwas länger dauert.

...
[2/7] Performing analysis...  [*********]                                                              (124.5s @ 4.53GB)
  29,732 (93.19%) of 31,905 classes reachable
  60,161 (70.30%) of 85,577 fields reachable
 261,973 (67.29%) of 389,319 methods reachable
   2,940 classes, 2,297 fields, and 97,421 methods registered for reflection
      81 classes,    90 fields, and    62 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                              (11.7s @ 4.67GB)
[4/7] Parsing methods...      [***]                                                                      (6.1s @ 5.91GB)
[5/7] Inlining methods...     [****]                                                                     (4.5s @ 4.39GB)
[6/7] Compiling methods...    [******]                                                                  (35.3s @ 4.60GB)
[7/7] Creating image...                                                                                 (12.9s @ 4.61GB)
  80.08MB (47.43%) for code area:   190,483 compilation units
  73.81MB (43.72%) for image heap:  660,125 objects and 189 resources
  14.95MB ( 8.86%) for other data
 168.84MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.66MB com.google.cloud.vision.v1p4beta1                   18.51MB byte[] for code metadata
   2.60MB com.google.cloud.vision.v1                           9.27MB java.lang.Class
   2.49MB com.google.protobuf                                  7.34MB byte[] for reflection metadata
   2.40MB com.google.cloud.vision.v1p3beta1                    6.35MB byte[] for java.lang.String
   2.17MB com.google.storage.v2                                5.72MB java.lang.String
   2.12MB com.google.firestore.v1                              4.46MB byte[] for embedded resources
   1.64MB sun.security.ssl                                     4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor
   1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    4.27MB byte[] for general heap data
   1.47MB com.google.cloud.vision.v1p2beta1                    2.50MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     1.17MB java.lang.Object[]
  58.34MB for 977 more packages                                9.19MB for 4667 more object types
------------------------------------------------------------------------------------------------------------------------
                        13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:28 min
[INFO] Finished at: 2023-10-10T19:53:30Z
[INFO] ------------------------------------------------------------------------

11. Container-Images erstellen und veröffentlichen

Lassen Sie uns ein Container-Image in zwei verschiedenen Versionen erstellen: eine als JIT image und die andere als Native Java image.

Legen Sie zuerst die Umgebungsvariablen des GCP-Projekts fest:

export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

JIT-Image erstellen:

./mvnw spring-boot:build-image -Pji

Sehen Sie sich das Build-Log im Terminal an:

[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z
[INFO]     [creator]     *** Images (4c84122a1826):
[INFO]     [creator]           docker.io/library/image-analysis-maven-jit:latest
[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Cache started at 2023-10-10T20:00:38Z
[INFO]     [creator]     Reusing cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO]     [creator]     Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  43.887 s
[INFO] Finished at: 2023-10-10T20:00:39Z
[INFO] ------------------------------------------------------------------------

Erstellen Sie das native AOT-Image:

./mvnw spring-boot:build-image -Pnative

Sehen Sie sich das Build-Log im Terminal an, einschließlich der Build-Logs des nativen Images.

Hinweis:

  • dass der Build je nach Maschine, auf der Sie testen, etwas länger dauert.
  • Die Bilder können mit UPX weiter komprimiert werden, haben aber einen kleinen negativen Einfluss auf die Startleistung. Daher wird für diesen Build kein UPX verwendet – es ist immer ein kleiner Kompromiss
...
[INFO]     [creator]     Saving docker.io/library/image-analysis-maven-native:latest...
[INFO]     [creator]     *** Images (13167702674e):
[INFO]     [creator]           docker.io/library/image-analysis-maven-native:latest
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:37 min
[INFO] Finished at: 2023-10-10T20:05:16Z
[INFO] ------------------------------------------------------------------------

Prüfen Sie, ob die Images erstellt wurden:

docker images | grep image-analysis

Taggen Sie die beiden Images und übertragen Sie sie per Push in GCR:

# JIT image
docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit

# Native(AOT) image
docker tag image-analysis-maven-native gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
docker push  gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native

12. In Cloud Run bereitstellen

Zeit für die Bereitstellung des Dienstes.

Sie stellen den Dienst zweimal bereit, einmal mit dem JIT-Image und ein zweites Mal mit dem AOT-Image(native). Beide Dienstbereitstellungen verarbeiten dasselbe Image aus dem Bucket zu Vergleichszwecken parallel.

Legen Sie zuerst die Umgebungsvariablen des GCP-Projekts fest:

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

Stellen Sie das JIT-Image bereit und beobachten Sie das Bereitstellungsprotokoll in der Console:

gcloud run deploy image-analysis-jit \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated

...
Deploying container to Cloud Run service [image-analysis-jit] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-jit] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jit-...-ew.a.run.app

Stellen Sie das native Image bereit und beobachten Sie das Bereitstellungsprotokoll in der Console:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated 
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app

13. Eventarc-Trigger einrichten

Eventarc bietet eine standardisierte Lösung zum Verwalten des Flusses von Statusänderungen, sogenannten Ereignissen, zwischen entkoppelten Mikrodiensten. Bei Auslösung leitet Eventarc diese Ereignisse über Pub/Sub-Abos an verschiedene Ziele weiter (in diesem Dokument finden Sie unter „Ereignisziele“) und verwaltet dabei die Übermittlung, Sicherheit, Autorisierung, Beobachtbarkeit und Fehlerbehandlung für Sie.

Sie können einen Eventarc-Trigger erstellen, damit Ihr Cloud Run-Dienst Benachrichtigungen zu einem bestimmten Ereignis oder einer Reihe von Ereignissen empfängt. Durch Angabe von Filtern für den Trigger können Sie das Routing des Ereignisses konfigurieren, einschließlich der Ereignisquelle und des Cloud Run-Zieldienstes.

Legen Sie zuerst die Umgebungsvariablen des GCP-Projekts fest:

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

Weisen Sie dem Cloud Storage-Dienstkonto pubsub.publisher zu:

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'

Richten Sie Eventarc-Trigger für JIT- und native Dienst-Images ein, um das Image zu verarbeiten:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jit-trigger \
     --destination-run-service=image-analysis-jit \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com

gcloud eventarc triggers create image-analysis-native-trigger \
     --destination-run-service=image-analysis-native \
     --destination-run-region=europe-west1 \
     --location=eu \
     --event-filters="type=google.cloud.storage.object.v1.finalized" \
     --event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
     --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com    

Sie sehen, dass die beiden Trigger erstellt wurden:

gcloud eventarc triggers list --location=eu

14. Dienstversionen testen

Sobald die Dienstbereitstellungen erfolgreich waren, posten Sie ein Bild in Cloud Storage. Sie sehen dann, ob unsere Dienste aufgerufen wurden, was die Vision API zurückgibt und ob Metadaten in Firestore gespeichert sind.

Gehen Sie zurück zu Cloud Storage und klicken Sie auf den Bucket, den wir zu Beginn des Labs erstellt haben:

33442485a1d76921.png

Klicken Sie auf der Seite mit den Bucket-Details auf die Schaltfläche Upload files, um ein Bild hochzuladen.

Mit Ihrer Codebasis wird beispielsweise ein GeekHour.jpeg-Image unter /services/image-analysis/java bereitgestellt. Wählen Sie ein Bild aus und drücken Sie Open button:

d57529452f62bd32.png

Sie können jetzt die Ausführung des Dienstes prüfen, beginnend mit image-analysis-jit, gefolgt von image-analysis-native.

Über das Feld „Hamburger“ (∆) und zum Dienst Cloud Run > image-analysis-jit gehen.

Klicken Sie auf Logs und beobachten Sie die Ausgabe:

ae1a4a94c7c7a166.png

In der Liste der Logs kann ich sehen, dass der JIT-Dienst image-analysis-jit aufgerufen wurde.

Die Logs geben Beginn und Ende der Dienstausführung an. Und dazwischen sehen wir die Protokolle, die wir in unsere Funktion mit den Protokollanweisungen auf der Ebene INFO eingefügt haben. Wir sehen:

  • Die Details des Ereignisses, das unsere Funktion auslöst,
  • Die Rohergebnisse des Vision API-Aufrufs
  • Die Beschriftungen, die auf dem von uns hochgeladenen Bild gefunden wurden,
  • Die Informationen zu den vorherrschenden Farben,
  • Ob das Bild sicher gezeigt werden kann,
  • Diese Metadaten zum Bild wurden schließlich in Firestore gespeichert.

Wiederholen Sie den Vorgang für den Dienst image-analysis-native.

Über das Feld „Hamburger“ (∆) und zum Dienst Cloud Run > image-analysis-native gehen.

Klicken Sie auf Logs und beobachten Sie die Ausgabe:

4afe22833c1fd14c.png

Sie sollten nun prüfen, ob die Bildmetadaten in Fiorestore gespeichert wurden.

Vom „Hamburger“ (∆) und zum Abschnitt Firestore gehen. Im Unterabschnitt Data (standardmäßig angezeigt) sollte die Sammlung pictures mit einem neuen Dokument zu sehen sein, das dem gerade hochgeladenen Bild entspricht:

82d6c468956e7cfc.png

15. Bereinigen (optional)

Wenn Sie nicht vorhaben, mit den anderen Labs dieser Reihe fortzufahren, können Sie Ressourcen bereinigen, um Kosten zu sparen und insgesamt ein guter Cloud-Nutzer zu sein. Sie können Ressourcen wie folgt einzeln bereinigen.

Löschen Sie den Bucket:

gsutil rb gs://${BUCKET_PICTURES}

Löschen Sie die Funktion:

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

Löschen Sie die Firestore-Sammlung, indem Sie Sammlung löschen aus der Sammlung auswählen:

6cc86a7b88fdb4d3.png

Alternativ können Sie das gesamte Projekt löschen:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Glückwunsch!

Glückwunsch! Sie haben den ersten Schlüsseldienst des Projekts erfolgreich implementiert!

Behandelte Themen

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • Native Java-Bilder

Nächste Schritte