1. 總覽
在第一個程式碼研究室中,您會將圖片儲存在 bucket 中。這會產生檔案建立事件,並由部署在 Cloud Run 中的服務處理。這項服務會呼叫 Vision API 進行圖片分析,並將結果儲存在資料存放區中。

課程內容
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
2. 設定和需求
自修實驗室環境設定
- 登入 Google Cloud 控制台,然後建立新專案或重複使用現有專案。如果沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶。



- 專案名稱是這個專案參與者的顯示名稱。這是 Google API 未使用的字元字串。你隨時可以更新該位置資訊。
- 專案 ID 在所有 Google Cloud 專案中不得重複,且設定後即無法變更。Cloud 控制台會自動產生不重複的字串,通常您不需要在意這個字串。在大多數程式碼研究室中,您需要參照專案 ID (通常會標示為
PROJECT_ID)。如果您不喜歡產生的 ID,可以產生另一個隨機 ID。你也可以嘗試自訂名稱,看看是否可用。完成這個步驟後就無法變更,且專案期間都會維持這個設定。 - 請注意,部分 API 會使用第三個值,也就是「專案編號」。如要進一步瞭解這三種值,請參閱說明文件。
- 接著,您需要在 Cloud 控制台中啟用帳單,才能使用 Cloud 資源/API。完成本程式碼研究室的費用應該不高,甚至完全免費。如要關閉資源,避免產生本教學課程以外的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 新使用者可參加價值$300 美元的免費試用計畫。
啟動 Cloud Shell
雖然可以透過筆電遠端操作 Google Cloud,但在本程式碼研究室中,您將使用 Google Cloud Shell,這是可在雲端執行的指令列環境。
在 Google Cloud 控制台中,點選右上工具列的 Cloud Shell 圖示:

佈建並連線至環境的作業需要一些時間才能完成。完成後,您應該會看到如下的內容:

這部虛擬機器搭載各種您需要的開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。您可以在瀏覽器中完成本程式碼研究室的所有作業。您不需要安裝任何軟體。
3. 啟用 API
在本實驗室中,您將使用 Cloud Functions 和 Vision API,但首先必須在 Cloud 控制台或使用 gcloud 啟用這些服務。
如要在 Cloud 控制台中啟用 Vision API,請在搜尋列中搜尋 Cloud Vision API:

系統會將您導向 Cloud Vision API 頁面:

按一下 ENABLE 按鈕。
或者,您也可以使用 gcloud 指令列工具,透過 Cloud Shell 啟用。
在 Cloud Shell 中執行下列指令:
gcloud services enable vision.googleapis.com
您應該會看到作業順利完成:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
同時啟用 Cloud Run 和 Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. 建立 bucket (主控台)
建立圖片的儲存空間值區。您可以透過 Google Cloud Platform 主控台 ( console.cloud.google.com) 或 Cloud Shell 或本機開發環境的 gsutil 指令列工具執行這項操作。
前往「儲存空間」
在「漢堡」選單 (☰) 中,前往 Storage 頁面。

為 bucket 命名
按一下 CREATE BUCKET 按鈕。

按一下「CONTINUE」。
選擇位置

在所選區域 (此處為 Europe) 建立多區域 bucket。
按一下「CONTINUE」。
選擇預設儲存空間級別

為資料選擇 Standard 儲存空間級別。
按一下「CONTINUE」。
設定存取權控管

由於您將使用可公開存取的圖片,因此希望儲存在這個 bucket 中的所有圖片都具有相同的統一存取權控管機制。
選擇 Uniform 存取權控管選項。
按一下「CONTINUE」。
設定保護/加密

保留預設值 (Google-managed key)),因為您不會使用自己的加密金鑰。
按一下 CREATE,最終完成值區建立程序。
將 allUsers 新增為儲存空間檢視者
前往 Permissions 分頁:

將 allUsers 成員新增至 bucket,並指派 Storage > Storage Object Viewer 角色,如下所示:

按一下「SAVE」。
5. 建立 bucket (gsutil)
您也可以使用 Cloud Shell 中的 gsutil 指令列工具建立 bucket。
在 Cloud Shell 中,為不重複的值區名稱設定變數。Cloud Shell 已將 GOOGLE_CLOUD_PROJECT 設為專屬專案 ID。你可以將該值附加至 bucket 名稱。
例如:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
在歐洲建立標準多區域:
gsutil mb -l EU gs://${BUCKET_PICTURES}
確認統一值區層級存取權:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
將 bucket 設為公開:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
前往控制台的 Cloud Storage 部分,您應該會看到公開 uploaded-pictures bucket:

如上一個步驟所述,測試您是否可以將圖片上傳至值區,並確認上傳的圖片可公開存取。
6. 測試 bucket 的公開存取權
返回儲存空間瀏覽器,您會在清單中看到自己的 bucket,並顯示「公開」存取權 (包括提醒您任何人都能存取該 bucket 內容的警告符號)。

現在值區已可接收圖片。
按一下 bucket 名稱,即可查看 bucket 詳細資料。

您可以在該處嘗試 Upload files 按鈕,測試是否能將圖片新增至值區。檔案選擇器彈出式視窗會要求你選取檔案。選取後,系統會將檔案上傳至儲存空間,並再次顯示自動指派給這個新檔案的public存取權。

Public 存取權標籤旁邊也會顯示小小的連結圖示。點選後,瀏覽器會前往該圖片的公開網址,格式如下:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
其中 BUCKET_NAME 是您為 bucket 選擇的全域專屬名稱,後面則是圖片的檔案名稱。
按一下圖片名稱旁的核取方塊,即可啟用 DELETE 按鈕,並刪除第一張圖片。
7. 準備資料庫
您會將 Vision API 提供的圖片資訊儲存到 Cloud Firestore 資料庫。這項服務是快速、全代管、無伺服器且雲端原生的 NoSQL 文件資料庫。前往 Cloud 控制台的「Firestore」部分,準備資料庫:

系統會提供兩個選項:Native mode 或 Datastore mode。使用原生模式,即可享有離線支援和即時同步等額外功能。
按一下 SELECT NATIVE MODE。

選擇多地區 (這裡選擇歐洲,但最好與函式和儲存空間值區位於相同地區)。
按一下 CREATE DATABASE 按鈕。
資料庫建立完成後,您應該會看到下列內容:

按一下 + START COLLECTION 按鈕,建立新的集合。
為集合命名 pictures。

您不需要建立文件,當新圖片儲存在 Cloud Storage 中,並由 Vision API 分析時,您會以程式輔助方式新增圖片。
按一下「Save」。
Firestore 會在新建立的集合中建立第一個預設文件,您可以安全地刪除該文件,因為其中不含任何實用資訊:

