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

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.

427de3100de3a61e.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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.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 muss für alle Google Cloud-Projekte eindeutig sein und ist 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 die Projekt-ID verweisen, die üblicherweise als PROJECT_ID gekennzeichnet ist. Wenn Ihnen die generierte ID nicht gefällt, können Sie eine weitere zufällige ID erstellen. 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 bestehen.
  • 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 sollte ohne großen Aufwand betrieben werden. Wenn Sie Ressourcen herunterfahren möchten, um über diese Anleitung hinaus keine Kosten zu verursachen, können Sie die von Ihnen erstellten Ressourcen oder das gesamte Projekt löschen. Neue Google Cloud-Nutzer haben Anspruch auf eine kostenlose Testversion von 300$.

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:

55efc1aaa7a4d3ad.png

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

7ffe5cbb04455448.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:

cf48b1747ba6a6fb.png

Daraufhin gelangen Sie zur Seite mit der Cloud Vision API:

ba4af419e6086fbb.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".

1930e055d138150a.png

Bucket benennen

Klicken Sie 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 Zugriffssteuerungsoption Uniform aus.

Klicken Sie auf CONTINUE.

Schutz/Verschlüsselung festlegen

d931c24c3e705a68.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:

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

a98ed4ba17873e40.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).

89e7a4d2c80a0319.png

Ihr Bucket ist jetzt bereit zum Empfangen von Bildern.

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

131387f12d3eb2d3.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.

e87584471a6e9c6d.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:

9e4708d2257de058.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.

9449ace8cc84de43.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:

56265949a124819e.png

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

Sammlung „pictures“ benennen.

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

5c2f1e17ea47f48f.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:

ecb8b95e3c791272.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:

f79613aff479d8ad.png

9. Dienstcode ansehen

Sehen Sie sich zuerst 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 der Java-Funktion aufgelistet sind. Aktualisieren Sie den Code, um die Maven-Abhängigkeit für die 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 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) {
    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 mithilfe von GraalVM eine JIT(JVM) based app image und dann eine AOT(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.2.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 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 Dienstprogramm für native Images 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 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(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 native AOT-Image:

./mvnw package -Pnative -DskipTests

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

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

Legen Sie zuerst die Umgebungsvariablen des GCP-Projekts 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 native AOT-Image:

./mvnw package -Pnative-image

Sehen Sie sich das Build-Log im Terminal an, einschließlich der Build-Logs der nativen Images und der Image-Komprimierung mit UPX.

Beachten Sie, dass der Build je nach Maschine, auf der Sie testen, etwas länger dauert.

...
[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 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(JVM) bereit und sehen Sie sich das Bereitstellungsprotokoll in der Console 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 native AOT-Image bereit und sehen Sie sich das Bereitstellungsprotokoll in der Console 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 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 JVM(JIT)- und AOT-Dienst-Images(native) ein, um das Image 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    

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:

ff8a6567afc76235.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:

347b76e8b775f2f5.png

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

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

Klicken Sie auf Logs und beobachten Sie die Ausgabe:

810a8684414ceafa.png

In der Liste der Logs kann ich sehen, dass der JIT(JVM)-Dienst image-analysis-jvm 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:

b80308c7d0f55a3.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:

933a20a9709cb006.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:

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 Schlüsseldienst des Projekts erfolgreich implementiert!

Behandelte Themen

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

Nächste Schritte