Pic-a-daily: Bilder mit nativen Google Java-Clientbibliotheken speichern und analysieren

1. Übersicht

Im ersten Codelab speichern Sie Bilder in einem Bucket. Dadurch wird ein Ereignis zum Erstellen einer Datei 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.

427de3100de3a61e.png

Lerninhalte

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

2. Einrichtung und Anforderungen

Umgebung zum selbstbestimmten 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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird. Sie können ihn jederzeit aktualisieren.
  • Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich (kann nach der Festlegung nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser String aussieht. In den meisten Codelabs müssen Sie auf die Projekt-ID verweisen (sie wird in der Regel als PROJECT_ID angegeben). Wenn Ihnen die generierte ID nicht gefällt, können Sie eine andere zufällige ID generieren. Alternativ können Sie es mit einem eigenen versuchen und sehen, ob es verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen.
  • Zur Information: Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
  1. Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs zu verwenden. Die Durchführung dieses Codelabs sollte keine oder nur geringe Kosten verursachen. Wenn Sie Ressourcen herunterfahren möchten, damit Ihnen nach Abschluss dieser Anleitung keine Kosten mehr in Rechnung gestellt werden, können Sie die von Ihnen erstellten Ressourcen oder das gesamte Projekt löschen. Neue Nutzer von Google Cloud kommen für das Programm für kostenlose Testversionen mit einem Guthaben von 300$ infrage.

Cloud Shell starten

Während Sie Google Cloud von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, 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:

55efc1aaa7a4d3ad.png

Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern. Anschließend sehen Sie in etwa Folgendes:

7ffe5cbb04455448.png

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

3. APIs aktivieren

In diesem Lab verwenden Sie Cloud Functions und die Vision API. Diese müssen jedoch zuerst in der Cloud Console oder mit gcloud aktiviert werden.

So aktivieren Sie die Vision API in der Cloud Console: Suchen Sie in der Suchleiste nach Cloud Vision API:

cf48b1747ba6a6fb.png

Sie gelangen auf die Seite der Cloud Vision API:

ba4af419e6086fbb.png

Klicken Sie auf ENABLE.

Alternativ können Sie sie 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 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 (Konsole)

Erstellen Sie einen Storage-Bucket für die Bilder. Dies ist über die Google Cloud Console ( console.cloud.google.com) oder mit dem gsutil-Befehlszeilentool in Cloud Shell oder Ihrer lokalen Entwicklungsumgebung möglich.

Rufen Sie über das Dreistrich-Menü (☰) die Seite Storage auf.

1930e055d138150a.png

Bucket benennen

Klicke auf die Schaltfläche CREATE BUCKET.

34147939358517f8.png

Klicken Sie auf CONTINUE.

Standort auswählen

197817f20be07678.png

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

Klicken Sie auf CONTINUE.

Standardspeicherklasse auswählen

53cd91441c8caf0e.png

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

Klicken Sie auf CONTINUE.

Zugriffssteuerung festlegen

8c2b3b459d934a51.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 Option Uniform aus.

Klicken Sie auf CONTINUE.

Schutz/Verschlüsselung einrichten

d931c24c3e705a68.png

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

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

„allUsers“ als Speicherbetrachter hinzufügen

Rufen Sie den Tab Permissions auf:

d0ecfdcff730ea51.png

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

e9f25ec1ea0b6cc6.png

Klicken Sie auf SAVE.

5. Bucket erstellen (gsutil)

Sie können auch das gsutil-Befehlszeilentool in Cloud Shell verwenden, um Buckets zu 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 das an den Bucket-Namen anhängen.

Beispiel:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

So erstellen Sie eine Standard-Multiregionenzone in Europa:

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

Einheitlichen Zugriff auf Bucket-Ebene sicherstellen:

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

Veröffentlichen Sie den Bucket:

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

Wenn Sie den Bereich Cloud Storage der Console aufrufen, sollte ein öffentlicher uploaded-pictures-Bucket vorhanden sein:

a98ed4ba17873e40.png

Testen Sie, ob Sie Bilder in den Bucket hochladen können und ob die hochgeladenen Bilder öffentlich verfügbar sind, wie im vorherigen Schritt beschrieben.

6. Öffentlichen Zugriff auf den Bucket testen

Wenn Sie zum Speicherbrowser zurückkehren, sehen Sie Ihren Bucket in der Liste mit dem Zugriff „Öffentlich“ (einschließlich eines Warnzeichens, das Sie daran erinnert, dass jeder auf den Inhalt dieses Buckets zugreifen kann).

89e7a4d2c80a0319.png

Ihr Bucket kann jetzt Bilder empfangen.

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

131387f12d3eb2d3.png

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

e87584471a6e9c6d.png

Neben dem Zugriffslabel Public sehen Sie auch ein kleines Linksymbol. Wenn Sie darauf klicken, wird in Ihrem Browser die öffentliche URL des Bildes aufgerufen, die so aussieht:

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

BUCKET_NAME ist der global eindeutige Name, den Sie für Ihren Bucket ausgewählt haben, gefolgt vom Dateinamen Ihres Bildes.

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

7. Datenbank vorbereiten

Sie speichern Informationen zum Bild, die von der Vision API bereitgestellt werden, in der Cloud Firestore-Datenbank. Das ist eine schnelle, vollständig verwaltete, serverlose, cloudnative NoSQL-Dokumentendatenbank. Bereiten Sie Ihre Datenbank vor, indem Sie in der Cloud Console den Bereich Firestore aufrufen:

9e4708d2257de058.png

Es gibt zwei Optionen: Native mode und Datastore mode. Verwenden Sie den nativen Modus, der zusätzliche Funktionen wie Offlineunterstützung und Echtzeitsynchronisierung bietet.

Klicken Sie auf SELECT NATIVE MODE.

9449ace8cc84de43.png

Wählen Sie eine Multi-Region aus (hier in Europa, aber idealerweise mindestens dieselbe Region wie Ihre Funktion und Ihr Speicher-Bucket).

Klicken Sie auf CREATE DATABASE.

Nachdem die Datenbank erstellt wurde, sollten Sie Folgendes sehen:

56265949a124819e.png

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

Name der Sammlung: pictures

75806ee24c4e13a7.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 bedenkenlos löschen, da es keine nützlichen Informationen enthält:

5c2f1e17ea47f48f.png

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

  • name (String): Der Dateiname des hochgeladenen Bildes, der auch der Schlüssel des Dokuments ist.
  • labels (Array von Strings): die Labels der von der Vision API erkannten Elemente
  • color (String): Der hexadezimale Farbcode der dominanten Farbe (z. B. #ab12ef)
  • created (Datum): Der Zeitstempel, zu dem die Metadaten dieses Bildes gespeichert wurden.
  • thumbnail (boolescher Wert): Ein optionales Feld, das vorhanden ist und „true“ enthält, wenn für dieses Bild eine Miniaturansicht generiert wurde.

Da wir in Firestore nach Bildern mit verfügbaren Thumbnails suchen 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

Sie können dies auch in der Cloud Console tun. Klicken Sie dazu in der Navigationsspalte links auf Indexes und erstellen Sie dann einen zusammengesetzten Index, wie unten dargestellt:

ecb8b95e3c791272.png

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

8. Code klonen

Klonen Sie den Code, falls Sie das im vorherigen Codelab noch nicht getan haben:

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

Sie können dann das Verzeichnis mit dem Dienst aufrufen, um mit dem Erstellen des Labs zu beginnen:

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

Der Dienst hat das folgende Dateilayout:

f79613aff479d8ad.png

9. Servicecode ansehen

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

Bearbeiten Sie zuerst die Datei pom.xml, in der die Abhängigkeiten unserer Java-Funktion aufgeführt sind. Aktualisieren Sie den Code, um die Maven-Abhängigkeit der Cloud Vision API hinzuzufügen:

<?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>

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

Jetzt kann eine Anfrage erstellt werden. 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);

Wir bitten um drei wichtige Funktionen der Vision API:

  • Labelerkennung: um zu verstehen, was auf den Bildern zu sehen ist
  • Bildattribute: um interessante Attribute des Bildes zu liefern (wir sind an der dominanten Farbe des Bildes interessiert)
  • SafeSearch: um zu wissen, ob das Bild sicher angezeigt werden kann (es sollte keine Inhalte nur für Erwachsene, medizinische, anstößige oder gewalttätige Inhalte enthalten)

An dieser Stelle können wir den Aufruf an die Vision API senden:

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

Zur Referenz sehen Sie hier, wie die Antwort der Vision API aussieht:

{
  "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. Deshalb haben wir diesen 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);
}

Wir rufen die Labels der Dinge, Kategorien oder Themen ab, die auf dem Bild erkannt wurden:

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 wissen, welche Farbe im Bild dominiert:

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

Prüfen wir, ob das Bild sicher angezeigt werden kann:

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, ob die Merkmale „nicht jugendfrei“, „Parodie“, „medizinisch“, „Gewalt“ oder „freizügig“ wahrscheinlich oder sehr wahrscheinlich sind.

Wenn das Ergebnis der SafeSearch-Erkennung in Ordnung ist, können wir Metadaten in Firestore speichern:

// 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. App-Images mit GraalVM erstellen (optional)

In diesem optionalen Schritt erstellen Sie mit GraalVM zuerst eine JIT(JVM) based app image und dann eine AOT(Native) Java app image.

Damit Sie den Build ausführen können, müssen Sie ein geeignetes JDK und den native-image-Builder installiert und konfiguriert haben. Dafür gibt es mehrere Möglichkeiten.

To start: Laden Sie die GraalVM 22.2.x Community Edition herunter und folgen Sie der Anleitung auf der Seite GraalVM-Installation.

Dieser Prozess lässt sich mit SDKMAN! erheblich vereinfachen.

Um die entsprechende JDK-Distribution mit SDKman zu installieren, verwenden Sie zuerst den Befehl „install“:

sdk install java 22.2.r17-grl

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

sdk use java 22.2.0.r17-grl

Installieren Sie native-image utility für GraalVM:

gu install native-image

In Cloudshell können Sie GraalVM und das native-image-Dienstprogramm mit diesen einfachen Befehlen installieren:

# 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 ../..

Legen Sie zuerst die Umgebungsvariablen für das GCP-Projekt fest:

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

Sie können dann das Verzeichnis mit dem Dienst aufrufen, um mit dem Erstellen des Labs zu beginnen:

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

Erstellen Sie das JIT-Anwendungs-Image(JVM):

./mvnw package -Pjvm

Sehen Sie sich das Build-Log im Terminal an:

...
[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] ------------------------------------------------------------------------

Erstellen Sie das AOT-Image(nativ):

./mvnw package -Pnative -DskipTests

Sehen Sie sich das Build-Log im Terminal an, einschließlich der Build-Logs für native Images:

Je nach Computer, auf dem Sie den Test ausführen, dauert der Build etwas länger.

...
[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. Container-Images erstellen und veröffentlichen

Wir erstellen ein Container-Image in zwei verschiedenen Versionen: einmal als JIT(JVM) image und einmal als AOT(Native) Java image.

Legen Sie zuerst die Umgebungsvariablen für das GCP-Projekt fest:

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

Erstellen Sie das JIT-Image(JVM):

./mvnw package -Pjvm-image

Sehen Sie sich das Build-Log im Terminal an:

[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] ------------------------------------------------------------------------

Erstellen Sie das AOT-Image(nativ):

./mvnw package -Pnative-image

Sehen Sie sich das Build-Log im Terminal an, einschließlich der Build-Logs für das native Image und der Bildkomprimierung mit UPX.

Der Build dauert je nach Computer, auf dem Sie testen, recht lange.

...
[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] ------------------------------------------------------------------------

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(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. In Cloud Run bereitstellen

Zeit für die Bereitstellung des Dienstes.

Sie stellen den Dienst zweimal bereit: einmal mit dem JIT-Image(JVM) und einmal mit dem AOT-Image(nativ). Beide Dienstbereitstellungen verarbeiten dasselbe Bild aus dem Bucket parallel zu Vergleichszwecken.

Legen Sie zuerst die Umgebungsvariablen für das GCP-Projekt 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(JVM) bereit und sehen Sie sich das Bereitstellungslog in der Konsole an:

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

Stellen Sie das AOT-Image(nativ) bereit und sehen Sie sich das Bereitstellungslog in der Konsole an:

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

13. Eventarc-Trigger einrichten

Eventarc bietet eine standardisierte Lösung, um den Ablauf von Statusänderungen, die als Ereignisse bezeichnet werden, zwischen entkoppelten Mikrodiensten zu verwalten. Bei Auslösung leitet Eventarc diese Ereignisse über Pub/Sub-Abos an verschiedene Ziele weiter (in diesem Dokument finden Sie weitere Informationen unter Ereignisziele) und verwaltet dabei die Bereitstellung, Sicherheit, Autorisierung, Beobachtbarkeit und Fehlerbehandlung.

Sie können einen Eventarc-Trigger erstellen, damit Ihr Cloud Run-Dienst Benachrichtigungen über ein bestimmtes Ereignis oder eine Reihe von Ereignissen erhält. 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 für das GCP-Projekt 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 JVM- (JIT) und AOT- (nativ) Dienst-Images ein, um das Bild zu verarbeiten:

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    

Prüfen Sie, ob die beiden Trigger erstellt wurden:

gcloud eventarc triggers list --location=eu

14. Dienstversionen testen

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

Kehren Sie zu Cloud Storage zurück und klicken Sie auf den Bucket, den wir am Anfang des Labs erstellt haben:

ff8a6567afc76235.png

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

Ein GeekHour.jpeg-Image wird beispielsweise mit Ihrem Code unter /services/image-analysis/java bereitgestellt. Wählen Sie ein Bild aus und drücken Sie die Open button:

347b76e8b775f2f5.png

Sie können jetzt die Ausführung des Dienstes prüfen, beginnend mit image-analysis-jvm und dann image-analysis-native.

Rufen Sie über das Dreistrich-Menü (☰) den Dienst Cloud Run > image-analysis-jvm auf.

Klicken Sie auf „Logs“ und sehen Sie sich die Ausgabe an:

810a8684414ceafa.png

Tatsächlich sehe ich in der Liste der Logs, dass der JIT(JVM)-Dienst image-analysis-jvm aufgerufen wurde.

Die Logs geben den Beginn und das Ende der Dienstausführung an. Dazwischen sehen wir die Logs, die wir mit den Log-Anweisungen auf INFO-Ebene in unsere Funktion eingefügt haben. Wir sehen:

  • Details zum Ereignis, das unsere Funktion auslöst,
  • Die Rohdaten aus dem Vision API-Aufruf
  • Die Labels, die auf dem von uns hochgeladenen Bild gefunden wurden.
  • Informationen zu dominanten Farben,
  • ob das Bild sicher angezeigt werden kann,
  • Schließlich werden diese Metadaten zum Bild in Firestore gespeichert.

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

Rufen Sie über das Dreistrich-Menü (☰) den Dienst Cloud Run > image-analysis-native auf.

Klicken Sie auf „Logs“ und sehen Sie sich die Ausgabe an:

b80308c7d0f55a3.png

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

Rufen Sie noch einmal über das Dreistrich-Menü (☰) den Bereich Firestore auf. Im Unterabschnitt Data (standardmäßig angezeigt) sollte die Sammlung pictures mit einem neuen Dokument angezeigt werden, das dem gerade hochgeladenen Bild entspricht:

933a20a9709cb006.png

15. Bereinigen (optional)

Wenn Sie nicht mit den anderen Labs der Reihe fortfahren möchten, können Sie Ressourcen bereinigen, um Kosten zu sparen und nicht mehr benötigte Ressourcen für andere freizugeben. Sie können Ressourcen einzeln so bereinigen:

Löschen Sie den Bucket:

gsutil rb gs://${BUCKET_PICTURES}

Funktion löschen:

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

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

410b551c3264f70a.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 wichtigen Dienst des Projekts erfolgreich implementiert.

Behandelte Themen

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

Nächste Schritte