Cloud Functions の関数をローカルで開発してテストする方法
Google Cloud Japan Team
サーバーレス アプリケーションの開発時に優れたデベロッパー エクスペリエンスを実現できるよう、ローカル開発環境を設定しましょう
※この投稿は米国時間 2022 年 12 月 16 日に、Google Cloud blog に投稿されたものの抄訳です。
サーバーレス プラットフォーム向けのコードを開発するには、開発フローに対して通常とは異なるアプローチを取る必要があります。サーバーレス プラットフォームは、コンピューティング インフラストラクチャから関数ハンドラに至るまで、スタック全体を提供、維持しているため、コードはフルマネージド型の抽象化された環境で実行されます。そのため、クラウド上で Cloud Functions の関数をデプロイして呼び出し、コードのデバッグを行うのは、時間のかかる非効率的な作業になる場合があります。幸いにも、Cloud Functions では、より迅速なコードの実装とデバッグを実現するための代替手段を提供しています。
このブログ投稿では、以下の方法についてご紹介します。
ローカルで Cloud Functions の関数を実行する
ローカルで Eventarc イベントを使用して Cloud Functions の関数を呼び出す
クラウドにデプロイされた場合と同じ権限を使用する
リモートに保存されたシークレットを Secret Manager から取得する
ローカルの Cloud Functions の関数内の Visual Studio Code でブレークポイントを設定する
オープンソースの Functions Framework を基盤とした Cloud Functions
Google Cloud は、オープン スタンダードとオープンソースを強く推進し、連携を図っています。Cloud Functions も例外ではありません。
Google は、永続的な HTTP アプリケーション内で関数コードをラップするための、完全にオープンソース化されたランタイム環境を提供しており、これは Functions Framework と呼ばれます。Functions Framework により、デベロッパーは自分のマシンやその他の場所で、Cloud Functions と同じランタイム環境を実行できます。デベロッパーは、サーバーレス環境がどのように動作するか推測したり、どのように本番環境をエミュレートするかについて心配したりする必要がなくなります。上図のように、Cloud Functions(第 2 世代)の関数は、実際には Google のサーバーレス コンテナのインフラストラクチャでホストされるコンテナを表しています。Google はコンテナのオペレーション システム、必要な言語ランタイム、Functions Framework をすべて提供しています。こうしたすべてのレイヤは、デプロイ プロセス中に Cloud Native Buildpacks を使用して、関数コードとその依存関係とともにパッケージ化されます。
実際の例
ここでは、毎日数千の画像を処理する TypeScript アプリケーションの一般的な例をご紹介します。このアプリケーションは画像から分析情報を導き出し、その結果として得られるオブジェクト ラベルを保存します。次の図は、このシナリオをイベント ドリブン マイクロサービスとして示しています。
Cloud Storage バケットに新しくアップロードされた画像ごとに、Eventarc によって Cloud Functions(第 2 世代)の関数が呼び出され、他のイベント メタデータを含む画像の場所が渡されます。TypeScript アプリケーション コードでは、Secret Manager に保存されたシークレットへのアクセス、Vision API を使用したオブジェクト ラベルの抽出、さらに、Cloud Firestore のドキュメント データベースでの画像のインデックス作成を行う必要があります。
次のコード スニペットは、関数コードを示しています。完全なサンプルは、こちらの GitHub リポジトリで入手できます。
この関数コードは、Google Cloud のエコシステムからの複数の依存関係を使用しています。
呼び出しをトリガーする Eventarc
ファイル名を暗号化するためのシークレットを提供する Secret Manager
アップロードされた画像のラベルを検出し取得する Cloud Vision API
結果を保存する Cloud Firestore
リポジトリ内の README ファイルには、このサンプル アプリケーションを Google Cloud プロジェクトにデプロイするために必要な手順がすべて記載されています。
次のセクションでは、Cloud Functions の外部で TypeScript 関数を実行するためのローカル開発環境の設定方法をご紹介します。この設定により、デプロイ前にローカルでテストを行い、コードのイテレーションを迅速に行えます。
Cloud Functions の関数をローカルで実行する方法
Functions Framework は、ローカルマシンやリモート開発用サーバーなど、対応言語をサポートするあらゆるプラットフォームにインストールして実行できます。
このサンプル関数のディレクトリで示されているように、次の CLI コマンドを使用して TypeScript 用の Functions Framework ライブラリをインストールできます。Python、Go、Java、C#、Ruby、PHP などの言語を使用している場合は、その言語に対応する Functions Framework のインストール方法に関するドキュメントを確認してください。このアプリケーションは TypeScript を使用しているため、ソースコードをコンパイルし、Functions Framework 用のソースとして「出力」フォルダを参照する必要があります。以下の package.json のコード スニペットによって、便利なホットリロードが可能になります。tsc と -w watch フラグを使用して、再コンパイルする必要のある変更がないかが監視されます。また、nodemon を使用して新たにコンパイルされたファイルの発生を検出し、ローカル Functions Framework を自動的に再起動します。
npm install
でアプリケーションの依存関係をすべてインストールした後、npm run watch
コマンドを使用してローカル エンドポイント http://localhost:8080/ を作成できます。
では、イベントによってトリガーされる Cloud Functions の関数のローカル エンドポイントの使用方法について見ていきましょう。
Eventarc イベントを使用して Cloud Functions の関数をローカルに呼び出す方法
関数コードは、Eventarc によって呼び出されることを想定しています。Eventarc では、イベントの構造にオープン スタンダードの CloudEvents 仕様を使用しています。Cloud Functions との直接統合には、HTTP リクエスト本文にイベント固有のヘッダー(タイプ、時間、ソース、サブジェクト、仕様のバージョンなど)とイベントデータが記述された本文が含まれる、HTTP プロトコル バインディングを使用しています。
想定される HTTP リクエストの構造とペイロードを使用して Cloud Functions のコードをローカルで呼び出すには、単純な curl コマンドを使用します。次の curl コマンドでは、image_bucket というバケットにアップロードされた画像ファイル CF_debugging_architecture.png の google.cloud.storage.object.v1.finalized イベントに対する HTTP プロトコル バインディングを実装し、localhost のポート 8080 でリッスンする Cloud Functions の関数を呼び出します。また、CloudEvents では CloudEvents Conformance という CLI ツールが提供されていて、CloudEvents のレシーバをテストできます。これは YAML ファイルを使用して、Cloud Storage バケットにアップロードされた画像の想定される Eventarc イベントを定義することにより、HTTP リクエストをフォーマットするのに役立ちます。こうした yaml のサンプルは GitHub リポジトリにあります。
このサンプル アプリケーションでは、CF_debugging_architecture.png ファイルが実際に存在し、image_bucket バケットに保存されていることを前提としています。ファイルが存在しない場合、ローカルでの実行はエラーが発生し終了します。
しかし、この Cloud Functions のサンプル関数は、Cloud Storage バケットや Vision API などの外部依存関係に依存しているため、呼び出すとすぐに UNAUTHENTICATED エラーをスローします。これに対する修正方法は、次のセクションで説明します。
要点をまとめると、単純な curl コマンドを使用することで、HTTP プロトコル バインディングによってアップロードされた画像の Eventarc イベントを送信し、ローカルでコードを呼び出すことができます。その他の Eventarc イベントの HTTP 本文の構造は、CloudEvents GitHub リポジトリで入手できます。
次に、ローカルの Cloud Functions の関数を認証する方法についてご紹介します。
クラウドにデプロイされた場合と同じ権限を使用する方法
Cloud Functions のサンプル関数では、Node.js クライアント ライブラリを使用して、Vision API や Firestore などの Google Cloud サービスを操作しています。クライアント ライブラリの認証は、Google Cloud にデプロイされたときに自動的に行われます。しかし、ローカルで作業している場合は、認証を構成する必要があります。
Google Cloud クライアント ライブラリは、アプリケーションのデフォルト認証情報(ADC)をサポートしているため、自動的に認証を処理します。ADC はアプリケーション環境に基づいて認証情報を自動的に検索し、その認証情報を Google Cloud APIs の認証に使用します。
Cloud Functions の関数をデプロイすると、Google Cloud からメタデータ サーバーを介してサービス アカウントの認証情報が提供されます。また、gcloud
を使用することで、gcloud auth application-default login
を介して ADC をローカルに設定できます。このコマンドは、ADC に使用するユーザー認証情報を取得します。さらに、--impersonate-service-account
フラグを使用することで、サービス アカウントの権限を借用できるようになり、Google Cloud APIs に対する認証にその ID を使用できます。サービス アカウントの権限を借用するには、ユーザー アカウントにサービス アカウント トークン作成者(roles/iam.serviceAccountTokenCreator)のロールが必要です。以下のコマンドでは、特定のサービス アカウントの ADC をローカルに設定しています。
このサンプル アプリケーションでは、Google Cloud にデプロイされたときに Cloud Functions の関数によって使用されたのと同じサービス アカウント(つまり関数の ID)を使用できます。プロビジョニングされた Cloud Functions の関数に関連付けられているサービス アカウントがわからない場合は、以下のコマンドを使用して、そのサービス アカウントのメールを取得できます。
同じサービス アカウントを使用することで、デプロイされた関数と同じ権限を使用してローカルでコードを実行できます。そのため、ローカルでの実行時にサービス アカウントの権限に対する変更を適切にテストできます。
このコマンドを実行し、npm run watch を使用してローカル開発用のサーバーを再起動すると、関数コードは Vision API と Firestore に対して認証されます。これで、コードを変更することなく、Cloud Functions に直接デプロイした場合と同様に、Google Cloud サービスを実行、利用できるようになりました。これにより、ローカルでの開発が非常に便利になります。
Cloud Functions の関数がクラウドで実行されると、Secret Manager によって構成されたすべてのシークレットが関数のコンテナに含められます。次のセクションでは、ローカル開発環境でこうしたシークレットを参照する方法についてご紹介します。
リモートに保存されたシークレットを Secret Manager から取得する方法
Cloud Functions のサービスを利用すると、お客様は Secret Manager を使って API キー、パスワード、その他の機密情報を安全に保管し、それらのシークレットを環境変数として渡す、または、ボリュームとしてマウントできます。しかし、ローカル実行環境には、このような自動的なメカニズムはありません。そのため、別の方法で Secret Manager に接続する必要があります。このセクションでは、サンプル アプリケーションがそのようなシークレットを使用し、環境変数 SECRET_API_KEY を介してシークレットにアクセスする方法を確認していきます。
シークレットを使用するために、ローカル開発環境向けのオプションがいくつか提供されています。
Secret Manager クライアント ライブラリを使用して、実行時にシークレットを直接リクエストできます。
gcloud
コマンドの gcloud secrets versions access
を使用して実行時にシークレットを取得し、自動化された方法で安全に環境変数としてシークレットを挿入できます。以下の package.json はこれを示しています。npm run watch
を実行すると、Secret Manager からの API キーのシークレットが SECRET_API_KEY
環境変数として、このノードプロセスに対してのみ設定されます。impersonate-service-account フラグを使用することで、この段階で、ローカル開発環境における実際的なシークレット権限の処理が可能になります。
ローカルの Cloud Functions の関数内の Visual Studio Code でブレークポイントを設定する方法
デバッグは統合開発環境(IDE)である Visual Studio Code のコア機能で、Cloud Functions の関数を開発する際にも非常に有用です。この IDE には Node.js ランタイムのデバッグ サポートが組み込まれていて、JavaScript、TypeScript、その他多くの言語をデバッグできます。
package.json の npm デバッグ スクリプトには、Functions Framework の実行時に --inspect
フラグが含まれます。
このフラグにより、Visual Studio Code は JavaScript Debugger をアタッチできるようになり、TypeScript ソースマップがサポートされます。ソースマップを使用すると、コードのシングル ステップ実行や、コンパイルされていない TypeScript ファイルへのブレークポイントの設定が可能になります。この Visual Studio Code に組み込まれたデバッグ機能を使用することにより、ローカル開発におけるデバッグ エクスペリエンスが向上します。
デバッガの自動アタッチメント機能を使用するように Visual Studio Code を構成した後、エディタの余白にブレークポイントを設定してからnpm run watch
を実行することで、デバッガモードが有効になります。curl または CloudEvents Conformance ツールのいずれかを使用してローカル エンドポイントを呼び出した場合、関数コードは指定されたブレークポイントで実行を一時停止します。これにより、現在の変数の割り当ての確認、コールスタックの調査など、さまざまなことができるようになります。上のスクリーンショットでは、ローカルでの実行時に、処理されたラベルをデバッグし labelsValues 変数を検査する方法を示しています。リモートの依存関係へのアクセスとホットリロードを組み合わせ、Visual Studio Code 内でデバッグを行うことによって、開発サイクルが高速化されます。なぜなら、わずか数秒でコードの変更をテストおよび検査できるためです。
まとめ
この記事では、外部依存関係を含み、Eventarc イベントによってトリガーされる TypeScript の Cloud Functions 関数をローカルで開発、デバッグする方法についてご紹介してきました。
Functions Framework と組み合わせてホットリロードを設定してデバッグを高速化し、変更のテストをわずか数秒で行う方法について学びました。さらに、ローカルの、権限を借用したアプリケーションのデフォルト認証情報を使用して、Cloud Functions にデプロイされた場合と同じ権限でローカル関数コードを実行する方法についてもご紹介しました。そして、Secret Manager からシークレットを取得する機能や、デバッグ中に Visual Studio Code でブレークポイントを使用する機能は、コードに変更を加える必要がないため、ローカル開発にとって非常に有用であることをご説明しました。
Google Cloud のオープン クラウド アプローチにより、サーバーレス アプリケーションをプログラミングする場合も、使い慣れた生産性の高い開発環境で作業することができます。Google Cloud のサーバーレス機能を活用することで、ローカル環境での開発とデバッグの高速化が実現します。
ご関心をお持ちの方は、以下のリソースをご活用ください。
- 戦略的クラウド エンジニア Christoph Stanger