Bilder asynchron verarbeiten


In dieser Anleitung erfahren Sie, wie Sie mit Cloud Run for Anthos, der Cloud Vision API und ImageMagick anstößige Bilder erkennen und unkenntlich machen, die in einen Cloud Storage-Bucket hochgeladen wurden. Diese Anleitung baut auf der Anleitung Pub/Sub mit Cloud Run for Anthos verwenden auf.

In dieser Anleitung erfahren Sie, wie Sie eine vorhandene Beispielanwendung ändern. Sie können auch das vollständige Beispiel herunterladen.

Lernziele

  • Einen asynchronen Datenverarbeitungsdienst in Cloud Run for Anthos schreiben, erstellen und bereitstellen
  • Den Dienst aufrufen, indem Sie eine Datei in Cloud Storage hochladen und eine Pub/Sub-Nachricht erstellen
  • Gewalttätige oder nicht jugendfreie Inhalte mit der Cloud Vision API erkennen
  • Anstößige Bilder mit ImageMagick unkenntlich machen
  • Den Dienst testen, indem Sie ein Bild eines fleischfressenden Zombies hochladen

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten von Google Cloud:

Mit dem Preisrechner können Sie eine Kostenschätzung für Ihre voraussichtliche Nutzung vornehmen. Neuen Google Cloud-Nutzern steht möglicherweise eine kostenlose Testversion zur Verfügung.

Hinweise

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  2. Make sure that billing is enabled for your Google Cloud project.

  3. Enable the Cloud Run for Anthos and Cloud Vision APIs.

    Enable the APIs

  4. gcloud CLI installieren und initialisieren
  5. Installieren Sie die Komponente kubectl:
    gcloud components install kubectl
  6. Aktualisieren Sie die Komponenten:
    gcloud components update
  7. Erstellen Sie mit Cloud Run for Anthos einen neuen Cluster. Folgen Sie dazu der Anleitung unter Cloud Run for Anthos einrichten.
  8. Richten Sie ein Pub/Sub-Thema, ein sicheres Push-Abo und einen ersten Cloud Run for Anthos-Dienst ein, um Nachrichten zu verarbeiten. Folgen Sie dazu der Anleitung Pub/Sub mit Cloud Run for Anthos verwenden.

gcloud-Standardeinstellungen einrichten

So konfigurieren Sie gcloud mit Standardeinstellungen für Ihren Cloud Run for Anthos-Dienst:

  1. Legen Sie ein Standardprojekt fest:

    gcloud config set project PROJECT_ID

    Ersetzen Sie PROJECT_ID durch den Namen des Projekts, das Sie für diese Anleitung verwenden.

  2. Konfigurieren Sie gcloud für Ihren Cluster:

    gcloud config set run/platform gke
    gcloud config set run/cluster CLUSTER-NAME
    gcloud config set run/cluster_location REGION

    Ersetzen Sie:

    • CLUSTER-NAME durch den Namen, den Sie für den Cluster verwendet haben, und
    • REGION durch den unterstützten Clusterstandort Ihrer Wahl.

Die Reihenfolge der Vorgänge verstehen

Der Datenfluss in dieser Anleitung umfasst die folgenden Schritte:

  1. Ein Nutzer lädt ein Bild in einen Cloud Storage-Bucket hoch.
  2. Cloud Storage veröffentlicht eine Nachricht über die neue Datei an Pub/Sub.
  3. Pub/Sub überträgt die Nachricht an den Cloud Run for Anthos-Dienst.
  4. Der Cloud Run for Anthos-Dienst ruft die Bilddatei ab, auf die in der Pub/Sub-Nachricht verwiesen wird.
  5. Der Cloud Run for Anthos-Dienst verwendet die Cloud Vision API, um das Bild zu analysieren.
  6. Wenn Inhalte mit Darstellungen von Gewalt oder nicht jugendfreie Inhalte erkannt werden, nutzt der Cloud Run for Anthos-Dienst ImageMagick, um das Bild unkenntlich zu machen.
  7. Der Cloud Run for Anthos-Dienst lädt das unkenntlich gemachte Bild zur Verwendung in einen anderen Cloud Storage-Bucket hoch.

Die nachfolgende Verwendung des unkenntlich gemachten Bildes bleibt dem Leser als Übung überlassen.

Cloud Storage-Buckets einrichten

  1. Erstellen Sie einen Cloud Storage-Bucket zum Hochladen von Bildern. Dabei sollte INPUT_BUCKET_NAME ein global eindeutiger Bucket-Name sein:

    gsutil mb gs://INPUT_BUCKET_NAME

    Der Cloud Run for Anthos-Dienst liest nur aus diesem Bucket.

  2. Erstellen Sie einen zweiten Cloud Storage-Bucket zum Ablegen unkenntlich gemachter Bilder. Dabei sollte BLURRED_BUCKET_NAME ein global eindeutiger Bucket-Name sein:

    gsutil mb gs://BLURRED_BUCKET_NAME

    Der Cloud Run for Anthos-Dienst lädt unkenntlich gemachte Bilder in diesen Bucket hoch. Durch die Verwendung eines separaten Buckets wird verhindert, dass verarbeitete Bilder den Dienst wieder auslösen.

