Pic-a-daily: Simpan dan Analisis Gambar dengan Library Klien Java Native Google

1. Ringkasan

Di lab kode pertama, Anda akan menyimpan gambar dalam bucket. Tindakan ini akan menghasilkan peristiwa pembuatan file yang akan ditangani oleh layanan yang di-deploy di Cloud Run. Layanan ini akan melakukan panggilan ke Vision API untuk melakukan analisis gambar dan menyimpan hasilnya di datastore.

427de3100de3a61e.png

Yang akan Anda pelajari

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

2. Penyiapan dan Persyaratan

Penyiapan lingkungan mandiri

  1. Login ke Google Cloud Console dan buat project baru atau gunakan kembali project yang sudah ada. Jika belum memiliki akun Gmail atau Google Workspace, Anda harus membuatnya.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Project name adalah nama tampilan untuk peserta project ini. String ini adalah string karakter yang tidak digunakan oleh Google API. Anda dapat memperbaruinya kapan saja.
  • Project ID harus unik di semua project Google Cloud dan tidak dapat diubah (tidak dapat diubah setelah ditetapkan). Cloud Console otomatis membuat string unik; biasanya Anda tidak mementingkan kata-katanya. Di sebagian besar codelab, Anda harus merujuk Project ID-nya (biasanya diidentifikasi sebagai PROJECT_ID). Jika tidak suka dengan ID yang dibuat, Anda dapat membuat ID acak lainnya. Atau, Anda dapat mencobanya sendiri dan melihat apakah ID tersebut tersedia. ID tidak dapat diubah setelah langkah ini dan akan tetap ada selama durasi project.
  • Sebagai informasi, ada nilai ketiga, Project Number yang digunakan oleh beberapa API. Pelajari lebih lanjut ketiga nilai ini di dokumentasi.
  1. Selanjutnya, Anda harus mengaktifkan penagihan di Konsol Cloud untuk menggunakan resource/API Cloud. Menjalankan operasi dalam codelab ini seharusnya tidak memerlukan banyak biaya, bahkan mungkin tidak sama sekali. Guna mematikan resource agar tidak menimbulkan penagihan di luar tutorial ini, Anda dapat menghapus resource yang dibuat atau menghapus seluruh project. Pengguna baru Google Cloud memenuhi syarat untuk mengikuti program Uji Coba Gratis senilai $300 USD.

Mulai Cloud Shell

Meskipun Google Cloud dapat dioperasikan dari jarak jauh menggunakan laptop Anda, dalam codelab ini, Anda akan menggunakan Google Cloud Shell, lingkungan command line yang berjalan di Cloud.

Dari Google Cloud Console, klik ikon Cloud Shell di toolbar kanan atas:

55efc1aaa7a4d3ad.png

Hanya perlu waktu beberapa saat untuk penyediaan dan terhubung ke lingkungan. Jika sudah selesai, Anda akan melihat tampilan seperti ini:

7ffe5cbb04455448.png

Mesin virtual ini berisi semua alat pengembangan yang Anda perlukan. Layanan ini menawarkan direktori beranda tetap sebesar 5 GB dan beroperasi di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Semua pekerjaan Anda dalam codelab ini dapat dilakukan di browser. Anda tidak perlu menginstal apa pun.

3. Mengaktifkan API

Untuk lab ini, Anda akan menggunakan Cloud Functions dan Vision API, tetapi keduanya harus diaktifkan terlebih dahulu di Cloud Console atau dengan gcloud.

Untuk mengaktifkan Vision API di Konsol Cloud, telusuri Cloud Vision API di kotak penelusuran:

cf48b1747ba6a6fb.png

Anda akan diarahkan ke halaman Cloud Vision API:

ba4af419e6086fbb.png

Klik tombol ENABLE.

Atau, Anda juga dapat mengaktifkannya di Cloud Shell menggunakan alat command line gcloud.

Di dalam Cloud Shell, jalankan perintah berikut:

gcloud services enable vision.googleapis.com

Anda akan melihat operasi selesai dengan berhasil:

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

