1. 概要
最初のコードラボでは、バケットに写真を保存します。これにより、Cloud Run にデプロイされたサービスによって処理されるファイル作成イベントが生成されます。このサービスは Vision API を呼び出して画像分析を行い、結果をデータストアに保存します。

学習内容
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。



- プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。この設定はいつでも変更できます。
- プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は
PROJECT_IDと識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。 - なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
- 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクト全体を削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Cloud Shell の起動
Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。
Google Cloud Console で、右上のツールバーにある Cloud Shell アイコンをクリックします。

プロビジョニングと環境への接続にはそれほど時間はかかりません。完了すると、次のように表示されます。

この仮想マシンには、必要な開発ツールがすべて用意されています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働します。そのため、ネットワークのパフォーマンスと認証機能が大幅に向上しています。この Codelab での作業はすべて、ブラウザ内から実行できます。インストールは不要です。
3. API を有効にする
このラボでは、Cloud Functions と Vision API を使用しますが、まず Cloud Console または gcloud で有効にする必要があります。
Cloud Console で 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. バケットを作成する(コンソール)
写真用のストレージ バケットを作成します。この操作は、Google Cloud Platform コンソール(console.cloud.google.com)または Cloud Shell やローカル開発環境の gsutil コマンドライン ツールを使用して行うことができます。
[ストレージ] に移動
ハンバーガー メニュー(☰)から Storage ページに移動します。

バケットに名前を付ける
[CREATE BUCKET] ボタンをクリックします。

[CONTINUE] をクリックします。
場所を選択

任意のリージョン(ここでは Europe)にマルチリージョン バケットを作成します。
[CONTINUE] をクリックします。
デフォルトのストレージ クラスを選択する

データの Standard ストレージ クラスを選択します。
[CONTINUE] をクリックします。
アクセス制御を設定する

一般公開されている画像を扱うため、このバケットに保存されているすべての画像に同じ均一なアクセス制御を設定します。
Uniform アクセス制御オプションを選択します。
[CONTINUE] をクリックします。
Set Protection/Encryption

独自の暗号鍵を使用しないため、デフォルト(Google-managed key))のままにします。
CREATE をクリックして、バケットの作成を完了します。
allUsers をストレージ ビューアとして追加
[Permissions] タブに移動します。

次のように、Storage > Storage Object Viewer ロールを持つ allUsers メンバーをバケットに追加します。

[SAVE] をクリックします。
5. バケットを作成する(gsutil)
Cloud Shell で gsutil コマンドライン ツールを使用してバケットを作成することもできます。
Cloud Shell で、一意のバケット名の変数を設定します。Cloud Shell では、GOOGLE_CLOUD_PROJECT が一意のプロジェクト ID に設定されています。これをバケット名の末尾に追加できます。
次に例を示します。
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
ヨーロッパに標準のマルチリージョン ゾーンを作成します。
gsutil mb -l EU gs://${BUCKET_PICTURES}
均一なバケットレベルのアクセスを確保します。
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
バケットを公開します。
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
コンソールの Cloud Storage セクションに移動すると、公開 uploaded-pictures バケットが表示されます。

前の手順で説明したように、バケットに写真をアップロードでき、アップロードした写真が一般公開されていることをテストします。
6. バケットへの公開アクセスをテストする
ストレージ ブラウザに戻ると、バケットがリストに表示され、アクセス権が [公開] になっています(バケットのコンテンツに誰でもアクセスできることを示す警告アイコンも表示されます)。

これで、バケットで写真を受け取れるようになりました。
バケット名をクリックすると、バケットの詳細が表示されます。

ここで、Upload files ボタンを試して、バケットに写真を追加できることをテストします。ファイル選択ポップアップが表示され、ファイルの選択を求められます。選択すると、バケットにアップロードされ、この新しいファイルに自動的に割り当てられた public アクセス権が再び表示されます。