In den folgenden Schritten erstellen Sie einen Dienst, der Benachrichtigungen über Dateiuploads in den INPUT_BUCKET_NAME verarbeitet, und stellen diesen bereit. Sie aktivieren die Benachrichtigungszustellung, nachdem Sie den Dienst bereitgestellt und getestet haben, um einen vorzeitigen Aufruf des neuen Dienstes zu vermeiden.

Beispielcode der Pub/Sub-Anleitung ändern

Diese Anleitung baut auf dem Code auf, der in der Pub/Sub-Anleitung zusammengestellt wurde. Wenn Sie diese Anleitung noch nicht abgeschlossen haben, tun Sie dies jetzt, überspringen Sie die Bereinigungsschritte und kehren Sie dann hierher zurück, um Bildverarbeitungsverhalten hinzuzufügen.

Bildverarbeitungscode hinzufügen

Der Bildverarbeitungscode ist zur besseren Lesbarkeit und zum einfacheren Testen von der Anfrageverarbeitung getrennt. So fügen Sie einen Bildverarbeitungscode hinzu:

  1. Wechseln Sie in das Verzeichnis des Beispielcodes der Pub/Sub-Anleitung.

  2. Fügen Sie Code hinzu, um die Bildverarbeitungsabhängigkeiten zu importieren, einschließlich Bibliotheken für die Integration in Google Cloud-Dienste, ImageMagick und das Dateisystem.

    Node.js

    Öffnen Sie in Ihrem Editor eine neue image.js-Datei und kopieren Sie Folgendes:
    const gm = require('gm').subClass({imageMagick: true});
    const fs = require('fs');
    const {promisify} = require('util');
    const path = require('path');
    const vision = require('@google-cloud/vision');
    
    const {Storage} = require('@google-cloud/storage');
    const storage = new Storage();
    const client = new vision.ImageAnnotatorClient();
    
    const {BLURRED_BUCKET_NAME} = process.env;

    Python

    Öffnen Sie in Ihrem Editor eine neue image.py-Datei und kopieren Sie Folgendes:
    import os
    import tempfile
    
    from google.cloud import storage, vision
    from wand.image import Image
    
    storage_client = storage.Client()
    vision_client = vision.ImageAnnotatorClient()

    Go

    Öffnen Sie in Ihrem Editor eine neue imagemagick/imagemagick.go-Datei und kopieren Sie Folgendes:
    
    // Package imagemagick contains an example of using ImageMagick to process a
    // file uploaded to Cloud Storage.
    package imagemagick
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"log"
    	"os"
    	"os/exec"
    
    	"cloud.google.com/go/storage"
    	vision "cloud.google.com/go/vision/apiv1"
    	"cloud.google.com/go/vision/v2/apiv1/visionpb"
    )
    
    // Global API clients used across function invocations.
    var (
    	storageClient *storage.Client
    	visionClient  *vision.ImageAnnotatorClient
    )
    
    func init() {
    	// Declare a separate err variable to avoid shadowing the client variables.
    	var err error
    
    	storageClient, err = storage.NewClient(context.Background())
    	if err != nil {
    		log.Fatalf("storage.NewClient: %v", err)
    	}
    
    	visionClient, err = vision.NewImageAnnotatorClient(context.Background())
    	if err != nil {
    		log.Fatalf("vision.NewAnnotatorClient: %v", err)
    	}
    }
    

    Java

    Öffnen Sie in Ihrem Editor eine neue src/main/java/com/example/cloudrun/ImageMagick.java-Datei und kopieren Sie Folgendes:
    import com.google.cloud.storage.Blob;
    import com.google.cloud.storage.BlobId;
    import com.google.cloud.storage.BlobInfo;
    import com.google.cloud.storage.Storage;
    import com.google.cloud.storage.StorageOptions;
    import com.google.cloud.vision.v1.AnnotateImageRequest;
    import com.google.cloud.vision.v1.AnnotateImageResponse;
    import com.google.cloud.vision.v1.BatchAnnotateImagesResponse;
    import com.google.cloud.vision.v1.Feature;
    import com.google.cloud.vision.v1.Feature.Type;
    import com.google.cloud.vision.v1.Image;
    import com.google.cloud.vision.v1.ImageAnnotatorClient;
    import com.google.cloud.vision.v1.ImageSource;
    import com.google.cloud.vision.v1.SafeSearchAnnotation;
    import com.google.gson.JsonObject;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    public class ImageMagick {
    
      private static final String BLURRED_BUCKET_NAME = System.getenv("BLURRED_BUCKET_NAME");
      private static Storage storage = StorageOptions.getDefaultInstance().getService();

  3. Fügen Sie Code hinzu, um eine Pub/Sub-Nachricht als Ereignisobjekt zu empfangen und die Bildverarbeitung zu steuern.

    Das Ereignis enthält Daten zum ursprünglich hochgeladenen Bild. Dieser Code bestimmt, ob das Bild unkenntlich gemacht werden muss. Die Ergebnisse einer Cloud Vision-Analyse werden dazu auf gewalttätige Inhalte oder Inhalte nur für Erwachsene überprüft.

    Node.js

    // Blurs uploaded images that are flagged as Adult or Violence.
    exports.blurOffensiveImages = async event => {
      // This event represents the triggering Cloud Storage object.
      const object = event;
    
      const file = storage.bucket(object.bucket).file(object.name);
      const filePath = `gs://${object.bucket}/${object.name}`;
    
      console.log(`Analyzing ${file.name}.`);
    
      try {
        const [result] = await client.safeSearchDetection(filePath);
        const detections = result.safeSearchAnnotation || {};
    
        if (
          // Levels are defined in https://cloud.google.com/vision/docs/reference/rest/v1/AnnotateImageResponse#likelihood
          detections.adult === 'VERY_LIKELY' ||
          detections.violence === 'VERY_LIKELY'
        ) {
          console.log(`Detected ${file.name} as inappropriate.`);
          return blurImage(file, BLURRED_BUCKET_NAME);
        } else {
          console.log(`Detected ${file.name} as OK.`);
        }
      } catch (err) {
        console.error(`Failed to analyze ${file.name}.`, err);
        throw err;
      }
    };

    Python

    def blur_offensive_images(data):
        """Blurs uploaded images that are flagged as Adult or Violence.
    
        Args:
            data: Pub/Sub message data
        """
        file_data = data
    
        file_name = file_data["name"]
        bucket_name = file_data["bucket"]
    
        blob = storage_client.bucket(bucket_name).get_blob(file_name)
        blob_uri = f"gs://{bucket_name}/{file_name}"
        blob_source = vision.Image(source=vision.ImageSource(image_uri=blob_uri))
    
        # Ignore already-blurred files
        if file_name.startswith("blurred-"):
            print(f"The image {file_name} is already blurred.")
            return
    
        print(f"Analyzing {file_name}.")
    
        result = vision_client.safe_search_detection(image=blob_source)
        detected = result.safe_search_annotation
    
        # Process image
        if detected.adult == 5 or detected.violence == 5:
            print(f"The image {file_name} was detected as inappropriate.")
            return __blur_image(blob)
        else:
            print(f"The image {file_name} was detected as OK.")
    
    

    Go

    
    // GCSEvent is the payload of a GCS event.
    type GCSEvent struct {
    	Bucket string `json:"bucket"`
    	Name   string `json:"name"`
    }
    
    // BlurOffensiveImages blurs offensive images uploaded to GCS.
    func BlurOffensiveImages(ctx context.Context, e GCSEvent) error {
    	outputBucket := os.Getenv("BLURRED_BUCKET_NAME")
    	if outputBucket == "" {
    		return errors.New("BLURRED_BUCKET_NAME must be set")
    	}
    
    	img := vision.NewImageFromURI(fmt.Sprintf("gs://%s/%s", e.Bucket, e.Name))
    
    	resp, err := visionClient.DetectSafeSearch(ctx, img, nil)
    	if err != nil {
    		return fmt.Errorf("AnnotateImage: %w", err)
    	}
    
    	if resp.GetAdult() == visionpb.Likelihood_VERY_LIKELY ||
    		resp.GetViolence() == visionpb.Likelihood_VERY_LIKELY {
    		return blur(ctx, e.Bucket, outputBucket, e.Name)
    	}
    	log.Printf("The image %q was detected as OK.", e.Name)
    	return nil
    }
    

    Java

    // Blurs uploaded images that are flagged as Adult or Violence.
    public static void blurOffensiveImages(JsonObject data) {
      String fileName = data.get("name").getAsString();
      String bucketName = data.get("bucket").getAsString();
      BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, fileName).build();
      // Construct URI to GCS bucket and file.
      String gcsPath = String.format("gs://%s/%s", bucketName, fileName);
      System.out.println(String.format("Analyzing %s", fileName));
    
      // Construct request.
      List<AnnotateImageRequest> requests = new ArrayList<>();
      ImageSource imgSource = ImageSource.newBuilder().setImageUri(gcsPath).build();
      Image img = Image.newBuilder().setSource(imgSource).build();
      Feature feature = Feature.newBuilder().setType(Type.SAFE_SEARCH_DETECTION).build();
      AnnotateImageRequest request =
          AnnotateImageRequest.newBuilder().addFeatures(feature).setImage(img).build();
      requests.add(request);
    
      // Send request to the Vision API.
      try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
        BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
        List<AnnotateImageResponse> responses = response.getResponsesList();
        for (AnnotateImageResponse res : responses) {
          if (res.hasError()) {
            System.out.println(String.format("Error: %s\n", res.getError().getMessage()));
            return;
          }
          // Get Safe Search Annotations
          SafeSearchAnnotation annotation = res.getSafeSearchAnnotation();
          if (annotation.getAdultValue() == 5 || annotation.getViolenceValue() == 5) {
            System.out.println(String.format("Detected %s as inappropriate.", fileName));
            blur(blobInfo);
          } else {
            System.out.println(String.format("Detected %s as OK.", fileName));
          }
        }
      } catch (Exception e) {
        System.out.println(String.format("Error with Vision API: %s", e.getMessage()));
      }
    }

  4. Rufen Sie das Bild, auf das verwiesen wird, aus dem oben erstellten Cloud Storage-Eingabe-Bucket auf, verwenden Sie ImageMagick, um das Bild mit einem Effekt zum Unkenntlichmachen zu transformieren, und laden Sie das Ergebnis in den Ausgabe-Bucket hoch.

    Node.js

    // Blurs the given file using ImageMagick, and uploads it to another bucket.
    const blurImage = async (file, blurredBucketName) => {
      const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
    
      // Download file from bucket.
      try {
        await file.download({destination: tempLocalPath});
    
        console.log(`Downloaded ${file.name} to ${tempLocalPath}.`);
      } catch (err) {
        throw new Error(`File download failed: ${err}`);
      }
    
      await new Promise((resolve, reject) => {
        gm(tempLocalPath)
          .blur(0, 16)
          .write(tempLocalPath, (err, stdout) => {
            if (err) {
              console.error('Failed to blur image.', err);
              reject(err);
            } else {
              console.log(`Blurred image: ${file.name}`);
              resolve(stdout);
            }
          });
      });
    
      // Upload result to a different bucket, to avoid re-triggering this function.
      const blurredBucket = storage.bucket(blurredBucketName);
    
      // Upload the Blurred image back into the bucket.
      const gcsPath = `gs://${blurredBucketName}/${file.name}`;
      try {
        await blurredBucket.upload(tempLocalPath, {destination: file.name});
        console.log(`Uploaded blurred image to: ${gcsPath}`);
      } catch (err) {
        throw new Error(`Unable to upload blurred image to ${gcsPath}: ${err}`);
      }
    
      // Delete the temporary file.
      const unlink = promisify(fs.unlink);
      return unlink(tempLocalPath);
    };

    Python

    def __blur_image(current_blob):
        """Blurs the given file using ImageMagick.
    
        Args:
            current_blob: a Cloud Storage blob
        """
        file_name = current_blob.name
        _, temp_local_filename = tempfile.mkstemp()
    
        # Download file from bucket.
        current_blob.download_to_filename(temp_local_filename)
        print(f"Image {file_name} was downloaded to {temp_local_filename}.")
    
        # Blur the image using ImageMagick.
        with Image(filename=temp_local_filename) as image:
            image.resize(*image.size, blur=16, filter="hamming")
            image.save(filename=temp_local_filename)
    
        print(f"Image {file_name} was blurred.")
    
        # Upload result to a second bucket, to avoid re-triggering the function.
        # You could instead re-upload it to the same bucket + tell your function
        # to ignore files marked as blurred (e.g. those with a "blurred" prefix)
        blur_bucket_name = os.getenv("BLURRED_BUCKET_NAME")
        blur_bucket = storage_client.bucket(blur_bucket_name)
        new_blob = blur_bucket.blob(file_name)
        new_blob.upload_from_filename(temp_local_filename)
        print(f"Blurred image uploaded to: gs://{blur_bucket_name}/{file_name}")
    
        # Delete the temporary file.
        os.remove(temp_local_filename)
    
    

    Go

    
    // blur blurs the image stored at gs://inputBucket/name and stores the result in
    // gs://outputBucket/name.
    func blur(ctx context.Context, inputBucket, outputBucket, name string) error {
    	inputBlob := storageClient.Bucket(inputBucket).Object(name)
    	r, err := inputBlob.NewReader(ctx)
    	if err != nil {
    		return fmt.Errorf("NewReader: %w", err)
    	}
    
    	outputBlob := storageClient.Bucket(outputBucket).Object(name)
    	w := outputBlob.NewWriter(ctx)
    	defer w.Close()
    
    	// Use - as input and output to use stdin and stdout.
    	cmd := exec.Command("convert", "-", "-blur", "0x8", "-")
    	cmd.Stdin = r
    	cmd.Stdout = w
    
    	if err := cmd.Run(); err != nil {
    		return fmt.Errorf("cmd.Run: %w", err)
    	}
    
    	log.Printf("Blurred image uploaded to gs://%s/%s", outputBlob.BucketName(), outputBlob.ObjectName())
    
    	return nil
    }
    

    Java

      // Blurs the file described by blobInfo using ImageMagick,
      // and uploads it to the blurred bucket.
      public static void blur(BlobInfo blobInfo) throws IOException {
        String bucketName = blobInfo.getBucket();
        String fileName = blobInfo.getName();
        // Download image
        Blob blob = storage.get(BlobId.of(bucketName, fileName));
        Path download = Paths.get("/tmp/", fileName);
        blob.downloadTo(download);
    
        // Construct the command.
        List<String> args = new ArrayList<>();
        args.add("convert");
        args.add(download.toString());
        args.add("-blur");
        args.add("0x8");
        Path upload = Paths.get("/tmp/", "blurred-" + fileName);
        args.add(upload.toString());
        try {
          ProcessBuilder pb = new ProcessBuilder(args);
          Process process = pb.start();
          process.waitFor();
        } catch (Exception e) {
          System.out.println(String.format("Error: %s", e.getMessage()));
        }
    
        // Upload image to blurred bucket.
        BlobId blurredBlobId = BlobId.of(BLURRED_BUCKET_NAME, fileName);
        BlobInfo blurredBlobInfo =
            BlobInfo.newBuilder(blurredBlobId).setContentType(blob.getContentType()).build();
        try {
          byte[] blurredFile = Files.readAllBytes(upload);
          Blob blurredBlob = storage.create(blurredBlobInfo, blurredFile);
          System.out.println(
              String.format("Blurred image uploaded to: gs://%s/%s", BLURRED_BUCKET_NAME, fileName));
        } catch (Exception e) {
          System.out.println(String.format("Error in upload: %s", e.getMessage()));
        }
    
        // Remove images from fileSystem
        Files.delete(download);
        Files.delete(upload);
      }
    }