Aktifkan Cloud Run dan Cloud Build juga:

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

4. Buat bucket (konsol)

Buat bucket penyimpanan untuk gambar. Anda dapat melakukannya dari konsol Google Cloud Platform ( console.cloud.google.com) atau dengan alat command line gsutil dari Cloud Shell atau lingkungan pengembangan lokal Anda.

Dari menu "tiga garis" (☰), buka halaman Storage.

1930e055d138150a.png

Beri nama bucket Anda

Klik tombol CREATE BUCKET.

34147939358517f8.png

Klik CONTINUE.

Pilih Lokasi

197817f20be07678.png

Buat bucket multi-regional di region pilihan Anda (di sini Europe).

Klik CONTINUE.

Pilih kelas penyimpanan default

53cd91441c8caf0e.png

Pilih kelas penyimpanan Standard untuk data Anda.

Klik CONTINUE.

Menetapkan Kontrol Akses

8c2b3b459d934a51.png

Karena Anda akan menggunakan gambar yang dapat diakses secara publik, Anda ingin semua gambar kita yang disimpan di bucket ini memiliki kontrol akses seragam yang sama.

Pilih opsi kontrol akses Uniform.

Klik CONTINUE.

Menetapkan Perlindungan/Enkripsi

d931c24c3e705a68.png

Tetap menggunakan default (Google-managed key), karena Anda tidak akan menggunakan kunci enkripsi Anda sendiri.

Klik CREATE, untuk menyelesaikan pembuatan bucket.

Menambahkan allUsers sebagai pelihat penyimpanan

Buka tab Permissions:

d0ecfdcff730ea51.png

Tambahkan anggota allUsers ke bucket, dengan peran Storage > Storage Object Viewer, sebagai berikut:

e9f25ec1ea0b6cc6.png

Klik SAVE.

5. Buat bucket (gsutil)

Anda juga dapat menggunakan alat command line gsutil di Cloud Shell untuk membuat bucket.

Di Cloud Shell, tetapkan variabel untuk nama bucket unik. Cloud Shell sudah menyetel GOOGLE_CLOUD_PROJECT ke project ID unik Anda. Anda dapat menambahkannya ke nama bucket.

Contoh:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Buat zona multi-region standar di Eropa:

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

Pastikan akses level bucket yang seragam:

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

Setel bucket untuk publik.

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

Jika Anda membuka bagian Cloud Storage konsol, Anda akan memiliki bucket uploaded-pictures publik:

a98ed4ba17873e40.png

Uji apakah Anda dapat mengupload gambar ke bucket dan gambar yang diupload tersedia secara publik, seperti yang dijelaskan pada langkah sebelumnya.

6. Menguji akses publik ke bucket

Kembali ke browser penyimpanan, Anda akan melihat bucket Anda dalam daftar, dengan akses "Publik" (termasuk tanda peringatan yang mengingatkan Anda bahwa siapa pun memiliki akses ke konten bucket tersebut).

89e7a4d2c80a0319.png

Bucket Anda sekarang siap menerima gambar.

Jika mengklik nama bucket, Anda akan melihat detail bucket.

131387f12d3eb2d3.png

Di sana, Anda dapat mencoba tombol Upload files untuk menguji apakah Anda dapat menambahkan gambar ke bucket. Pop-up pemilih file akan meminta Anda memilih file. Setelah dipilih, file akan diupload ke bucket Anda, dan Anda akan melihat kembali akses public yang telah otomatis diberikan ke file baru ini.

e87584471a6e9c6d.png

Di samping label akses Public, Anda juga akan melihat ikon link kecil. Saat mengkliknya, browser Anda akan membuka URL publik gambar tersebut, yang akan berbentuk:

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

Dengan BUCKET_NAME adalah nama unik secara global yang telah Anda pilih untuk bucket, lalu nama file gambar Anda.

Dengan mencentang kotak di samping nama gambar, tombol DELETE akan diaktifkan, dan Anda dapat menghapus gambar pertama ini.

7. Menyiapkan database

Anda akan menyimpan informasi tentang gambar yang diberikan oleh Vision API ke database Cloud Firestore, yaitu database dokumen NoSQL yang cepat, terkelola sepenuhnya, tanpa server, dan cloud-native. Siapkan database Anda dengan membuka bagian Firestore di Konsol Cloud:

9e4708d2257de058.png

Dua opsi ditawarkan: Native mode atau Datastore mode. Gunakan mode native, yang menawarkan fitur tambahan seperti dukungan offline dan sinkronisasi real-time.

Klik SELECT NATIVE MODE.

9449ace8cc84de43.png

Pilih multi-region (di sini di Eropa, tetapi idealnya setidaknya region yang sama dengan fungsi dan bucket penyimpanan Anda).

Klik tombol CREATE DATABASE.

Setelah database dibuat, Anda akan melihat hal berikut:

56265949a124819e.png

Buat koleksi baru dengan mengklik tombol + START COLLECTION.

Beri nama koleksi pictures.

75806ee24c4e13a7.png

Anda tidak perlu membuat dokumen. Anda akan menambahkannya secara terprogram saat gambar baru disimpan di Cloud Storage dan dianalisis oleh Vision API.

Klik Save.

Firestore membuat dokumen default pertama dalam koleksi yang baru dibuat. Anda dapat menghapus dokumen tersebut dengan aman karena tidak berisi informasi yang berguna:

5c2f1e17ea47f48f.png