Public アクセスラベルの横に、小さなリンクアイコンも表示されます。クリックすると、ブラウザはその画像の公開 URL に移動します。この URL の形式は次のようになります。
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME は、バケットに選択したグローバルに一意の名前と、写真のファイル名です。
画像名の横にあるチェックボックスをオンにすると、DELETE ボタンが有効になり、最初の画像を削除できます。
7. データベースを準備する
Vision API から提供された画像に関する情報は、高速、フルマネージド、サーバーレス、クラウドネイティブの NoSQL ドキュメント データベースである Cloud Firestore データベースに保存します。Cloud Console の Firestore セクションに移動して、データベースを準備します。

Native mode または Datastore mode の 2 つのオプションがあります。ネイティブ モードを使用します。ネイティブ モードでは、オフライン サポートやリアルタイム同期などの追加機能を利用できます。
SELECT NATIVE MODE をクリックします。

マルチリージョンを選択します(ここではヨーロッパですが、理想的には関数とストレージ バケットと同じリージョンを少なくとも選択します)。
[CREATE DATABASE] ボタンをクリックします。
データベースが作成されると、次のようになります。

+ START COLLECTION ボタンをクリックして、新しいコレクションを作成します。
名前のコレクション pictures。

ドキュメントを作成する必要はありません。新しい写真が Cloud Storage に保存され、Vision API によって分析されると、それらの写真がプログラムで追加されます。
[Save] をクリックします。
Firestore は、新しく作成されたコレクションに最初のデフォルト ドキュメントを作成します。このドキュメントには有用な情報は含まれていないため、安全に削除できます。

コレクションにプログラムで作成されるドキュメントには、次の 4 つのフィールドが含まれます。
- name(文字列): アップロードされた写真のファイル名。ドキュメントのキーでもあります。
- labels(文字列の配列): Vision API によって認識されたアイテムのラベル
- color(文字列): ドミナント カラーの 16 進数カラーコード(例: #ab12ef)
- created(日付): この画像のメタデータが保存されたときのタイムスタンプ
- thumbnail(ブール値): この写真のサムネイル画像が生成された場合に存在し、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 を使用して pom.xml で Java クライアント ライブラリを有効にする方法を見てみましょう。
まず、Java 関数の依存関係を一覧表示する pom.xml ファイルを編集します。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 に送信するリクエストを 1 つ準備します。
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 ディストリビューションをインストールするには、まず install コマンドを使用します。
sdk install java 22.2.r17-grl
JIT ビルドと AOT ビルドの両方でこのバージョンを使用するように SDKman に指示します。
sdk use java 22.2.0.r17-grl
GraalVM 用の native-image utility をインストールします。
gu install native-image
Cloudshell では、次の簡単なコマンドを使用して、GraalVM と native-image ユーティリティをインストールできます。
# 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. コンテナ イメージをビルドして公開する
2 つの異なるバージョンのコンテナ イメージをビルドします。1 つは JIT(JVM) image、もう 1 つは 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
2 つのイメージにタグを付けて GCR に push します。
# 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 にデプロイする
サービスのデプロイにかかる時間。
サービスは 2 回デプロイします。1 回目は JIT(JVM)イメージを使用し、2 回目は AOT(ネイティブ)イメージを使用します。両方のサービス デプロイは、比較のためにバケットから同じイメージを並行して処理します。
まず、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 サブスクリプション経由でさまざまな宛先に転送します(このドキュメントのイベントの宛先を参照)。
指定したイベントまたは一連のイベントの通知を Cloud Run サービスが受信するように、Eventarc トリガーを作成できます。トリガーにフィルタを指定することで、イベントソースとターゲット 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
Cloud Storage サービス アカウントに pubsub.publisher を付与します。
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
2 つのトリガーが作成されたことを確認します。
gcloud eventarc triggers list --location=eu
14. テスト サービス バージョン
サービス デプロイが成功したら、Cloud Storage に写真を投稿し、サービスが呼び出されたかどうか、Vision API が何を返すか、メタデータが Firestore に保存されているかどうかを確認します。
Cloud Storage に戻り、ラボの開始時に作成したバケットをクリックします。

バケットの詳細ページで、Upload files ボタンをクリックして写真をアップロードします。
たとえば、GeekHour.jpeg イメージは、コードベースとともに /services/image-analysis/java に提供されます。画像を選択して 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 イメージ