この Codelab について
1. 概要
この Codelab では、前のラボをベースに、サムネイル サービスを追加します。サムネイル サービスは、大きな画像を撮影してサムネイルを作成するウェブコンテナです。
画像が Cloud Storage にアップロードされると、通知が Cloud Pub/Sub 経由で Cloud Run ウェブコンテナに送信されます。そこで画像のサイズが変更され、Cloud Storage の別のバケットに保存されます。
学習内容
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。
- プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列で、いつでも更新できます。
- プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud Console により一意の文字列が自動生成されます(通常は内容を意識する必要はありません)。ほとんどの Codelab では、プロジェクト ID を参照する必要があります(通常、プロジェクト ID は「
PROJECT_ID
」の形式です)。好みの文字列でない場合は、別のランダムな ID を生成するか、独自の ID を試用して利用可能であるかどうかを確認することができます。プロジェクトの作成後、ID は「フリーズ」されます。 - 3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
- 次に、Cloud のリソースや API を使用するために、Cloud Console で課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルを終了した後に課金が発生しないようにリソースをシャットダウンするには、Codelab の最後にある「クリーンアップ」の手順を行います。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Cloud Shell の起動
Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。
GCP Console で右上のツールバーにある Cloud Shell アイコンをクリックします。
プロビジョニングと環境への接続にはそれほど時間はかかりません。完了すると、次のように表示されます。
この仮想マシンには、必要な開発ツールがすべて用意されています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働します。そのため、ネットワークのパフォーマンスと認証機能が大幅に向上しています。このラボでの作業はすべて、ブラウザから実行できます。
3. API を有効にする
このラボでは、コンテナ イメージをビルドするために Cloud Build を、コンテナをデプロイするために Cloud Run が必要になります。
Cloud Shell から両方の API を有効にします。
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
オペレーションが正常に完了することを確認できます。
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. 別のバケットを作成する
アップロードした画像のサムネイルを別のバケットに保存します。gsutil
を使用して 2 番目のバケットを作成しましょう。
Cloud Shell 内で、一意のバケット名の変数を設定します。Cloud Shell には、GOOGLE_CLOUD_PROJECT
に一意のプロジェクト ID がすでに設定されています。これをバケット名に追加できます。次に、欧州に均一なアクセス権を持つ公開マルチリージョン バケットを作成します。
BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT gsutil mb -l EU gs://$BUCKET_THUMBNAILS gsutil uniformbucketlevelaccess set on gs://$BUCKET_THUMBNAILS gsutil iam ch allUsers:objectViewer gs://$BUCKET_THUMBNAILS
これで、新しい公開バケットが作成されます。
5. コードのクローンを作成する
コードのクローンを作成し、サービスを含むディレクトリに移動します。
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
サービスのファイル レイアウトは次のようになります。
services | ├── thumbnails | ├── nodejs | ├── Dockerfile ├── index.js ├── package.json
thumbnails/nodejs
フォルダ内には、次の 3 つのファイルがあります。
index.js
には Node.js コードが含まれています。package.json
はライブラリの依存関係を定義します。Dockerfile
はコンテナ イメージを定義します。
6. コードを探索する
コードを確認するには、Cloud Shell ウィンドウの上部にある Open Editor
ボタンをクリックして、組み込みのテキスト エディタを使用します。
専用のブラウザ ウィンドウでエディタを開いて、広い画面スペースを利用することもできます。
依存関係
package.json
ファイルは、必要なライブラリ依存関係を定義します。
{
"name": "thumbnail_service",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"bluebird": "^3.7.2",
"express": "^4.17.1",
"imagemagick": "^0.1.3",
"@google-cloud/firestore": "^4.9.9",
"@google-cloud/storage": "^5.8.3"
}
}
Cloud Storage ライブラリは、Cloud Storage 内の画像ファイルを読み取って保存するために使用されます。Firestore が画像のメタデータを更新します。Express は JavaScript / Node ウェブ フレームワークです。ボディパーサー モジュールを使用すると、受信リクエストを簡単に解析できます。Bluebird は Promise の処理に使用され、Imagemagick は画像操作用のライブラリです。
Dockerfile
Dockerfile
には、アプリケーションのコンテナ イメージを定義します。
FROM node:14-slim
# installing Imagemagick
RUN set -ex; \
apt-get -y update; \
apt-get -y install imagemagick; \
rm -rf /var/lib/apt/lists/*; \
mkdir /tmp/original; \
mkdir /tmp/thumbnail;
WORKDIR /picadaily/services/thumbnails
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]
ベースイメージは Node 14 で、画像操作には imagemagick ライブラリが使用されます。オリジナルとサムネイルの画像ファイルを格納するため、いくつかの一時ディレクトリが作成されます。次に、コードに必要な NPM モジュールをインストールしてから、npm start
でコードを開始します。
index.js
このプログラムの機能をより深く理解するために、コードを細かく見てみましょう。
const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const app = express();
app.use(express.json());
まず必要な依存関係を用意し、Express ウェブ アプリケーションを作成します。さらに、JSON ボディパーサーを使用するよう指定します。これは、受信リクエストは実際には POST リクエストを介してアプリケーションに送信される JSON ペイロードにすぎません。
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
これらの受信ペイロードを / ベース URL で受け取り、コードをエラーロジック処理でラップします。これにより、Google Cloud ウェブ コンソールの Stackdriver Logging インターフェースから表示されるログを確認することで、コードでエラーが発生している理由についてより適切な情報を得ることができます。
const pubSubMessage = req.body;
console.log(`PubSub message: ${JSON.stringify(pubSubMessage)}`);
const fileEvent = JSON.parse(Buffer.from(pubSubMessage.message.data, 'base64').toString().trim());
console.log(`Received thumbnail request for file ${fileEvent.name} from bucket ${fileEvent.bucket}`);
Cloud Run プラットフォームでは、Pub/Sub メッセージは、次の形式の JSON ペイロードとして HTTP POST リクエストを介して送信されます。
{
"message": {
"attributes": {
"bucketId": "uploaded-pictures",
"eventTime": "2020-02-27T09:22:43.255225Z",
"eventType": "OBJECT_FINALIZE",
"notificationConfig": "projects/_/buckets/uploaded-pictures/notificationConfigs/28",
"objectGeneration": "1582795363255481",
"objectId": "IMG_20200213_181159.jpg",
"payloadFormat": "JSON_API_V1"
},
"data": "ewogICJraW5kIjogInN0b3JhZ2Ujb2JqZWN...FQUU9Igp9Cg==",
"messageId": "1014308302773399",
"message_id": "1014308302773399",
"publishTime": "2020-02-27T09:22:43.973Z",
"publish_time": "2020-02-27T09:22:43.973Z"
},
"subscription": "projects/serverless-picadaily/subscriptions/gcs-events-subscription"
}
この JSON ドキュメントで非常に興味深いのは、message.data
属性の内容です。これは単なる文字列ですが、実際のペイロードを Base 64 にエンコードします。そのため、上記のコードではこの属性の Base 64 コンテンツをデコードしています。デコードされた data
属性には、Cloud Storage イベントの詳細を表す別の JSON ドキュメントが含まれます。ドキュメントはメタデータの中でも、ファイル名とバケット名を示します。
{
"kind": "storage#object",
"id": "uploaded-pictures/IMG_20200213_181159.jpg/1582795363255481",
"selfLink": "https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg",
"name": "IMG_20200213_181159.jpg",
"bucket": "uploaded-pictures",
"generation": "1582795363255481",
"metageneration": "1",
"contentType": "image/jpeg",
"timeCreated": "2020-02-27T09:22:43.255Z",
"updated": "2020-02-27T09:22:43.255Z",
"storageClass": "STANDARD",
"timeStorageClassUpdated": "2020-02-27T09:22:43.255Z",
"size": "4944335",
"md5Hash": "QzBIoPJBV2EvqB1EVk1riw==",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg?generation=1582795363255481&alt=media",
"crc32c": "hQ3uHg==",
"etag": "CLmJhJu08ecCEAE="
}
画像とバケット名に注目します。このコードでは、サムネイル処理のためにバケットから画像を取得します。
const bucket = storage.bucket(fileEvent.bucket);
const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);
const originalFile = path.resolve('/tmp/original', fileEvent.name);
const thumbFile = path.resolve('/tmp/thumbnail', fileEvent.name);
await bucket.file(fileEvent.name).download({
destination: originalFile
});
console.log(`Downloaded picture into ${originalFile}`);
環境変数から出力ストレージ バケットの名前を取得しています。
ファイル作成によって Cloud Run サービスがトリガーされた送信元バケットと、生成されたイメージを保存する宛先バケットがあります。imagemagick ライブラリはサムネイルをローカルの /tmp
一時ディレクトリに作成するので、path
組み込み API を使用してローカル ファイルを処理します。await
を実行して、アップロードされた画像ファイルをダウンロードするための非同期呼び出しを行います。
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
imagemagick モジュールは async
/ await
にあまり適していないため、JavaScript の Promise(Bluebird モジュールで提供されている)でまとめます。次に、ソースファイルと宛先ファイルのパラメータと、作成するサムネイルのサイズを指定して、作成した非同期のサイズ変更 / 切り抜き関数を呼び出します。
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
サムネイル ファイルが Cloud Storage にアップロードされると、Cloud Firestore のメタデータも更新され、この画像のサムネイルが実際に生成されたことを示すブール値フラグが追加されます。
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(fileEvent.name);
await doc.set({
thumbnail: true
}, {merge: true});
console.log(`Updated Firestore about thumbnail creation for ${fileEvent.name}`);
res.status(204).send(`${fileEvent.name} processed`);
リクエストが終了したら、ファイルが適切に処理されたという HTTP POST リクエストに応答します。
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
ソースファイルの最後に、Express でウェブ アプリケーションをデフォルト ポート 8080 で起動する手順が記載されています。
7. ローカルでテストする
コードをローカルでテストして、クラウドにデプロイする前に機能することを確認する。
thumbnails/nodejs
フォルダ内で npm の依存関係をインストールし、サーバーを起動します。
npm install; npm start
問題がなければ、サーバーがポート 8080 で起動します。
Started thumbnail generator on port 8080
CTRL-C
を使用して終了します。
8. コンテナ イメージをビルドして公開する
Cloud Run はコンテナを実行しますが、まずコンテナ イメージ(Dockerfile
で定義)をビルドする必要があります。Google Cloud Build を使用してコンテナ イメージをビルドし、Google Container Registry にホストできます。
Dockerfile
がある thumbnails/nodejs
フォルダ内で、次のコマンドを実行してコンテナ イメージをビルドします。
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
1 ~ 2 分後にビルドが成功するはずです。
Cloud Build の「履歴」セクションにもビルドの成功が表示されます。
ビルド ID をクリックすると、[ビルド アーティファクト] の詳細ビューが表示されます。タブを開くと、コンテナ イメージが Cloud Registry(GCR)にアップロードされたことがわかります。
必要に応じて、コンテナ イメージが Cloud Shell でローカルで実行されていることを再確認できます。
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
コンテナのポート 8080 でサーバーを起動します。
Started thumbnail generator on port 8080
CTRL-C
を使用して終了します。
9. Cloud Run へのデプロイ
Cloud Run にデプロイする前に、Cloud Run のリージョンをサポートされているいずれかのリージョンに設定し、プラットフォームを managed
に設定します。
gcloud config set run/region europe-west1 gcloud config set run/platform managed
構成が設定されていることを確認できます。
gcloud config list ... [run] platform = managed region = europe-west1
次のコマンドを実行して、Cloud Run にコンテナ イメージをデプロイします。
SERVICE_NAME=thumbnail-service gcloud run deploy $SERVICE_NAME \ --image gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service \ --no-allow-unauthenticated \ --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS
--no-allow-unauthenticated
フラグに注意してください。これにより、Cloud Run サービスは内部サービスとなり、特定のサービス アカウントによってのみトリガーされます。
デプロイに成功すると、次の出力が表示されます。
Cloud コンソール UI に移動すると、サービスが正常にデプロイされたことも表示されます。
10. Pub/Sub 経由で Cloud Storage イベントを Cloud Run へ送信
サービスは準備できましたが、新しく作成した Cloud Run サービスに対して Cloud Storage イベントを作成する必要があります。Cloud Storage は Cloud Pub/Sub 経由でファイル作成イベントを送信できますが、送信するにはいくつかの手順があります。
通信パイプラインとして Pub/Sub トピックを作成します。
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
ファイルがバケットに保存されるときに Pub/Sub 通知を作成します。
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
後で作成する Pub/Sub サブスクリプションのサービス アカウントを作成します。
SERVICE_ACCOUNT=$TOPIC_NAME-sa gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name "Cloud Run Pub/Sub Invoker"
サービス アカウントに、Cloud Run サービスを呼び出す権限を付与します。
SERVICE_NAME=thumbnail-service gcloud run services add-iam-policy-binding $SERVICE_NAME \ --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \ --role=roles/run.invoker
2021 年 4 月 8 日以前に Pub/Sub サービス アカウントを有効にした場合は、Pub/Sub サービス アカウントに iam.serviceAccountTokenCreator
ロールを付与します。
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)') gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \ --role=roles/iam.serviceAccountTokenCreator
IAM の変更が反映されるまで数分かかることがあります。
最後に、このサービス アカウントを使用して Pub/Sub サブスクリプションを作成します。
SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)') gcloud pubsub subscriptions create $TOPIC_NAME-subscription --topic $TOPIC_NAME \ --push-endpoint=$SERVICE_URL \ --push-auth-service-account=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com
サブスクリプションが作成されたことを確認できます。コンソールで Pub/Sub に移動し、gcs-events
トピックを選択すると、下部にサブスクリプションが表示されます。
11. サービスをテストする
設定が機能しているかどうかをテストするには、新しい画像を uploaded-pictures
バケットにアップロードし、thumbnails
バケットにサイズ変更した新しい画像が想定どおりに表示されることを確認します。
また、ログを再確認して、Cloud Run サービスのさまざまなステップで実施されているロギング メッセージを確認することもできます。
12. クリーンアップ(オプション)
シリーズの他のラボを継続する予定がない場合は、リソースをクリーンアップすることで費用を節約し、クラウド全般に精通するようにしてください。次のようにして、リソースを個別にクリーンアップできます。
バケットを削除します。
gsutil rb gs://$BUCKET_THUMBNAILS
次のコマンドで Service を削除します。
gcloud run services delete $SERVICE_NAME -q
Pub/Sub トピックを削除します。
gcloud pubsub topics delete $TOPIC_NAME
また、プロジェクト全体を削除することもできます。
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. お疲れさまでした
これですべて完了です。
- 新しい画像がアップロードされるとトピックに関する Pub/Sub メッセージを送信する通知を Cloud Storage に作成しました。
- 必要な IAM バインディングとアカウントを定義している(すべて自動化されている Cloud Functions とは異なり、ここでは手動で構成する)。
- Cloud Run サービスが Pub/Sub メッセージを受信できるようにサブスクリプションを作成しました。
- 新しい画像がバケットにアップロードされるたびに、新しい Cloud Run サービスによって画像のサイズが変更されます。
学習した内容
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub