Pic-a-harian: Lab 1—Menyimpan dan menganalisis gambar (Native Java)

1. Ringkasan

Di codelab 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 akan melakukan panggilan ke Vision API untuk melakukan analisis gambar dan menyimpan hasilnya di datastore.

c0650ee4a76db35e.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.

295004821bab6a87.pngS

37d264871000675d.png

96d86d3d5655cdbe.pngS

  • 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 bersifat 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 (umumnya diidentifikasi sebagai PROJECT_ID). Jika tidak suka dengan ID yang dibuat, Anda dapat membuat ID acak lainnya. Atau, Anda dapat mencobanya sendiri, dan lihat apakah ID tersebut tersedia. ID tidak dapat diubah setelah langkah ini dan tersedia 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 tidak akan memakan 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 project-nya. 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:

84688aa223b1c3a2.pngS

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

320e18fedb7fbe0.pngS

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 Anda harus mengaktifkannya terlebih dahulu di Cloud Console atau dengan gcloud.

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

8f3522d790bb026c.pngS

Anda akan diarahkan ke halaman Cloud Vision API:

d785572fa14c87c2.png

Klik tombol ENABLE.

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

Di dalam Cloud Shell, jalankan perintah berikut:

gcloud services enable vision.googleapis.com

Anda akan melihat operasi berhasil diselesaikan:

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

Aktifkan juga Cloud Run dan Cloud Build:

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

4. Membuat bucket (konsol)

Buat bucket penyimpanan untuk foto. 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 "hamburger" (⇧), buka halaman Storage.

d08ecb0ae29330a1.png

Menamai bucket

Klik tombol CREATE BUCKET.

8951851554a430d2.pngS

Klik CONTINUE.

Pilih Lokasi

24b24625157ab467.pngS

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

Klik CONTINUE.

Pilih kelas penyimpanan default

9e7bd365fa94a2e0.pngS

Pilih kelas penyimpanan Standard untuk data Anda.

Klik CONTINUE.

Menyetel Kontrol Akses

1ff4a1f6e57045f5.pngS

Saat Anda akan bekerja dengan gambar yang dapat diakses oleh publik, Anda ingin semua foto yang disimpan dalam bucket ini memiliki kontrol akses yang seragam.

Pilih opsi kontrol akses Uniform.

Klik CONTINUE.

Menyetel Perlindungan/Enkripsi

2d469b076029d365.pngS

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

Klik CREATE, untuk menyelesaikan pembuatan bucket.

Menambahkan allUsers sebagai penampil penyimpanan

Buka tab Permissions:

19564b3ad8688ae8.pngS

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

d655e760c76d62c1.png

Klik SAVE.

5. Membuat bucket (gsutil)

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

Di Cloud Shell, tetapkan variabel untuk nama bucket yang unik. Cloud Shell sudah memiliki GOOGLE_CLOUD_PROJECT yang ditetapkan 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 seragam:

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

Buat bucket menjadi publik.

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

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

65c63ef4a6eb30ad.pngS

Uji apakah Anda dapat mengupload gambar ke bucket dan gambar yang diupload akan tersedia untuk 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 label "Publik" akses Anda (termasuk tanda peringatan yang mengingatkan Anda bahwa siapa saja memiliki akses ke konten dalam bucket tersebut).

e639a9ba625b71a6.png

Bucket Anda sekarang siap untuk menerima gambar.

Jika mengklik nama bucket, Anda akan melihat detail bucket.

1f88a2290290aba8.pngS

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, akses akan diupload ke bucket Anda, dan Anda akan melihat lagi akses public yang telah otomatis diatribusikan ke file baru ini.

1209e7ebe1f63b10.pngS

Di sepanjang 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 berupa nama unik global yang telah Anda pilih untuk bucket Anda, kemudian nama file foto Anda.

Dengan mengklik kotak centang di sepanjang nama foto, 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 dalam database Cloud Firestore, yaitu database dokumen NoSQL yang cepat, terkelola sepenuhnya, serverless, dan berbasis cloud. Siapkan database Anda dengan membuka bagian Firestore di Konsol Cloud:

e57a673537b5deca.png

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

Klik SELECT NATIVE MODE.

1a2e363fae5c7e96.pngS

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

Klik tombol CREATE DATABASE.

Setelah database dibuat, Anda akan melihat tampilan berikut:

7dcc82751ed483fb.pngS

Buat koleksi baru dengan mengklik tombol + START COLLECTION.

Koleksi nama pictures.

dce3d73884ac8c83.png

Anda tidak perlu membuat dokumen. Anda akan menambahkannya secara terprogram karena 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:

63e95c844b3f79d3.pngS

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

  • name (string): nama file gambar yang diupload, yang juga merupakan kunci dokumen
  • label (array string): label item yang dikenali oleh Vision API
  • color (string): kode warna heksadesimal dari warna dominan (yaitu #ab12ef)
  • dibuat (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 mengurutkan berdasarkan tanggal pembuatan, kita harus 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, pada kolom navigasi di sebelah kiri, lalu membuat indeks gabungan seperti yang ditunjukkan di bawah ini:

2236d3a024a59232.pngS

Klik Create. Pembuatan indeks dapat memerlukan waktu beberapa menit.

8. Meng-clone kode

Clone kode, jika Anda belum melakukannya di codelab sebelumnya:

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

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

4c2a18a2c8b69dc5.pngS

9. Mempelajari kode layanan

Anda mulai dengan melihat cara Library Klien Java diaktifkan di pom.xml menggunakan BOM:

Pertama, buka file pom.xml yang mencantumkan dependensi aplikasi Java; fokus pada penggunaan Vision, Cloud Storage, dan Firestore 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0-M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>services</groupId>
        <artifactId>image-analysis</artifactId>
        <version>0.0.1</version>
        <name>image-analysis</name>
        <description>Spring App for Image Analysis</description>
    <properties>
        <java.version>17</java.version>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.compiler.source>17</maven.compiler.source>        
        <spring-cloud.version>2023.0.0-M2</spring-cloud.version>
        <testcontainers.version>1.19.1</testcontainers.version>
    </properties>
...
  <dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>libraries-bom</artifactId>
            <version>26.24.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
  </dependencyManagement>
— 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
                <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-function-web</artifactId>
                </dependency>
        <dependency>
            <groupId>com.google.cloud.functions</groupId>
            <artifactId>functions-framework-api</artifactId>
            <version>1.1.0</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>        

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

@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 isi foto
  • Properti gambar: untuk memberikan atribut gambar yang menarik (kami lebih tertarik dengan warna dominan gambar)
  • Safe search: untuk mengetahui apakah gambar aman untuk ditampilkan (gambar 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 ini 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 bisa melanjutkan. Jadi, mengapa 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 hal-hal, 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);
}

Kita tertarik untuk mengetahui warna dominan pada 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 periksa apakah gambar 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 sedang memeriksa karakteristik dewasa / spoofing / medis / kekerasan / tidak pantas untuk mengetahui apakah hal tersebut mungkin atau sangat mungkin.

Jika tidak ada masalah untuk hasil safe search, kita dapat menyimpan metadata di Firestore:

// Saving result to Firestore
if (isSafe) {
          ApiFuture<WriteResult> writeResult = 
               eventService.storeImage(fileName, labels,
                                       mainColor);
          logger.info("Picture metadata saved in Firestore at " + 
               writeResult.get().getUpdateTime());
}
...
  public ApiFuture<WriteResult> storeImage(String fileName, 
                                           List<String> labels, 
                                           String mainColor) {
    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());

    return doc.set(data, SetOptions.merge());
  }

10. Membuat Image Aplikasi dengan GraalVM

Pada langkah opsional ini, Anda akan mem-build JIT based app image, lalu Native Java app image, menggunakan GraalVM.

Untuk menjalankan build, Anda harus memastikan bahwa Anda memiliki JDK yang sesuai serta telah menginstal dan mengonfigurasi builder gambar native. Tersedia beberapa opsi.

To start, download GraalVM 22.3.x Community Edition dan ikuti petunjuk di halaman Penginstalan GraalVM.

Proses ini dapat sangat disederhanakan dengan bantuan SDKMAN.

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

sdk install java 17.0.8-graal

Minta SDKman menggunakan versi ini, untuk build JIT dan AOT:

sdk use java 17.0.8-graal

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

# download GraalVM
wget https://download.oracle.com/graalvm/17/latest/graalvm-jdk-17_linux-x64_bin.tar.gz 
tar -xzf graalvm-jdk-17_linux-x64_bin.tar.gz

ls -lart

# configure Java 17 and GraalVM for Java 17
# note the name of the latest GraalVM version, as unpacked by the tar command
echo Existing JVM: $JAVA_HOME
cd graalvm-jdk-17.0.8+9.1

export JAVA_HOME=$PWD
cd bin
export PATH=$PWD:$PATH

echo JAVA HOME: $JAVA_HOME
echo PATH: $PATH

cd ../..

# validate the version with
java -version 

# observe
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)

Pertama, tetapkan variabel lingkungan project GCP:

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

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

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

Bangun image aplikasi JIT:

./mvnw package

Amati log build di terminal:

...
[INFO] Results:
[INFO] 
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ image-analysis ---
[INFO] Building jar: /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:3.2.0-M3:repackage (repackage) @ image-analysis ---
[INFO] Replacing main artifact /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis-0.0.1.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.335 s
[INFO] Finished at: 2023-10-10T19:33:25Z
[INFO] ------------------------------------------------------------------------

Bangun image Native(menggunakan AOT):

./mvnw native:compile -Pnative

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

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

...
[2/7] Performing analysis...  [*********]                                                              (124.5s @ 4.53GB)
  29,732 (93.19%) of 31,905 classes reachable
  60,161 (70.30%) of 85,577 fields reachable
 261,973 (67.29%) of 389,319 methods reachable
   2,940 classes, 2,297 fields, and 97,421 methods registered for reflection
      81 classes,    90 fields, and    62 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                              (11.7s @ 4.67GB)
[4/7] Parsing methods...      [***]                                                                      (6.1s @ 5.91GB)
[5/7] Inlining methods...     [****]                                                                     (4.5s @ 4.39GB)
[6/7] Compiling methods...    [******]                                                                  (35.3s @ 4.60GB)
[7/7] Creating image...                                                                                 (12.9s @ 4.61GB)
  80.08MB (47.43%) for code area:   190,483 compilation units
  73.81MB (43.72%) for image heap:  660,125 objects and 189 resources
  14.95MB ( 8.86%) for other data
 168.84MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   2.66MB com.google.cloud.vision.v1p4beta1                   18.51MB byte[] for code metadata
   2.60MB com.google.cloud.vision.v1                           9.27MB java.lang.Class
   2.49MB com.google.protobuf                                  7.34MB byte[] for reflection metadata
   2.40MB com.google.cloud.vision.v1p3beta1                    6.35MB byte[] for java.lang.String
   2.17MB com.google.storage.v2                                5.72MB java.lang.String
   2.12MB com.google.firestore.v1                              4.46MB byte[] for embedded resources
   1.64MB sun.security.ssl                                     4.30MB c.oracle.svm.core.reflect.SubstrateMethodAccessor
   1.51MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3    4.27MB byte[] for general heap data
   1.47MB com.google.cloud.vision.v1p2beta1                    2.50MB com.oracle.svm.core.hub.DynamicHubCompanion
   1.34MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3     1.17MB java.lang.Object[]
  58.34MB for 977 more packages                                9.19MB for 4667 more object types
------------------------------------------------------------------------------------------------------------------------
                        13.5s (5.7% of total time) in 75 GCs | Peak RSS: 9.44GB | CPU load: 6.13
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
 /home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating '/home/user/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis' in 3m 57s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:28 min
[INFO] Finished at: 2023-10-10T19:53:30Z
[INFO] ------------------------------------------------------------------------

11. Membangun dan Memublikasikan Image Container

Mari kita bangun image container dalam dua versi yang berbeda: satu sebagai JIT image dan yang lainnya sebagai Native Java image.

Pertama, tetapkan variabel lingkungan project GCP:

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

Bangun gambar JIT:.

./mvnw spring-boot:build-image -Pji

Amati log build di terminal:

[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... started at 2023-10-10T20:00:31Z
[INFO]     [creator]     *** Images (4c84122a1826):
[INFO]     [creator]           docker.io/library/image-analysis-maven-jit:latest
[INFO]     [creator]     Timer: Saving docker.io/library/image-analysis-maven-jit:latest... ran for 6.975913605s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Exporter ran for 8.068588001s and ended at 2023-10-10T20:00:38Z
[INFO]     [creator]     Timer: Cache started at 2023-10-10T20:00:38Z
[INFO]     [creator]     Reusing cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO]     [creator]     Timer: Cache ran for 200.449002ms and ended at 2023-10-10T20:00:38Z
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-jit:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  43.887 s
[INFO] Finished at: 2023-10-10T20:00:39Z
[INFO] ------------------------------------------------------------------------

Bangun image AOT(Native):

./mvnw spring-boot:build-image -Pnative

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

Catatan:

  • proses build membutuhkan waktu lebih lama, tergantung pada mesin yang diuji
  • gambar dapat dikompresi lebih lanjut dengan UPX, tetapi berdampak negatif kecil pada performa startup, oleh karena itu build ini tidak menggunakan UPX - selalu merupakan sedikit kompromi
