Tutorial memproses gambar dari Cloud Storage


Tutorial ini menunjukkan penggunaan Cloud Run, Cloud Vision API, dan ImageMagick untuk mendeteksi dan memburamkan gambar menyinggung yang diupload ke bucket Cloud Storage. Tutorial ini dibuat berdasarkan tutorial Menggunakan Pub/Sub dengan Cloud Run.

Tutorial ini membahas cara memodifikasi aplikasi contoh yang sudah ada. Anda juga dapat mendownload contoh yang sudah selesai jika ingin.

Tujuan

  • Menulis, membangun, dan men-deploy layanan pemrosesan data asinkron ke Cloud Run.
  • Memanggil layanan dengan mengupload file ke Cloud Storage dan membuat pesan Pub/Sub.
  • Gunakan Cloud Vision API untuk mendeteksi konten kekerasan atau khusus dewasa.
  • Gunakan ImageMagick untuk memburamkan gambar yang menyinggung.
  • Uji layanan dengan mengupload gambar zombie pemakan daging.

Biaya

Dalam dokumen ini, Anda menggunakan komponen Google Cloud yang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga. Pengguna baru Google Cloud mungkin memenuhi syarat untuk mendapatkan uji coba gratis.

Sebelum memulai

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

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

  4. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

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

  6. Aktifkan API Artifact Registry, Cloud Build, Pub/Sub, Cloud Run, Cloud Storage and Cloud Vision.

    Mengaktifkan API

  7. Instal dan lakukan inisialisasi gcloud CLI.
  8. Perbarui komponen:
    gcloud components update
  9. Siapkan topik Pub/Sub, langganan push yang aman, dan layanan Cloud Run awal untuk menangani pesan dengan mengikuti Tutorial menggunakan Pub/Sub

Peran yang diperlukan

Untuk mendapatkan izin yang Anda perlukan untuk menyelesaikan tutorial, minta administrator Anda untuk memberi Anda peran IAM berikut di project Anda:

Untuk mengetahui informasi selengkapnya tentang cara memberikan peran, lihat Mengelola akses ke project, folder, dan organisasi.

Anda mungkin juga bisa mendapatkan izin yang diperlukan melalui peran khusus atau peran bawaan lainnya.

Menyiapkan default gcloud

Untuk mengonfigurasi gcloud dengan setelan default untuk layanan Cloud Run Anda:

  1. Setel project default Anda:

    gcloud config set project PROJECT_ID

    Ganti PROJECT_ID dengan nama project yang Anda buat untuk tutorial ini.

  2. Konfigurasi gcloud untuk region yang Anda pilih:

    gcloud config set run/region REGION

    Ganti REGION dengan region Cloud Run pilihan Anda yang didukung.

Lokasi Cloud Run

Cloud Run bersifat regional, berarti infrastruktur yang menjalankan layanan Cloud Run Anda terletak di region tertentu dan dikelola oleh Google agar tersedia secara redundan di semua zona dalam region tersebut.

Memenuhi persyaratan latensi, ketersediaan, atau ketahanan adalah faktor utama untuk memilih region tempat layanan Cloud Run dijalankan. Pada umumnya, Anda dapat memilih region yang paling dekat dengan pengguna Anda, tetapi Anda harus mempertimbangkan lokasi dari produk Google Cloud lain yang digunakan oleh layanan Cloud Run Anda. Menggunakan produk Google Cloud secara bersamaan di beberapa lokasi dapat memengaruhi latensi serta biaya layanan Anda.

Cloud Run tersedia di region berikut:

Tergantung harga Tingkat 1

  • asia-east1 (Taiwan)
  • asia-northeast1 (Tokyo)
  • asia-northeast2 (Osaka)
  • asia-south1 (Mumbai, India)
  • europe-north1 (Finlandia) ikon daun CO2 Rendah
  • europe-southwest1 (Madrid) ikon daun CO2 Rendah
  • europe-west1 (Belgia) ikon daun CO2 Rendah
  • europe-west4 (Belanda) ikon daun CO2 Rendah
  • europe-west8 (Milan)
  • europe-west9 (Paris) ikon daun CO2 Rendah
  • me-west1 (Tel Aviv)
  • us-central1 (Iowa) ikon daun CO2 Rendah
  • us-east1 (South Carolina)
  • us-east4 (North Virginia)
  • us-east5 (Columbus)
  • us-south1 (Dallas) ikon daun CO2 Rendah
  • us-west1 (Oregon) ikon daun CO2 Rendah

Tergantung harga Tingkat 2

  • africa-south1 (Johannesburg)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seoul, Korea Selatan)
  • asia-southeast1 (Singapura)
  • asia-southeast2 (Jakarta)
  • asia-south2 (Delhi, India)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Warsawa, Polandia)
  • europe-west10 (Berlin) ikon daun CO2 Rendah
  • europe-west12 (Turin)
  • europe-west2 (London, Inggris Raya) ikon daun CO2 Rendah
  • europe-west3 (Frankfurt, Jerman) ikon daun CO2 Rendah
  • europe-west6 (Zurich, Swiss) ikon daun CO2 Rendah
  • me-central1 (Doha)
  • me-central2 (Dammam)
  • northamerica-northeast1 (Montreal) ikon daun CO2 Rendah
  • northamerica-northeast2 (Toronto) ikon daun CO2 Rendah
  • southamerica-east1 (São Paulo, Brasil) ikon daun CO2 Rendah
  • southamerica-west1 (Santiago, Cile) ikon daun CO2 Rendah
  • us-west2 (Los Angeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Jika sudah membuat layanan Cloud Run, Anda dapat melihat region di dasbor Cloud Run di Konsol Google Cloud.

Memahami urutan operasi

Alur data dalam tutorial ini mengikuti langkah-langkah berikut:

  1. Pengguna mengupload gambar ke bucket Cloud Storage.
  2. Cloud Storage memublikasikan pesan tentang file baru ke Pub/Sub.
  3. Pub/Sub mengirim pesan ke layanan Cloud Run.
  4. Layanan Cloud Run mengambil file gambar yang direferensikan dalam pesan Pub/Sub.
  5. Layanan Cloud Run menggunakan Cloud Vision API untuk menganalisis gambar.
  6. Jika konten kekerasan atau khusus dewasa terdeteksi, layanan Cloud Run akan menggunakan ImageMagick untuk memburamkan gambar.
  7. Layanan Cloud Run mengupload gambar yang diburamkan ke bucket Cloud Storage lain untuk digunakan.

Penggunaan gambar yang diburamkan berikutnya akan dibiarkan sebagai latihan bagi pembaca.

Membuat repositori standar Artifact Registry

Buat repositori standar Artifact Registry untuk menyimpan image container Anda:

gcloud artifacts repositories create REPOSITORY \
    --repository-format=docker \
    --location=REGION

Ganti:

  • REPOSITORY dengan nama unik untuk repositori.
  • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.

Menyiapkan bucket Cloud Storage

gcloud

  1. Buat bucket Cloud Storage untuk mengupload gambar, dengan INPUT_BUCKET_NAME nama bucket yang unik secara global:

    gcloud storage buckets create gs://INPUT_BUCKET_NAME

    Layanan Cloud Run hanya membaca dari bucket ini.

  2. Buat bucket Cloud Storage kedua untuk menerima gambar yang diburamkan, dengan BLURRED_BUCKET_NAME nama bucket yang unik secara global:

    gcloud storage buckets create gs://BLURRED_BUCKET_NAME

    Layanan Cloud Run mengupload gambar yang diburamkan ke bucket ini. Menggunakan bucket terpisah akan mencegah gambar yang diproses memicu kembali layanan.

    Secara default, revisi Cloud Run dijalankan sebagai akun layanan default Compute Engine.

    Sebaliknya, jika Anda menggunakan akun layanan yang dikelola pengguna, pastikan Anda telah menetapkan peran IAM yang diperlukan sehingga akun tersebut memiliki izin storage.objects.get untuk membaca dari INPUT_BUCKET_NAME dan izin storage.objects.create untuk mengupload ke BLURRED_BUCKET_NAME.

Terraform

Untuk mempelajari cara menerapkan atau menghapus konfigurasi Terraform, lihat Perintah dasar Terraform.

Membuat dua bucket Cloud Storage: satu untuk mengupload gambar asli dan satu lagi layanan Cloud Run untuk mengupload gambar yang diburamkan.

Untuk membuat kedua bucket Cloud Storage dengan nama yang unik secara global, tambahkan kode berikut ke file main.tf yang sudah ada:

resource "random_id" "bucket_suffix" {
  byte_length = 8
}

resource "google_storage_bucket" "imageproc_input" {
  name     = "input-bucket-${random_id.bucket_suffix.hex}"
  location = "us-central1"
}

output "input_bucket_name" {
  value = google_storage_bucket.imageproc_input.name
}

resource "google_storage_bucket" "imageproc_output" {
  name     = "output-bucket-${random_id.bucket_suffix.hex}"
  location = "us-central1"
}

output "blurred_bucket_name" {
  value = google_storage_bucket.imageproc_output.name
}

Secara default, revisi Cloud Run dijalankan sebagai akun layanan default Compute Engine.

Sebaliknya, jika Anda menggunakan akun layanan yang dikelola pengguna, pastikan Anda telah menetapkan peran IAM yang diperlukan sehingga akun tersebut memiliki izin storage.objects.get untuk membaca dari google_storage_bucket.imageproc_input dan izin storage.objects.create untuk mengupload ke google_storage_bucket.imageproc_output.

Pada langkah berikut, Anda akan membuat dan men-deploy layanan yang memproses notifikasi upload file ke INPUT_BUCKET_NAME. Anda harus mengaktifkan pengiriman notifikasi setelah men-deploy dan menguji layanan, untuk menghindari pemanggilan layanan baru secara dini.

Mengubah kode contoh tutorial Pub/Sub

Tutorial ini dibuat berdasarkan kode yang disusun dalam Tutorial Menggunakan Pub/Sub. Jika Anda belum menyelesaikan tutorial tersebut, lakukan sekarang dan lewati langkah-langkah pembersihan, lalu kembali ke sini untuk menambahkan aktivitas pemrosesan gambar.

Menambahkan kode pemrosesan gambar

Kode pemrosesan gambar dipisahkan dari penanganan permintaan untuk keterbacaan dan kemudahan pengujian. Untuk menambahkan kode pemrosesan gambar:

  1. Ubah ke direktori kode contoh tutorial Pub/Sub.

  2. Tambahkan kode untuk mengimpor dependensi pemrosesan gambar, termasuk library untuk diintegrasikan dengan layanan Google Cloud, ImageMagick, dan sistem file.

    Node.js

    Buka file image.js baru di editor Anda, lalu salin di hal berikut:
    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

    Buka file image.py baru di editor Anda, lalu salin di hal berikut:
    import os
    import tempfile
    
    from google.cloud import storage, vision
    from wand.image import Image
    
    storage_client = storage.Client()
    vision_client = vision.ImageAnnotatorClient()

    Go

    Buka file imagemagick/imagemagick.go baru di editor Anda, dan salin hal berikut:
    
    // 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

    Buka file src/main/java/com/example/cloudrun/ImageMagick.java baru di editor Anda, lalu salin di hal berikut:
    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. Tambahkan kode untuk menerima pesan Pub/Sub sebagai objek peristiwa dan mengontrol pemrosesan gambar.

    Peristiwa berisi data tentang gambar yang pertama kali diupload. Kode ini menentukan apakah gambar perlu diburamkan dengan memeriksa hasil analisis Cloud Vision untuk menemukan konten kekerasan atau khusus dewasa.

    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. Ambil gambar yang direferensikan dari bucket input Cloud Storage yang dibuat di atas, gunakan ImageMagick untuk mengubah gambar dengan efek blur, dan upload hasilnya ke bucket output.

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

Mengintegrasikan pemrosesan gambar ke dalam kode contoh Pub/Sub

Untuk memodifikasi layanan yang ada guna memasukkan kode pemrosesan gambar:

  1. Tambahkan dependensi baru untuk layanan Anda, termasuk library klien Cloud Storage dan Cloud Vision.

    Node.js

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

    Python

    Tambahkan library klien yang diperlukan sehingga milik requirements.txt Anda akan terlihat seperti ini:
    Flask==3.0.3
    google-cloud-storage==2.12.0
    google-cloud-vision==3.4.5
    gunicorn==22.0.0
    Wand==0.6.13
    Werkzeug==3.0.3
    

    Go

    Contoh aplikasi go menggunakan modul go, dependensi baru yang ditambahkan di atas dalam pernyataan impor imagemagick/imagemagick.go akan secara otomatis didownload oleh perintah berikutnya yang membutuhkan.

    Java

    Tambahkan dependensi berikut <dependencyManagement> di bagian pom.xml:
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-dependencies</artifactId>
      <version>4.9.2</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    
    Tambahkan dependensi berikut <dependencies> di bagian pom.xml:
    <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. Tambahkan paket sistem ImageMagick ke container Anda dengan mengubah Dockerfile pernyataan FROM di bawah. Jika menggunakan Dockerfile "multi-tahap", tempatkan di tahap terakhir.

    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/*
    
    Alpine
    
    # 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
    

    Baca selengkapnya tentang cara bekerja dengan paket sistem di layanan Cloud Run dalam Tutorial menggunakan paket sistem.

  3. Mengganti kode penanganan pesan Pub/Sub yang ada dengan panggilan fungsi ke logika pemburaman baru.

    Node.js

    File app.js menentukan aplikasi Express.js dan menyiapkan pesan Pub/Sub yang diterima untuk digunakan. Buat perubahan berikut:

    • Tambahkan kode untuk mengimpor file image.js baru
    • Hapus kode "Hello World" yang ada dari rute
    • Tambahkan kode untuk memvalidasi pesan Pub/Sub lebih lanjut
    • Menambahkan kode untuk memanggil fungsi pemrosesan gambar baru

      Setelah selesai, kodenya akan terlihat seperti ini:

    
    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

    File main.py menentukan aplikasi Flask dan menyiapkan pesan Pub/Sub yang diterima untuk digunakan. Buat perubahan berikut:

    • Tambahkan kode untuk mengimpor file image.py baru
    • Hapus kode "Hello World" yang ada dari rute
    • Tambahkan kode untuk memvalidasi pesan Pub/Sub lebih lanjut
    • Menambahkan kode untuk memanggil fungsi pemrosesan gambar baru

      Setelah selesai, kodenya akan terlihat seperti ini:

    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

    File main.go menentukan layanan HTTP dan menyiapkan pesan Pub/Sub yang diterima untuk digunakan. Buat perubahan berikut:

    • Tambahkan kode untuk mengimpor file imagemagick.go baru
    • Hapus kode "Hello World" yang ada dari pengendali
    • Tambahkan kode untuk memvalidasi pesan Pub/Sub lebih lanjut
    • Menambahkan kode untuk memanggil fungsi pemrosesan gambar baru

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

    File PubSubController.java menentukan pengontrol yang menangani permintaan HTTP dan menyiapkan pesan Pub/Sub yang diterima untuk digunakan. Buat perubahan berikut:

    • Menambahkan impor baru
    • Hapus kode "Hello World" yang ada dari pengontrol
    • Tambahkan kode untuk memvalidasi pesan Pub/Sub lebih lanjut
    • Menambahkan kode untuk memanggil fungsi pemrosesan gambar baru

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

Mendownload contoh lengkap

Untuk mengambil contoh kode Pemrosesan Gambar lengkap untuk digunakan:

  1. Clone repositori aplikasi contoh ke mesin lokal Anda:

    Node.js

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Python

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Go

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Java

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

    Sebagai alternatif, anda dapat mendownload contoh seperti file zip dan mengekstraknya.

  2. Ubah ke direktori yang berisikan kode contoh Cloud Run:

    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/

Mengirimkan kode

Kode pengiriman terdiri dari tiga langkah: mem-build image container dengan Cloud Build, mengupload image container ke Artifact Registry, dan men-deploy image container ke Cloud Run.

Untuk kode pengiriman Anda:

  1. Build container Anda dan publikasikan di Artifact Registry:

    Node.js

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub

    Dengan pubsub adalah nama layanan Anda.

    Ganti:

    • PROJECT_ID dengan project ID Google Cloud Anda
    • REPOSITORY dengan nama repositori Artifact Registry.
    • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Artifact Registry dan dapat digunakan kembali jika diperlukan.

    Python

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub

    Dengan pubsub adalah nama layanan Anda.

    Ganti:

    • PROJECT_ID dengan project ID Google Cloud Anda
    • REPOSITORY dengan nama repositori Artifact Registry.
    • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Artifact Registry dan dapat digunakan kembali jika diperlukan.

    Go

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub

    Dengan pubsub adalah nama layanan Anda.

    Ganti:

    • PROJECT_ID dengan project ID Google Cloud Anda
    • REPOSITORY dengan nama repositori Artifact Registry.
    • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Artifact Registry dan dapat digunakan kembali jika diperlukan.

    Java

    Contoh ini menggunakan Jib untuk mem-build image Docker menggunakan alat Java umum. Jib mengoptimalkan build container tanpa memerlukan Dockerfile atau menginstal Docker. Pelajari lebih lanjut cara mem-build container Java dengan Jib.

    1. Dengan Dockerfile, konfigurasikan dan build image dasar menggunakan paket sistem yang diinstal untuk menggantikan image dasar default Jib:

      # 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.12_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 REGION-docker.pkg.dev/PROJECT_ID
      /REPOSITORY/imagemagick

      Ganti:

      • PROJECT_ID dengan project ID Google Cloud Anda
      • REPOSITORY dengan nama repositori Artifact Registry.
      • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.
    2. Gunakan helper kredensial gcloud untuk memberi otorisasi Docker agar dikirim ke Artifact Registry Anda.

      gcloud auth configure-docker

    3. Build container akhir Anda dengan Jib dan publikasikan di Artifact Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.4.0</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=REGION-docker.pkg.dev/PROJECT_ID
      /REPOSITORY/pubsub \
        -Djib.from.image=REGION-docker.pkg.dev/PROJECT_ID
      /REPOSITORY/imagemagick

      Ganti:

      • PROJECT_ID dengan project ID Google Cloud Anda
      • REPOSITORY dengan nama repositori Artifact Registry.
      • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.

  2. Jalankan perintah berikut untuk men-deploy layanan Anda, menggunakan nama layanan yang sama dengan yang digunakan dalam Tutorial Menggunakan Pub/Sub:

    Node.js

    gcloud run deploy pubsub-tutorial --image REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --no-allow-unauthenticated

    Python

    gcloud run deploy pubsub-tutorial --image REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --no-allow-unauthenticated

    Go

    gcloud run deploy pubsub-tutorial --image REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --no-allow-unauthenticated

    Java

    gcloud run deploy pubsub-tutorial --image REGION-docker.pkg.dev/PROJECT_ID
    /REPOSITORY/pubsub --set-env-vars=BLURRED_BUCKET_NAME=BLURRED_BUCKET_NAME --memory 512M --no-allow-unauthenticated

    Dengan pubsub adalah nama penampung dan pubsub-tutorial adalah nama layanan. Perhatikan bahwa image container di-deploy ke layanan dan region (Cloud Run) yang Anda konfigurasi sebelumnya di bagian Menyiapkan default gcloud. Ganti:

    • PROJECT_ID dengan project ID Google Cloud Anda
    • REPOSITORY dengan nama repositori Artifact Registry.
    • REGION dengan region Google Cloud yang akan digunakan untuk repositori Artifact Registry.
    • BLURRED_BUCKET_NAME dengan bucket Cloud Storage yang Anda buat sebelumnya untuk menerima gambar yang diburamkan untuk menetapkan variabel lingkungan.

    Flag --no-allow-unauthenticated membatasi akses yang tidak diautentikasi ke layanan. Dengan menjaga privasi layanan, Anda dapat mengandalkan integrasi Pub/Sub otomatis Cloud Run untuk mengautentikasi permintaan. Lihat Mengintegrasikan dengan Pub/Sub untuk mengetahui detail selengkapnya tentang cara konfigurasi ini. Baca bagian Mengelola Akses untuk mengetahui detail lebih lanjut tentang autentikasi berbasis IAM.

    Tunggu hingga deployment selesai: proses ini memerlukan waktu sekitar setengah menit. Jika berhasil, command line akan menampilkan URL layanan.

Mengaktifkan notifikasi dari Cloud Storage

Konfigurasi Cloud Storage untuk memublikasikan pesan ke topik Pub/Sub setiap kali file (dikenal sebagai objek) diupload atau diubah. Kirim notifikasi ke topik yang dibuat sebelumnya sehingga setiap upload file baru akan memanggil layanan.

gcloud

gcloud storage service-agent --project=PROJECT_ID
gcloud storage buckets notifications create gs://INPUT_BUCKET_NAME --topic=myRunTopic --payload-format=json

myRunTopic adalah topik yang Anda buat di tutorial sebelumnya.

Ganti INPUT_BUCKET_NAME dengan nama yang Anda gunakan saat membuat bucket.

Untuk detail selengkapnya tentang notifikasi bucket penyimpanan, baca notifikasi perubahan objek.

Terraform

Untuk mempelajari cara menerapkan atau menghapus konfigurasi Terraform, lihat Perintah dasar Terraform.

Untuk mengaktifkan notifikasi, akun layanan Cloud Storage yang unik ke project harus ada dan memiliki izin IAM pubsub.publisher pada topik Pub/Sub. Untuk memberikan izin ini dan membuat notifikasi Cloud Storage, tambahkan kode berikut ke file main.tf yang sudah ada:

data "google_storage_project_service_account" "gcs_account" {}

resource "google_pubsub_topic_iam_binding" "binding" {
  topic   = google_pubsub_topic.default.name
  role    = "roles/pubsub.publisher"
  members = ["serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"]
}

resource "google_storage_notification" "notification" {
  bucket         = google_storage_bucket.imageproc_input.name
  payload_format = "JSON_API_V1"
  topic          = google_pubsub_topic.default.id
  depends_on     = [google_pubsub_topic_iam_binding.binding]
}

Cobalah

  1. Upload gambar yang menyinggung, seperti gambar zombie pemakan daging ini:

    curl -o zombie.jpg https://cdn.pixabay.com/photo/2015/09/21/14/24/zombie-949916_960_720.jpg
    gcloud storage cp zombie.jpg gs://INPUT_BUCKET_NAME

    pada INPUT_BUCKET_NAME bucket Cloud Storage yang Anda buat sebelumnya untuk mengupload gambar.

  2. Buka log layanan:

    1. Buka halaman Cloud Run di Google Cloud Console
    2. Klik layanan pubsub-tutorial.
    3. Pilih tab Logs. Log mungkin memerlukan waktu beberapa saat untuk ditampilkan. Jika Anda tidak segera melihatnya, periksa lagi setelah beberapa saat.
  3. Cari pesan Blurred image: zombie.png.

  4. Anda dapat melihat gambar yang diburamkan di bucket Cloud Storage BLURRED_BUCKET_NAME yang Anda buat sebelumnya: cari bucket di halaman Cloud Storage di Google Cloud Console

Pembersihan

Jika Anda membuat project baru untuk tutorial ini, hapus project tersebut. Jika Anda menggunakan project yang ada dan ingin mempertahankannya tanpa ada perubahan yang ditambahkan dalam tutorial ini, hapus resource yang dibuat untuk tutorial.

Menghapus project

Cara termudah untuk menghilangkan penagihan adalah dengan menghapus project yang Anda buat untuk tutorial.

Untuk menghapus project:

  1. Di konsol Google Cloud, buka halaman Manage resource.

    Buka Manage resource

  2. Pada daftar project, pilih project yang ingin Anda hapus, lalu klik Delete.
  3. Pada dialog, ketik project ID, lalu klik Shut down untuk menghapus project.

Menghapus resource tutorial

  1. Hapus layanan Cloud Run yang Anda deploy dalam tutorial ini:

    gcloud run services delete SERVICE-NAME

    Dengan SERVICE-NAME adalah nama layanan pilihan Anda.

    Anda juga dapat menghapus layanan Cloud Run dari Konsol Google Cloud.

  2. Hapus konfigurasi region default gcloud yang Anda tambahkan selama penyiapan tutorial:

     gcloud config unset run/region
    
  3. Hapus konfigurasi project:

     gcloud config unset project
    
  4. Hapus resource Google Cloud lain yang dibuat dalam tutorial ini:

Langkah selanjutnya

  • Pelajari lebih lanjut cara mempertahankan data dengan Cloud Run menggunakan Cloud Storage.
  • Pahami cara menggunakan Cloud Vision API untuk mendeteksi hal-hal selain konten vulgar.
  • Pelajari arsitektur referensi, diagram, dan praktik terbaik tentang Google Cloud. Lihat Cloud Architecture Center kami.