Bildverarbeitung in den Pub/Sub-Beispielcode einbinden

So ändern Sie den vorhandenen Dienst zum Einbinden des Bildverarbeitungscodes:

  1. Fügen Sie neue Abhängigkeiten für Ihren Dienst hinzu, einschließlich der Cloud Vision- und Cloud Storage-Clientbibliotheken:

    Node.js

    npm install --save gm @google-cloud/storage @google-cloud/vision

    Python

    Fügen Sie die erforderlichen Clientbibliotheken hinzu, damit requirements.txt wie folgt aussieht:
    Flask==2.1.0
    pytest==7.0.1; python_version > "3.0"
    # pin pytest to 4.6.11 for Python2.
    pytest==7.0.1; python_version < "3.0"
    gunicorn==20.1.0
    google-cloud-vision==3.4.2
    google-cloud-storage==2.9.0
    Wand==0.6.11
    

    Go

    Die go-Beispielanwendung verwendet go-Module. Die neuen Abhängigkeiten, die oben in der Importanweisung imagemagick/imagemagick.go hinzugefügt wurden, werden automatisch mit dem nächsten Befehl heruntergeladen, der sie benötigt.

    Java

    Fügen Sie unter <dependencyManagement> im pom.xml die Abhängigkeit hinzu:
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-dependencies</artifactId>
      <version>4.5.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    
    Fügen Sie unter <dependencies> in pom.xml die folgenden Abhängigkeiten hinzu:
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    </dependency>
    

  2. Fügen Sie das ImageMagick-Systempaket zu Ihrem Container hinzu, indem Sie Dockerfile unter der Anweisung FROM ändern. Wenn Sie ein mehrstufiges Dockerfile mit mehreren Phasen verwenden, platzieren Sie dieses in der letzten Phase.

    Debian/Ubuntu
    
    # Install Imagemagick into the container image.
    # For more on system packages review the system packages tutorial.
    # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
    RUN set -ex; \
      apt-get -y update; \
      apt-get -y install imagemagick; \
      rm -rf /var/lib/apt/lists/*
    
    Alpen
    
    # Install Imagemagick into the container image.
    # For more on system packages review the system packages tutorial.
    # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
    RUN apk add --no-cache imagemagick
    

    Weitere Informationen zum Arbeiten mit Systempaketen in Ihrem Cloud Run for Anthos-Dienst finden Sie in der Anleitung zum Verwenden von Systempaketen.

  3. Ersetzen Sie den vorhandenen Pub/Sub-Nachrichtenverarbeitungscode durch einen Funktionsaufruf für unsere neue Logik zum Unkenntlichmachen.

    Node.js

    Die app.js-Datei definiert die Express.js-Anwendung und bereitet empfangene Pub/Sub-Nachrichten für die Verwendung vor. Nehmen Sie die folgenden Änderungen vor:

    • Fügen Sie Code hinzu, um die neue image.js-Datei zu importieren
    • Entfernen Sie den vorhandenen „Hello-World”-Code aus der Route
    • Fügen Sie Code hinzu, um die Pub/Sub-Nachricht weiter zu validieren
    • Fügen Sie Code hinzu, um die neue Bildverarbeitungsfunktion aufzurufen

      Wenn Sie fertig sind, sieht der Code so aus:

    
    const express = require('express');
    const app = express();
    
    // This middleware is available in Express v4.16.0 onwards
    app.use(express.json());
    
    const image = require('./image');
    
    app.post('/', async (req, res) => {
      if (!req.body) {
        const msg = 'no Pub/Sub message received';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
      if (!req.body.message || !req.body.message.data) {
        const msg = 'invalid Pub/Sub message format';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      // Decode the Pub/Sub message.
      const pubSubMessage = req.body.message;
      let data;
      try {
        data = Buffer.from(pubSubMessage.data, 'base64').toString().trim();
        data = JSON.parse(data);
      } catch (err) {
        const msg =
          'Invalid Pub/Sub message: data property is not valid base64 encoded JSON';
        console.error(`error: ${msg}: ${err}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      // Validate the message is a Cloud Storage event.
      if (!data.name || !data.bucket) {
        const msg =
          'invalid Cloud Storage notification: expected name and bucket properties';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      try {
        await image.blurOffensiveImages(data);
        res.status(204).send();
      } catch (err) {
        console.error(`error: Blurring image: ${err}`);
        res.status(500).send();
      }
    });

    Python

    Die Datei main.py definiert die Flask-Anwendung und bereitet empfangene Pub/Sub-Nachrichten für die Verwendung vor. Nehmen Sie die folgenden Änderungen vor:

    • Fügen Sie Code hinzu, um die neue image.py-Datei zu importieren
    • Entfernen Sie den vorhandenen „Hello-World”-Code aus der Route
    • Fügen Sie Code hinzu, um die Pub/Sub-Nachricht weiter zu validieren
    • Fügen Sie Code hinzu, um die neue Bildverarbeitungsfunktion aufzurufen

      Wenn Sie fertig sind, sieht der Code so aus:

    import base64
    import json
    import os
    
    from flask import Flask, request
    
    import image
    
    app = Flask(__name__)
    
    @app.route("/", methods=["POST"])
    def index():
        """Receive and parse Pub/Sub messages containing Cloud Storage event data."""
        envelope = request.get_json()
        if not envelope:
            msg = "no Pub/Sub message received"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        if not isinstance(envelope, dict) or "message" not in envelope:
            msg = "invalid Pub/Sub message format"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        # Decode the Pub/Sub message.
        pubsub_message = envelope["message"]
    
        if isinstance(pubsub_message, dict) and "data" in pubsub_message:
            try:
                data = json.loads(base64.b64decode(pubsub_message["data"]).decode())
    
            except Exception as e:
                msg = (
                    "Invalid Pub/Sub message: "
                    "data property is not valid base64 encoded JSON"
                )
                print(f"error: {e}")
                return f"Bad Request: {msg}", 400
    
            # Validate the message is a Cloud Storage event.
            if not data["name"] or not data["bucket"]:
                msg = (
                    "Invalid Cloud Storage notification: "
                    "expected name and bucket properties"
                )
                print(f"error: {msg}")
                return f"Bad Request: {msg}", 400
    
            try:
                image.blur_offensive_images(data)
                return ("", 204)
    
            except Exception as e:
                print(f"error: {e}")
                return ("", 500)
    
        return ("", 500)
    

    Go

    Die Datei main.go definiert den HTTP-Dienst und bereitet empfangene Pub/Sub-Nachrichten zur Verwendung vor. Nehmen Sie die folgenden Änderungen vor:

    • Fügen Sie Code hinzu, um die neue imagemagick.go-Datei zu importieren
    • Entfernen Sie den vorhandenen „Hello-World”-Code aus dem Handler
    • Fügen Sie Code hinzu, um die Pub/Sub-Nachricht weiter zu validieren
    • Fügen Sie Code hinzu, um die neue Bildverarbeitungsfunktion aufzurufen

    
    // Sample image-processing is a Cloud Run service which performs asynchronous processing on images.
    package main
    
    import (
    	"encoding/json"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    
    	"github.com/GoogleCloudPlatform/golang-samples/run/image-processing/imagemagick"
    )
    
    func main() {
    	http.HandleFunc("/", HelloPubSub)
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    	// Start HTTP server.
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See the documentation for more details:
    // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
    type PubSubMessage struct {
    	Message struct {
    		Data []byte `json:"data,omitempty"`
    		ID   string `json:"id"`
    	} `json:"message"`
    	Subscription string `json:"subscription"`
    }
    
    // HelloPubSub receives and processes a Pub/Sub push message.
    func HelloPubSub(w http.ResponseWriter, r *http.Request) {
    	var m PubSubMessage
    	body, err := ioutil.ReadAll(r.Body)
    	if err != nil {
    		log.Printf("ioutil.ReadAll: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    	if err := json.Unmarshal(body, &m); err != nil {
    		log.Printf("json.Unmarshal: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	var e imagemagick.GCSEvent
    	if err := json.Unmarshal(m.Message.Data, &e); err != nil {
    		log.Printf("json.Unmarshal: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	if e.Name == "" || e.Bucket == "" {
    		log.Printf("invalid GCSEvent: expected name and bucket")
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	if err := imagemagick.BlurOffensiveImages(r.Context(), e); err != nil {
    		log.Printf("imagemagick.BlurOffensiveImages: %v", err)
    		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    	}
    }
    

    Java

    Die Datei PubSubController.java definiert den Controller, der HTTP-Anfragen verarbeitet und empfangene Pub/Sub-Nachrichten für die Verwendung vorbereitet. Nehmen Sie die folgenden Änderungen vor:

    • Fügen Sie die neuen Importe hinzu
    • Entfernen Sie den vorhandenen „Hello-World”-Code vom Controller
    • Fügen Sie Code hinzu, um die Pub/Sub-Nachricht weiter zu validieren
    • Fügen Sie Code hinzu, um die neue Bildverarbeitungsfunktion aufzurufen

    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import java.util.Base64;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    // PubsubController consumes a Pub/Sub message.
    @RestController
    public class PubSubController {
      @RequestMapping(value = "/", method = RequestMethod.POST)
      public ResponseEntity<String> receiveMessage(@RequestBody Body body) {
        // Get PubSub message from request body.
        Body.Message message = body.getMessage();
        if (message == null) {
          String msg = "Bad Request: invalid Pub/Sub message format";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        // Decode the Pub/Sub message.
        String pubSubMessage = message.getData();
        JsonObject data;
        try {
          String decodedMessage = new String(Base64.getDecoder().decode(pubSubMessage));
          data = JsonParser.parseString(decodedMessage).getAsJsonObject();
        } catch (Exception e) {
          String msg = "Error: Invalid Pub/Sub message: data property is not valid base64 encoded JSON";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        // Validate the message is a Cloud Storage event.
        if (data.get("name") == null || data.get("bucket") == null) {
          String msg = "Error: Invalid Cloud Storage notification: expected name and bucket properties";
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
        }
    
        try {
          ImageMagick.blurOffensiveImages(data);
        } catch (Exception e) {
          String msg = String.format("Error: Blurring image: %s", e.getMessage());
          System.out.println(msg);
          return new ResponseEntity<>(msg, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<>(HttpStatus.OK);
      }
    }

Das vollständige Beispiel herunterladen

So rufen Sie das vollständige Codebeispiel für die Bildverarbeitung ab:

  1. Klonen Sie das Repository der Beispiel-App auf Ihren lokalen Computer:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

  2. Wechseln Sie in das Verzeichnis, das den Beispielcode für Cloud Run for Anthos enthält:

    Node.js

    cd nodejs-docs-samples/run/image-processing/

    Python

    cd python-docs-samples/run/image-processing/

    Go

    cd golang-samples/run/image-processing/

    Java

    cd java-docs-samples/run/image-processing/

Code versenden

Das Versenden von Code erfolgt in drei Schritten: ein Container-Image mit Cloud Build erstellen, in die Container Registry hochladen und in Cloud Run for Anthos bereitstellen.

So versenden Sie den Code:

  1. Erstellen Sie einen Container und veröffentlichen Sie ihn in Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und pubsub der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und pubsub der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und pubsub der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Java

    In diesem Beispiel wird Jib verwendet, um Docker-Images mit gängigen Java-Tools zu erstellen. Jib optimiert Container-Builds, ohne dass ein Dockerfile erforderlich ist oder Docker installiert sein muss. Weitere Informationen zum Erstellen von Java-Containern mit Jib

    1. Konfigurieren und erstellen Sie mit dem Dockerfile ein Basis-Image mit den installierten Systempaketen, um das Standard-Basis-Image von Jib zu überschreiben:

      # Use eclipse-temurin for base image.
      # It's important to use JDK 8u191 or above that has container support enabled.
      # https://hub.docker.com/_/eclipse-temurin/
      # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
      FROM eclipse-temurin:17.0.8_7-jre
      
      # Install Imagemagick into the container image.
      # For more on system packages review the system packages tutorial.
      # https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
      RUN set -ex; \
        apt-get -y update; \
        apt-get -y install imagemagick; \
        rm -rf /var/lib/apt/lists/*

      gcloud builds submit --tag gcr.io/PROJECT_ID/imagemagick

      Dabei ist PROJECT_ID die ID des GCP-Projekts.

    2. Erstellen Sie Ihren endgültigen Container mit Jib und veröffentlichen Sie ihn in Container Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.3.2</version>
        <configuration>
          <from>
            <image>gcr.io/PROJECT_ID/imagemagick</image>
          </from>
          <to>
            <image>gcr.io/PROJECT_ID/pubsub</image>
          </to>
        </configuration>
      </plugin>
      
      mvn compile jib:build \
        -Dimage=gcr.io/PROJECT_ID/pubsub \
        -Djib.from.image=gcr.io/PROJECT_ID/imagemagick

      Dabei ist PROJECT_ID die ID des GCP-Projekts.

  2. Führen Sie den folgenden Befehl aus, um den Dienst mit demselben Dienstnamen bereitzustellen, den Sie in der Anleitung zu Pub/Sub verwendet haben:

    Node.js

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Python

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Go

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME

    Java

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --memory 512M

    Ersetzen Sie PROJECT_ID durch Ihre GCP-Projekt-ID. pubsub ist der Containername und pubsub-tutorial der Name des Dienstes. Beachten Sie, dass das Container-Image für den Dienst und den Cluster bereitgestellt wird, den Sie zuvor unter gcloud-Standardeinstellungen einrichten konfiguriert haben.

    Ersetzen Sie BLURRED_BUCKET_NAME durch Ihren Cloud Storage-Bucket, den Sie zuvor erstellt haben, um unkenntlich gemachte Bilder zum Festlegen der Umgebungsvariablen zu erhalten.

    Warten Sie, bis die Bereitstellung abgeschlossen ist. Dies kann ungefähr eine halbe Minute dauern. Bei Erfolg wird in der Befehlszeile die Dienst-URL angezeigt.

Benachrichtigungen aus Cloud Storage aktivieren

Konfigurieren Sie Cloud Storage so, dass eine Nachricht in einem Pub/Sub-Thema immer dann veröffentlicht wird, wenn eine Datei, die als Objekt bezeichnet wird, hochgeladen oder geändert wird. Senden Sie die Benachrichtigung an das zuvor erstellte Thema, damit beim Hochladen neuer Dateien der Dienst aufgerufen wird.

gsutil notification create -t myRunTopic -f json gs://INPUT_BUCKET_NAME

Der gsutil-Befehl wird als Teil des Google Cloud CLI installiert. myRunTopic ist das Thema, das Sie in der vorherigen Anleitung erstellt haben.

Ersetzen Sie INPUT_BUCKET_NAME durch den Namen, den Sie beim Erstellen der Buckets verwendet haben.

Weitere Informationen zu Storage-Bucket-Benachrichtigungen finden Sie unter Benachrichtigungen zu Objektänderungen.

Testen

  1. Laden Sie ein anstößiges Bild hoch, z. B. dieses Bild eines fleischfressenden Zombies:

    gsutil cp zombie.jpg gs://INPUT_BUCKET_NAME

    Dabei ist INPUT_BUCKET_NAME der Cloud Storage-Bucket, den Sie zuvor zum Hochladen von Bildern erstellt haben.

  2. Rufen Sie die Dienstlogs auf:

    1. Rufen Sie in der Google Cloud Console die Seite „Cloud Run for Anthos” auf:

      Zu Cloud Run for Anthos

    2. Klicken Sie auf den Dienst pubsub-tutorial.

    3. Wählen Sie den Tab Logs aus. Es kann einige Momente dauern, bis Logs angezeigt werden. Falls sie nicht sofort angezeigt werden, warten Sie kurz ab und sehen Sie dann noch einmal nach.

  3. Suchen Sie nach der Nachricht Blurred image: zombie.png.

  4. Sie können die unkenntlich gemachten Bilder im Cloud Storage-Bucket BLURRED_BUCKET_NAME ansehen, den Sie zuvor erstellt haben. Suchen Sie ihn auf der Cloud Storage-Seite in der Google Cloud Console.

Bereinigen

Wenn Sie ein neues Projekt für diese Anleitung erstellt haben, löschen Sie das Projekt. Wenn Sie ein vorhandenes Projekt verwendet haben und es beibehalten möchten, ohne die Änderungen in dieser Anleitung hinzuzufügen, löschen Sie die für die Anleitung erstellten Ressourcen.

Projekt löschen

Am einfachsten vermeiden Sie weitere Kosten, wenn Sie das zum Ausführen der Anleitung erstellte Projekt löschen.

So löschen Sie das Projekt:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Anleitungsressourcen löschen

  1. Löschen Sie den Cloud Run for Anthos-Dienst, den Sie in dieser Anleitung bereitgestellt haben:

    gcloud run services delete SERVICE-NAME

    Dabei ist SERVICE-NAME der von Ihnen ausgewählte Dienstname.

    Sie können Cloud Run for Anthos-Dienste auch über die Google Cloud Console löschen:

    Zu Cloud Run for Anthos

  2. Entfernen Sie die gcloud-Standardkonfigurationen, die Sie während der Einrichtung der Anleitung hinzugefügt haben.

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. Entfernen Sie die Projektkonfiguration:

     gcloud config unset project
    
  4. Löschen Sie sonstige Google Cloud-Ressourcen, die in dieser Anleitung erstellt wurden:

Nächste Schritte