Pic-a-daily: ラボ 3 — 最近撮影した写真のコラージュを作成する

1. 概要

このコードラボでは、Cloud Scheduler によって定期的にトリガーされる新しい Cloud Run サービス(コラージュ サービス)を作成します。このサービスは、アップロードされた最新の写真を取得して、それらの写真のコラージュを作成します。Cloud Firestore で最近の写真のリストを見つけ、Cloud Storage から実際の写真ファイルをダウンロードします。

df20f5d0402b54b4.png

学習内容

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

Cloud Shell の起動

Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。

GCP Console で右上のツールバーにある Cloud Shell アイコンをクリックします。

bce75f34b2c53987.png

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

f6ef2b5f13479f3a.png

この仮想マシンには、必要な開発ツールがすべて用意されています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働します。そのため、ネットワークのパフォーマンスと認証機能が大幅に向上しています。このラボでの作業はすべて、ブラウザから実行できます。

3. API を有効にする

Cloud Run サービスを定期的にトリガーするには、Cloud Scheduler が必要です。有効になっていることを確認します。

gcloud services enable cloudscheduler.googleapis.com

オペレーションが正常に完了したことを確認します。

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. コードのクローンを作成する

前のコードラボでまだ行っていない場合は、コードのクローンを作成します。

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

その後、サービスを含むディレクトリに移動できます。

cd serverless-photosharing-workshop/services/collage/nodejs

サービスのファイル レイアウトは次のようになります。

services
 |
 ├── collage
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

フォルダ内には次の 3 つのファイルがあります。

  • index.js には Node.js コードが含まれています
  • package.json は、ライブラリの依存関係を定義します
  • Dockerfile は、コンテナ イメージを定義します。

5. コードを探索する

依存関係

package.json ファイルは、必要なライブラリの依存関係を定義します。

{
  "name": "collage_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 ライブラリを使用します。以前に保存した写真のメタデータを取得するために、Cloud 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/*

WORKDIR /picadaily/services/collage
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]

軽量の Node 14 ベースイメージを使用しています。imagemagick ライブラリをインストールしています。次に、コードに必要な NPM モジュールをインストールし、npm start でノードコードを実行します。

index.js

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

プログラムの実行に必要なさまざまな依存関係があります。Express は使用する Node ウェブ フレームワーク、ImageMagick は画像操作を行うためのライブラリ、Bluebird は JavaScript の Promise を処理するためのライブラリ、Path はファイルとディレクトリのパスを処理するために使用されます。Storage と Firestore は、それぞれ Google Cloud Storage(画像のバケット)と Cloud Firestore データストアを操作するために使用されます。

const app = express();

app.get('/', async (req, res) => {
    try {
        console.log('Collage request');

        /* ... */

    } catch (err) {
        console.log(`Error: creating the collage: ${err}`);
        console.error(err);
        res.status(500).send(err);
    }
});

上記の Node ハンドラの構造では、アプリが HTTP GET リクエストに応答します。また、エラーが発生した場合の処理も行っています。この構造の中身を見てみましょう。

const thumbnailFiles = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
    .where('thumbnail', '==', true)
    .orderBy('created', 'desc')
    .limit(4).get();

if (snapshot.empty) {
    console.log('Empty collection, no collage to make');
    res.status(204).send("No collage created.");
} else {

    /* ... */

}

コラージュ サービスには、サムネイルが生成された写真が少なくとも 4 枚必要です。まず 4 枚の写真をアップロードしてください。

Cloud Firestore に保存されているメタデータから、ユーザーがアップロードした最新の 4 枚の写真を取得します。結果のコレクションが空かどうかを確認し、コードの else ブランチでさらに処理を進めます。

ファイル名のリストを収集しましょう。

snapshot.forEach(doc => {
    thumbnailFiles.push(doc.id);
});
console.log(`Picture file names: ${JSON.stringify(thumbnailFiles)}`);

これらの各ファイルは、サムネイル バケットからダウンロードします。バケット名は、デプロイ時に設定した環境変数から取得します。

const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);

await Promise.all(thumbnailFiles.map(async fileName => {
    const filePath = path.resolve('/tmp', fileName);
    await thumbBucket.file(fileName).download({
        destination: filePath
    });
}));
console.log('Downloaded all thumbnails');

最新のサムネイルがアップロードされたら、ImageMagick ライブラリを使用して、それらのサムネイル画像の 4x4 グリッドを作成します。Bluebird ライブラリとその Promise 実装を使用して、コールバック駆動型のコードを async / await フレンドリーなコードに変換し、画像コラージュを作成する Promise を待機します。

const collagePath = path.resolve('/tmp', 'collage.png');

const thumbnailPaths = thumbnailFiles.map(f => path.resolve('/tmp', f));
const convert = Promise.promisify(im.convert);
await convert([
    '(', ...thumbnailPaths.slice(0, 2), '+append', ')',
    '(', ...thumbnailPaths.slice(2), '+append', ')',
    '-size', '400x400', 'xc:none', '-background', 'none',  '-append',
    collagePath]);
