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

1. 概要

この Codelab では、新しい Cloud Run サービスであるコラージュ サービスを作成します。このサービスは、Cloud Scheduler によって定期的にトリガーされます。このサービスは、アップロードされた最新の写真を取得し、それらの写真のコラージュを作成します。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. コードのクローンを作成する

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

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 Firerstore に保存されているメタデータから、ユーザーがアップロードした最新の 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 Buildpack は、使用している言語を自動的に検出し、コードの依存関係を取得して、Google が管理する安全なベースイメージを使用して本番環境対応のコンテナ イメージを作成します。これにより、Cloud Run は Google Cloud Buildpacks を使用して、Dockerfile で定義されたコンテナ イメージをビルドします。

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

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

8. Cloud Scheduler を設定する

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

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

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 コンソールの [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 トピックで 1 分ごとにメッセージを push するので、Cloud Run のコラージュ サービスが呼び出され、複数の画像をまとめて追加して画像を作成できます。

学習した内容

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

次のステップ