...
[INFO]     [creator]     Saving docker.io/library/image-analysis-maven-native:latest...
[INFO]     [creator]     *** Images (13167702674e):
[INFO]     [creator]           docker.io/library/image-analysis-maven-native:latest
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/bellsoft-liberica:native-image-svm'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/syft:syft'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO]     [creator]     Adding cache layer 'buildpacksio/lifecycle:cache.sbom'
[INFO] 
[INFO] Successfully built image 'docker.io/library/image-analysis-maven-native:latest'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:37 min
[INFO] Finished at: 2023-10-10T20:05:16Z
[INFO] ------------------------------------------------------------------------

Validasi bahwa image telah dibuat:

docker images | grep image-analysis

Beri tag dan kirim kedua image ke GCR:

# JIT image
docker tag image-analysis-maven-jit gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit

# Native(AOT) image
docker tag image-analysis-maven-native gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native
docker push  gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native

12. Men-deploy ke Cloud Run

Saatnya men-deploy layanan.

Anda akan men-deploy layanan dua kali, sekali menggunakan image JIT dan yang kedua menggunakan image AOT(Native). Kedua deployment layanan akan memproses image 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 dan amati log deployment di konsol:

gcloud run deploy image-analysis-jit \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-jit \
     --region europe-west1 \
     --memory 2Gi --allow-unauthenticated

...
Deploying container to Cloud Run service [image-analysis-jit] in project [...] region [europe-west1]
✓ Deploying... Done.                                                                                                                                                               
  ✓ Creating Revision...                                                                                                                                                           
  ✓ Routing traffic...                                                                                                                                                             
  ✓ Setting IAM Policy...                                                                                                                                                          
Done.                                                                                                                                                                              
Service [image-analysis-jit] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jit-...-ew.a.run.app

Deploy image Native dan amati log deployment di konsol:

gcloud run deploy image-analysis-native \
     --image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-maven-native \
     --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 aliran perubahan status, yang disebut peristiwa, di antara microservice yang dipisahkan. Saat dipicu, Eventarc akan 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 agar layanan Cloud Run Anda menerima notifikasi tentang satu atau serangkaian peristiwa yang ditentukan. Dengan menentukan filter untuk pemicu, Anda dapat mengonfigurasi perutean peristiwa, termasuk sumber peristiwa dan layanan Cloud Run target.

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 JIT dan Native guna memproses gambar:

gcloud eventarc triggers list --location=eu

gcloud eventarc triggers create image-analysis-jit-trigger \
     --destination-run-service=image-analysis-jit \
     --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. Versi Layanan Pengujian

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

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

33442485a1d76921.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 satu gambar, lalu tekan Open button:

d57529452f62bd32.png

Anda kini dapat memeriksa eksekusi layanan, dimulai dengan image-analysis-jit, diikuti dengan image-analysis-native.

Dari "hamburger" (⇧), buka layanan Cloud Run > image-analysis-jit.

Klik Logs dan amati outputnya:

ae1a4a94c7c7a166.png

Dan memang, dalam daftar log, saya bisa melihat bahwa layanan JIT image-analysis-jit dipanggil.

Log menunjukkan awal dan akhir eksekusi layanan. Dan di antaranya, kita dapat melihat log yang kita masukkan ke dalam fungsi dengan laporan log di level INFO. Kita dapat melihat:

  • Detail peristiwa yang memicu fungsi kita,
  • Hasil mentah dari panggilan Vision API,
  • Label yang ditemukan dalam gambar yang kita unggah,
  • Informasi tentang warna yang dominan,
  • Apakah gambar tersebut aman untuk ditampilkan,
  • Dan pada akhirnya, {i>metadata<i} yang berkaitan dengan gambar tersebut telah disimpan di Firestore.

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

Dari "hamburger" (⇧), buka layanan Cloud Run > image-analysis-native.

Klik Logs dan amati outputnya:

4afe22833c1fd14c.pngS

Sekarang Anda perlu mengamati apakah metadata gambar telah disimpan di Fiorestore.

Lagi dari "hamburger" (⇧), buka bagian Firestore. Di subbagian Data (ditampilkan secara default), Anda akan melihat koleksi pictures dengan tambahan dokumen baru, sesuai dengan gambar yang baru saja Anda upload:

82d6c468956e7cfc.png

15. Pembersihan (Opsional)

Jika tidak ingin melanjutkan lab lain dalam seri ini, Anda dapat menghapus resource untuk menghemat biaya dan menjadi cloud citizen yang baik secara keseluruhan. Anda dapat membersihkan resource satu per satu seperti 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:

6cc86a7b88fdb4d3.pngS

Atau, Anda dapat menghapus seluruh project:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

16. Selamat!

Selamat! Anda telah berhasil menerapkan layanan kunci enkripsi pertama project.

Yang telah kita bahas

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

Langkah Berikutnya