我們將在集合中以程式輔助方式建立的文件會包含 4 個欄位:
- name (字串):上傳圖片的檔案名稱,也是文件的鍵
- 標籤 (字串陣列):Vision API 辨識項目的標籤
- color (字串):主色的十六進位顏色代碼 (即 #ab12ef)
- created (日期):儲存這張圖片中繼資料的時間戳記
- 縮圖 (布林值):選用欄位,如果系統已為這張相片產生縮圖,這個欄位就會存在並設為 true
由於我們會在 Firestore 中搜尋有縮圖的圖片,並依建立日期排序,因此需要建立搜尋索引。
您可以在 Cloud Shell 中使用下列指令建立索引:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
您也可以在 Cloud 控制台中執行這項操作,方法是點選左側導覽欄中的 Indexes,然後建立複合索引,如下所示:

按一下 Create,建立索引可能需要幾分鐘的時間。
8. 複製程式碼
複製程式碼 (如果您在上一個程式碼研究室中尚未複製):
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
接著,您可以前往包含服務的目錄,開始建構實驗室:
cd serverless-photosharing-workshop/services/image-analysis/java
服務的檔案版面配置如下:

9. 探索服務代碼
首先,您會瞭解如何透過 BOM 啟用 Java 用戶端程式庫:pom.xml
首先,請編輯 pom.xml 檔案,其中列出 Java 函式的依附元件。更新程式碼,新增 Cloud Vision API Maven 依附元件:
<?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>
這項功能是在 EventController 類別中實作。每次將新圖片上傳至值區時,服務都會收到通知以進行處理:
@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 {
...
}
程式碼會繼續驗證 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);
}
現在可以建構要求,程式碼會準備這類要求,以便傳送至 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);
我們要求 Vision API 提供 3 項主要功能:
- 標籤偵測:瞭解圖片內容
- 圖片屬性:提供圖片的有趣屬性 (我們感興趣的是圖片的主色)
- 安全搜尋:判斷圖片是否適合顯示 (不應含有成人 / 醫療 / 煽情 / 暴力內容)
此時,我們可以呼叫 Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
以下是 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
}
如果沒有傳回任何錯誤,我們就可以繼續,這就是我們有這個 if 區塊的原因:
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);
}
我們要取得圖片中辨識到的事物、類別或主題的標籤:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
我們想知道圖片的主色:
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);
}
讓我們檢查圖片是否安全無虞:
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);
}
我們會檢查成人 / 惡搞 / 醫療 / 暴力 / 煽情特徵,判斷這些特徵不太可能或非常可能存在。
如果安全搜尋結果沒問題,我們可以在 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. 使用 GraalVM 建構應用程式映像檔 (選用)
在這個選用步驟中,您將使用 GraalVM 建構 JIT(JVM) based app image 和 AOT(Native) Java app image。
如要執行建構作業,請務必安裝並設定適當的 JDK 和 native-image 建構工具。你可以選擇以下做法:
To start,下載 GraalVM 22.2.x Community Edition,然後按照 GraalVM 安裝頁面的操作說明進行。
SDKMAN! 可大幅簡化這個程序。
如要使用 SDKman 安裝適當的 JDK 發行版本,請先使用安裝指令:
sdk install java 22.2.r17-grl
指示 SDKman 使用這個版本,進行 JIT 和 AOT 建構:
sdk use java 22.2.0.r17-grl
安裝 GraalVM 適用的 native-image utility:
gu install native-image
在 Cloudshell 中,您可以執行下列簡單指令,輕鬆安裝 GraalVM 和原生映像檔公用程式:
# 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 ../..
首先,設定 GCP 專案環境變數:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
接著,您可以前往包含服務的目錄,開始建構實驗室:
cd serverless-photosharing-workshop/services/image-analysis/java
建構 JIT(JVM) 應用程式映像檔:
./mvnw package -Pjvm
在終端機中觀察建構記錄:
... [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] ------------------------------------------------------------------------
建構 AOT(原生) 映像檔:
./mvnw package -Pnative -DskipTests
在終端機中觀察建構記錄,包括原生映像檔建構記錄:
請注意,建構作業需要較長的時間,視您測試的機器而定。
...
[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. 建構及發布容器映像檔
現在讓我們建構兩個不同版本的容器映像檔:一個是 JIT(JVM) image,另一個是 AOT(Native) Java image。
首先,設定 GCP 專案環境變數:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
建構 JIT(JVM) 映像檔:
./mvnw package -Pjvm-image
在終端機中觀察建構記錄:
[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] ------------------------------------------------------------------------
建構 AOT(原生) 映像檔:
./mvnw package -Pnative-image
在終端機中觀察建構記錄,包括原生映像檔建構記錄,以及使用 UPX 壓縮映像檔。
請注意,建構時間會因測試的機器而異,可能需要較長的時間
... [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] ------------------------------------------------------------------------
驗證映像檔是否已建構完成:
docker images | grep image-analysis
將這兩個映像檔加上標記並推送至 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. 部署至 Cloud Run
部署服務。
您將部署服務兩次,一次使用 JIT(JVM) 映像檔,另一次使用 AOT(原生) 映像檔。這兩項服務部署作業會平行處理來自 Bucket 的相同圖片,以供比較。
首先,設定 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
部署 JIT(JVM) 映像檔,並在控制台中觀察部署記錄:
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
部署 AOT(原生) 映像檔,並觀察控制台中的部署記錄:
gcloud run deploy image-analysis-native \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app
13. 設定 Eventarc 觸發條件
Eventarc 提供標準化解決方案,可管理已分離微服務之間的狀態變更流程 (稱為「事件」)。觸發之後,Eventarc 會透過 Pub/Sub 訂閱項目將這些事件轉送至不同的目的地 (請參閱本文的「事件目的地」一節),同時為您管理傳送、安全防護、授權、觀測和錯誤處理工作。
您可以建立 Eventarc 觸發條件,讓 Cloud Run 服務接收特定事件或事件集的通知。指定觸發條件的篩選器,即可設定事件的轉送路徑,包括事件來源和目標 Cloud Run 服務。
首先,設定 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
將 pubsub.publisher 授予 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'
為 JVM(JIT) 和 AOT(原生) 服務映像檔設定 Eventarc 觸發條件,以處理圖片:
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
您會發現已建立兩個觸發條件:
gcloud eventarc triggers list --location=eu
14. 測試服務版本
服務部署成功後,請將圖片發布至 Cloud Storage,確認服務是否已叫用、Vision API 傳回的內容,以及中繼資料是否儲存在 Firestore 中。
返回 Cloud Storage,然後按一下我們在實驗室開始時建立的 bucket:

進入值區詳細資料頁面後,按一下 Upload files 按鈕即可上傳圖片。
舉例來說,程式碼基底的 /services/image-analysis/java 下提供 GeekHour.jpeg 圖片。選取圖片並按下 Open button:

現在可以檢查服務的執行情況,先輸入 image-analysis-jvm,再輸入 image-analysis-native。
在「漢堡」選單 (☰) 中,前往 Cloud Run > image-analysis-jvm 服務。
按一下「記錄」,觀察輸出內容:

確實,在記錄清單中,我可以看到 JIT(JVM) 服務 image-analysis-jvm 已遭叫用。
記錄會顯示服務執行的開始和結束時間。中間則會顯示我們在函式中加入的記錄,記錄陳述式位於 INFO 層級。我們看到:
- 觸發函式的事件詳細資料,
- Vision API 呼叫的原始結果,
- 我們在您上傳的圖片中找到的標籤,
- 主要顏色資訊,
- 圖片是否適合顯示
- 最終,這些圖片中繼資料會儲存在 Firestore 中。
請為 image-analysis-native 服務重複執行這個程序。
在「漢堡」選單 (☰) 中,前往 Cloud Run > image-analysis-native 服務。
按一下「記錄」,觀察輸出內容:

現在請觀察圖片中繼資料是否已儲存在 Fiorestore 中。
再次從「漢堡」選單 (☰) 前往 Firestore 部分。在 Data 子專區 (預設顯示),您應該會看到 pictures 集合新增的文件,對應您剛上傳的圖片:

15. 清除 (選用)
如果您不打算繼續進行本系列的其他實驗室,可以清除資源來節省費用,並成為優質的雲端使用者。您可以按照下列方式個別清除資源。
刪除值區:
gsutil rb gs://${BUCKET_PICTURES}
刪除函式:
gcloud functions delete picture-uploaded --region europe-west1 -q
從集合中選取「刪除集合」,即可刪除 Firestore 集合:

或者,您也可以刪除整個專案:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. 恭喜!
恭喜!您已成功實作專案的第一項重要服務!
涵蓋內容
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
- 原生 Java 圖片