このドキュメントに書いてあること
これまで Google Cloud Functionsをローカル環境でテストするときは、cloud-functions-emulator という公式で提供されているツールが一般的に利用されていました。しかしながらこのツールは現在archiveされており、作者が2019年5月16日に作成した issueによると functions-framework という新しいツールの利用を推奨しています。
このドキュメントでは、functions-framework を利用して Google Cloud FunctionsをLocalから実行する方法、特に公式では提供されていない eventsトリガーを利用してpubsubのメッセージを読み込ませる方法について記載します。
※ なお、2019年6月26日になっても公式ドキュメントでは @google-cloud/functions-emulator
を利用するようにと書かれています。
https://cloud.google.com/functions/docs/emulator?hl=ja#getting_started
functions-frameworkとは?
Node.jsを利用してFaaSを書くためのOSSのフレームワークです。このフレームワークを利用して書かれたコードは Cloud Functionsだけでなく、Cloud Runなどでも利用することができます。
そのようなことを目的としていると公式ドキュメントには書かれているものの、Cloud Run複数のAPIを持つ場合のケースに対応しておらず、現状では Cloud Functionsを Localで起動するためのツールとして使われることがメインになるかと思います。Cloud Functionsでの利用であれば、Localの開発環境のみinstallするだけですぐに利用することができます。
npm i -D @google-cloud/functions-framework
このあたりの実装を読んでみると、内部でexpressサーバーを起動して、利用者が作成した functionをラッピングしていることが見て取れます。実行は簡単で、ラッピングしたいfunctionを下記のように指定してコマンドを実行すれば動きます。
npx functions-framework --target=helloWorld
実際に動かしてみる
プログラムの用意
僕が実際に利用しているプログラムからの抜粋です。cloud pubsubのmessageを受け取った後、その中身を見てSlackに通知しています。
export async function slack_reporter(data: any) {
const dataBuffer = Buffer.from(data.data, "base64");
const body = dataBuffer.toString("ascii");
const client = await SlackClient.create();
await client.post(body);
}
import { WebAPICallResult, WebClient } from "@slack/client";
export class SlackClient {
public static async create(): Promise<SlackClient> {
if (!this.instance) {
const token = process.env.SLACK_TOKEN as string;
const channel = process.env.SLACK_CHANNEL_ID as string;
this.instance = new SlackClient(token, channel);
}
return this.instance;
}
private static instance: SlackClient;
private slackCleint: WebClient;
private channel: string;
constructor(token: string, channel: string) {
this.slackCleint = new WebClient(token);
this.channel = channel;
}
public async post(text: string): Promise<WebAPICallResult> {
return this.slackCleint.chat.postMessage({
username: "ERP-HR Bot",
channel: this.channel,
text,
});
}
}
pubsubのmessageを作成する
pubsubで送信されるmessageのフォーマットについては、公式ドキュメントによると下記のように指定されています。
{
"data": string,
"attributes": {
string: string,
...
},
"messageId": string,
"publishTime": string
}
このように色々なパラメータが存在していますが、今回のケースで利用するのは data
のみです。理由については公式ドキュメントに記載されています。ということで messageを作成していきます。この data
パラメータはbase64でエンコードする必要があるため、下記のコマンドでエンコードします。
$ echo -n "hogehoge" | base64
aG9nZWhvZ2U=
これをmessageで送信するjsonに組み込むと下記のようになります。
{
"data": "aG9nZWhvZ2U="
}
Local環境でCloud Functionsを起動する
公式のドキュメントによると、functions frameworkを起動する際のoptionは --port, --target, --signature-typeの3点です。ここでは実行したい functionは slack_reporter
であり、トリガーはpubsubにしたいので、下記のように設定をしました。
$ npx functions-framework --target=slack_reporter --signature-type=event
Serving function...
Function: slack_reporter
URL: http://localhost:8080/
呼び出し
Cloud FunctionもLocalで起動したので、次は起動しているCloud Functionを実行していきます。ここで Functions Frameworkのissueを読んでいくと次のようなissueが見つかります。
そしてこちらのPR上で、どのようにmessageを送信することが適切なのか議論されています。ということで、これから記述する方法は将来正しくない方法になってしまう可能性がありますが、とはいえ今試す必要がある人がいるとも思うので書いておきます。
curlを使って下記のように実行すると、Localで動いているCloud Functionsが pubsubのメッセージを読み込むことができます。
$ curl -X POST -H 'Content-Type:application/json; charset=utf-8' \
-H 'ce-type: xxx' \
-H 'ce-specversion: xxx' \
-H 'ce-source: xxx' \
-H 'ce-id: xxx' \
-d "$(cat mock_pubsub.json)" http://localhost:8080
すると、こんな感じでslackに通知されました。めでたしめでたし。
この理由については、このあたりのコードに書いてあるのですが、もうちょっと追加調査したいことがあるので、またの機会に書こうと思います〜。