console.log("Created local collage picture");

コラージュ写真は一時フォルダのローカル ディスクに保存されているため、Cloud Storage にアップロードしてから、成功レスポンス(ステータス コード 2xx)を返す必要があります。

await thumbBucket.upload(collagePath);
console.log("Uploaded collage to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}");

res.status(204).send("Collage created.");

次に、Node スクリプトが受信リクエストをリッスンするようにします。

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started collage service on port ${PORT}`);
});

ソースファイルの最後に、Express がデフォルトのポート 8080 でウェブ アプリケーションを実際に起動する手順があります。

6. ローカルでテストする

コードをローカルでテストして、クラウドにデプロイする前に動作することを確認します。

collage/nodejs フォルダ内で、npm の依存関係をインストールしてサーバーを起動します。

npm install; npm start

すべてがうまくいけば、ポート 8080 でサーバーが起動します。

Started collage service on port 8080

終了するには CTRL-C を使用します。

7. ビルドして 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 Build を使用してコンテナ イメージを手動でビルドして公開する代わりに、Cloud Run を使用して Google Cloud Buildpacks でコンテナ イメージをビルドすることもできます。

次のコマンドを実行してコンテナ イメージをビルドします。

BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT
SERVICE_NAME=collage-service
gcloud run deploy $SERVICE_NAME \
    --source . \
    --no-allow-unauthenticated \
    --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS

–-source フラグに注意してください。これは、Cloud Run のソースベースのデプロイ です。Dockerfile がソースコード ディレクトリにある場合、アップロードしたソースコードはその Dockerfile を使用してビルドされます。ソースコード ディレクトリに Dockerfile が存在しない場合、Google Cloud Buildpacks は、使用されている言語を自動的に検出してコードの依存関係を取得し、Google が管理する安全なベースイメージを使用して本番環境に対応したコンテナ イメージを作成します。これにより、Cloud Run は Google Cloud Buildpacks を使用して Dockerfile で定義されたコンテナ イメージをビルドします。

ソースベースのデプロイでは、Artifact Registry を使用してビルドされたコンテナが保存されます。Artifact Registry は、Google Container Registry の最新バージョンです。プロジェクトで API がまだ有効になっていない場合、CLI は API を有効にするよう求め、デプロイ先のリージョンに cloud-run-source-deploy という名前のリポジトリを作成します。

--no-allow-unauthenticated フラグを指定すると、Cloud Run サービスは特定のサービス アカウントによってのみトリガーされる内部サービスになります。

8. Cloud Scheduler を設定する

Cloud Run サービスが準備され、デプロイされたので、毎分サービスを呼び出す定期的なスケジュールを作成します。

サービス アカウントの作成:

SERVICE_ACCOUNT=collage-scheduler-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
   --display-name "Collage Scheduler Service Account"

サービス アカウントに Cloud Run サービスを呼び出す権限を付与します。

gcloud run services add-iam-policy-binding $SERVICE_NAME \
   --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --role=roles/run.invoker

1 分ごとに実行される Cloud Scheduler ジョブを作成します。

SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)')
gcloud scheduler jobs create http $SERVICE_NAME-job --schedule "* * * * *" \
   --http-method=GET \
   --location=europe-west1 \
   --uri=$SERVICE_URL \
   --oidc-service-account-email=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
   --oidc-token-audience=$SERVICE_URL

Cloud Console の Cloud Scheduler セクションに移動すると、設定が完了し、Cloud Run サービスの URL を指していることがわかります。

35119e28c1da53f3.png

9. サービスをテストする

設定が機能しているかどうかをテストするには、thumbnails バケットでコラージュ画像(collage.png)を確認します。サービスのログを確認することもできます。

93922335a384be2e.png

10. クリーンアップ(省略可)

シリーズの他のラボを続行する予定がない場合は、リソースをクリーンアップして費用を節約し、クラウドのマナーを守りましょう。リソースを個別にクリーンアップするには、次の操作を行います。

次のコマンドで Service を削除します。

gcloud run services delete $SERVICE_NAME -q

Cloud Scheduler ジョブを削除します。

gcloud scheduler jobs delete $SERVICE_NAME-job -q

または、プロジェクト全体を削除することもできます。

gcloud projects delete $GOOGLE_CLOUD_PROJECT

11. 完了

おめでとうございます!スケジュール設定されたサービスを作成しました。Cloud Scheduler が Pub/Sub トピックに毎分メッセージを push するため、Cloud Run コラージュ サービスが呼び出され、写真を結合して結果の画像を作成できます。

学習した内容

  • Cloud Run
  • Cloud Scheduler
  • Cloud Storage
  • Cloud Firestore

次のステップ