Dokumen yang akan dibuat secara terprogram dalam koleksi kita akan berisi 4 kolom:

  • name (string): nama file gambar yang diupload, yang juga merupakan kunci dokumen
  • labels (array string): label item yang dikenali oleh Vision API
  • color (string): kode warna heksadesimal dari warna dominan (yaitu. #ab12ef)
  • created (tanggal): stempel waktu saat metadata gambar ini disimpan
  • thumbnail (boolean): kolom opsional yang akan ada dan bernilai benar jika gambar thumbnail telah dibuat untuk gambar ini

Karena kita akan menelusuri di Firestore untuk menemukan gambar yang memiliki thumbnail, dan mengurutkannya berdasarkan tanggal pembuatan, kita perlu membuat indeks penelusuran.

Anda dapat membuat indeks dengan perintah berikut di Cloud Shell:

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

Atau, Anda juga dapat melakukannya dari Konsol Cloud, dengan mengklik Indexes di kolom navigasi di sebelah kiri, lalu membuat indeks gabungan seperti yang ditunjukkan di bawah:

ecb8b95e3c791272.png

Klik Create. Pembuatan indeks dapat memerlukan waktu beberapa menit.

8. Buat clone kode

Lakukan clone kode, jika Anda belum melakukannya di lab kode sebelumnya:

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

Kemudian, Anda dapat membuka direktori yang berisi layanan untuk mulai membangun lab:

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

Anda akan memiliki tata letak file berikut untuk layanan:

f79613aff479d8ad.png

9. Mempelajari kode layanan

Anda akan memulai dengan melihat cara mengaktifkan Library Klien Java di pom.xml menggunakan BOM:

Pertama, edit file pom.xml yang mencantumkan dependensi fungsi Java kita. Perbarui kode untuk menambahkan dependensi Maven Cloud Vision API:

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

Fungsi ini diimplementasikan di class EventController. Setiap kali gambar baru diupload ke bucket, layanan akan menerima notifikasi untuk diproses:

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

Kode akan melanjutkan untuk memvalidasi header Cloud Events:

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

Permintaan kini dapat dibuat dan kode akan menyiapkan satu permintaan tersebut untuk dikirim ke Vision API:

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

Kami meminta 3 kemampuan utama Vision API:

  • Deteksi label: untuk memahami konten dalam gambar tersebut
  • Properti gambar: untuk memberikan atribut menarik dari gambar (kita tertarik dengan warna dominan gambar)
  • Penelusuran aman: untuk mengetahui apakah gambar aman untuk ditampilkan (tidak boleh berisi konten dewasa / medis / tidak pantas / kekerasan)

Pada tahap ini, kita dapat melakukan panggilan ke Vision API:

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

Sebagai referensi, berikut tampilan respons dari Vision API:

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

Jika tidak ada error yang ditampilkan, kita dapat melanjutkan, oleh karena itu kita memiliki blok if ini:

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

Kita akan mendapatkan label benda, kategori, atau tema yang dikenali dalam gambar:

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

Kami ingin mengetahui warna dominan gambar:

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

Mari kita periksa apakah gambar tersebut aman untuk ditampilkan:

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

Kami memeriksa karakteristik dewasa / pemalsuan / medis / kekerasan / tidak senonoh untuk melihat apakah karakteristik tersebut kemungkinan atau sangat mungkin tidak ada.

Jika hasil penelusuran aman tidak masalah, kita dapat menyimpan metadata di Firestore:

// 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. Membangun Image Aplikasi dengan GraalVM (opsional)

Pada langkah opsional ini, Anda akan membangun JIT(JVM) based app image, lalu AOT(Native) Java app image, menggunakan GraalVM.

Untuk menjalankan build, Anda harus memastikan bahwa Anda telah menginstal dan mengonfigurasi JDK yang sesuai dan builder native-image. Ada beberapa opsi yang tersedia.

To start, download GraalVM 22.2.x Community Edition dan ikuti petunjuk di halaman penginstalan GraalVM.

Proses ini dapat disederhanakan dengan bantuan SDKMAN!

Untuk menginstal distribusi JDK yang sesuai dengan SDKman, mulailah dengan menggunakan perintah penginstalan:

sdk install java 22.2.r17-grl

Instruksikan SDKman untuk menggunakan versi ini, baik untuk build JIT maupun AOT:

sdk use java 22.2.0.r17-grl

Instal native-image utility untuk GraalVM:

gu install native-image

Di Cloudshell, untuk memudahkan Anda, Anda dapat menginstal GraalVM dan utilitas native-image dengan perintah sederhana berikut:

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

Pertama, tetapkan variabel lingkungan project GCP:

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

Kemudian, Anda dapat membuka direktori yang berisi layanan untuk mulai membangun lab:

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

Bangun image aplikasi JIT(JVM):

./mvnw package -Pjvm

Amati log build di terminal:

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

Bangun image AOT(Native):.

./mvnw package -Pnative -DskipTests

Amati log build di terminal, termasuk log build image native:

Perhatikan bahwa proses build memerlukan waktu yang cukup lama, bergantung pada mesin yang Anda gunakan untuk pengujian.

...
[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. Membangun dan Memublikasikan Image Container

Mari kita buat image container dalam dua versi yang berbeda: satu sebagai JIT(JVM) image dan yang lainnya sebagai AOT(Native) Java image.

Pertama, tetapkan variabel lingkungan project GCP:

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

Buat image JIT(JVM):.

./mvnw package -Pjvm-image

Amati log build di terminal:

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

Bangun image AOT(Native):.

./mvnw package -Pnative-image

Amati log build di terminal, termasuk log build image native dan kompresi image menggunakan UPX.

Perhatikan bahwa proses build memerlukan waktu yang cukup lama, bergantung pada perangkat yang Anda gunakan untuk pengujian

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

Validasi bahwa image telah dibuat:

docker images | grep image-analysis

Beri tag dan kirim kedua gambar ke 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. Men-deploy ke Cloud Run

Saatnya men-deploy layanan.

Anda akan men-deploy layanan dua kali, sekali menggunakan image JIT(JVM) dan yang kedua menggunakan image AOT(Native). Kedua deployment layanan akan memproses gambar yang sama dari bucket secara paralel, untuk tujuan perbandingan.

Pertama, tetapkan variabel lingkungan project GCP:

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

Deploy image JIT(JVM) dan amati log deployment di konsol:

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

Deploy image AOT(Native) dan amati log deployment di konsol:

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. Menyiapkan Pemicu Eventarc

Eventarc menawarkan solusi standar untuk mengelola alur perubahan status, yang disebut peristiwa, antara microservice yang dipisahkan. Saat dipicu, Eventarc merutekan peristiwa ini melalui langganan Pub/Sub ke berbagai tujuan (dalam dokumen ini, lihat Tujuan peristiwa) sambil mengelola pengiriman, keamanan, otorisasi, kemampuan observasi, dan penanganan error untuk Anda.

Anda dapat membuat pemicu Eventarc sehingga layanan Cloud Run Anda menerima notifikasi mengenai peristiwa atau rangkaian peristiwa tertentu. Dengan menentukan filter untuk pemicu, Anda dapat mengonfigurasi pemilihan rute peristiwa, termasuk sumber peristiwa dan layanan target Cloud Run.

Pertama, tetapkan variabel lingkungan project GCP:

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

Berikan pubsub.publisher ke akun layanan Cloud Storage:

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'

Siapkan pemicu Eventarc untuk image layanan JVM(JIT) dan AOT(Native) guna memproses image:

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    

Perhatikan bahwa kedua pemicu telah dibuat:

gcloud eventarc triggers list --location=eu

14. Menguji Versi Layanan

Setelah deployment layanan berhasil, Anda akan memposting gambar ke Cloud Storage, melihat apakah layanan kami dipanggil, apa yang ditampilkan Vision API, dan apakah metadata disimpan di Firestore.

Kembali ke Cloud Storage, lalu klik bucket yang kita buat di awal lab:

ff8a6567afc76235.png

Setelah berada di halaman detail bucket, klik tombol Upload files untuk mengupload gambar.

Misalnya, gambar GeekHour.jpeg disediakan dengan codebase Anda di /services/image-analysis/java. Pilih gambar, lalu tekan Open button:

347b76e8b775f2f5.png

Sekarang Anda dapat memeriksa eksekusi layanan, dimulai dengan image-analysis-jvm, diikuti dengan image-analysis-native.

Dari menu "tiga garis" (☰), buka layanan Cloud Run > image-analysis-jvm.

Klik Logs dan amati outputnya:

810a8684414ceafa.png

Dan memang, dalam daftar log, saya dapat melihat bahwa layanan JIT(JVM) image-analysis-jvm dipanggil.

Log menunjukkan awal dan akhir eksekusi layanan. Di antaranya, kita dapat melihat log yang kita masukkan dalam fungsi dengan pernyataan log di tingkat INFO. Kami melihat:

  • Detail acara yang memicu fungsi kita,
  • Hasil mentah dari panggilan Vision API,
  • Label yang ditemukan dalam gambar yang kami upload,
  • Informasi warna dominan,
  • Apakah gambar aman untuk ditampilkan,
  • Dan akhirnya metadata tentang gambar tersebut telah disimpan di Firestore.

Anda akan mengulangi proses untuk layanan image-analysis-native.

Dari menu "tiga garis" (☰), buka layanan Cloud Run > image-analysis-native.

Klik Logs dan amati outputnya:

b80308c7d0f55a3.png

Sekarang Anda harus mengamati apakah metadata gambar telah disimpan di Firestore.

Sekali lagi dari menu "tiga garis" (☰), buka bagian Firestore. Di subbagian Data (ditampilkan secara default), Anda akan melihat koleksi pictures dengan dokumen baru yang ditambahkan, sesuai dengan gambar yang baru saja Anda upload:

933a20a9709cb006.png

15. Membersihkan (Opsional)

Jika tidak berniat melanjutkan lab lainnya dalam seri ini, Anda dapat membersihkan resource untuk menghemat biaya dan menjadi pengguna cloud yang baik secara keseluruhan. Anda dapat membersihkan resource satu per satu sebagai berikut.

Hapus bucket:

gsutil rb gs://${BUCKET_PICTURES}

Hapus fungsi:

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

Hapus koleksi Firestore dengan memilih Hapus koleksi dari koleksi:

410b551c3264f70a.png

Atau, Anda dapat menghapus seluruh project:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Selamat!

Selamat! Anda telah berhasil menerapkan layanan utama pertama dari project ini.

Yang telah kita bahas

  • Cloud Storage
  • Cloud Run
  • Cloud Vision API
  • Cloud Firestore
  • Gambar Java Native

Langkah Berikutnya