4月はGCP素振り月間と銘打っていろいろと触り始めたのでまぁそうだよねみたいな話多めですがやったこと残しておきます
Cloud Functions + Pub/Sub + GCS
Functions Framework
Cloud Functionsなどの関数を開発するためのフレームワーク
公式の説明は下記
Functions Framework | Google Cloud Functions に関するドキュメント
Functions Frameworkを用いることでCloud FunctionsやCloud Runなどで使う関数をローカルで開発できる
クイックスタートではHTTPリクエストを受け付ける関数の想定のよう
とりあえずクイックスタートは数分で行えるのでやっみて「なるほど」って感じ
注: 現在、Function Frameworks は Node と Go の関数に対応しています。
今回はNodeで進める
Pub/Subと連携するCloud Functions
今回やってみたかったのは「軽量タスクをPub/Sub経由で実行する」関数
探してみたところ下記記事でFunctions FrameworkとPub/Subで開発するときのTipsが乗っていたのでそれに習ってやってみた
functions-framework を利用したGoogle Cloud Functionsにおいてpubsubのテストをする方法 - selmertsxの素振り日記
ローカルでの開発
- インストール
cd func_dir npm init npm install -D @google-cloud/functions-framework
- 関数を書く
最初の数行はWeb UIからPub/Subイベントを購読する関数を作成した場合についてくるコメント
例としてはてなブックマークのAPIをたたいてレスポンスをそのままGCSへ保存する関数を書いた
/** * Triggered from a message on a Cloud Pub/Sub topic. * * @param {!Object} event Event payload. * @param {!Object} context Metadata for the event. */ const fetch = require('node-fetch'); const {Storage} = require('@google-cloud/storage'); exports.collectHatenaCount = async (event, context) => { const data = Buffer.from(event.data, 'base64').toString(); const params = JSON.parse(data); const urlBase = 'http://b.hatena.ne.jp/entry/jsonlite/?url='; const requestUrl = `${urlBase}${params.url}`; const bucketName = process.env['BUCKET']; const storage = new Storage(); const res = await fetch(requestUrl).then((res) => { return res.json(); }); const bucket = storage.bucket(bucketName); const file = bucket.file(`share-count/service=${params.service}/${encodeURIComponent(params.url)}`); await file.save(JSON.stringify(res)); };
Buffer.from(.....
でbase64デコードしているのはPub/Subのイベントデータがbase64エンコードされた値で送られてくるから
他は特にURLもらってAPIたたいてそのままレスポンスをGCSへ保存するという流れ
インストール手順とは別途で下記もインストールする
npm install @google-cloud/storage node-fetch
サーバ立ち上げ
--signature-type
はhttp
かevent
が選択できる、今回はPub/Subを想定しているのでevent
を指定
--target
は実行する関数名
$ npx functions-framework --target=collectHatenaCount --signature-type=event > shared-count-hatena@1.0.0 start /home/vagrant/sandbox/shared-count/hatena > functions-framework --target=collectHatenaCount --signature-type=event Serving function... Function: collectHatenaCount URL: http://localhost:8080/
イベント確認
Pub/Subから送られてくるデータはbase64エンコードされているのでCurlで確認する際も下準備が必要
これは実際にPub/Subへメッセージをpublishして開発と同じコードにできるよう調整した
curl -XPOST http://localhost:8080/ -H 'Content-Type:application/json; charset=utf-8' -d "{\"data\": {\"data\": \"$(cat event.json|base64|tr -d '\n')\"}}"
- event.json
{ "url": "https://swfz.hatenablog.com/entry/2019/03/03/233624", "service": "hatena" }
デプロイまで
ローカルでの開発が終わったらデプロイまでやりたいところですね
以降はgcloud
コマンドで必要なリソースなどを作成していきます
Pub/SubのTopic作成
gcloud pubsub topics create | Cloud SDK のドキュメント | Google Cloud
$ gcloud pubsub topics create memo-collector-hatena
これだけでTopicができる、めちゃくちゃ簡単…
関数のデプロイ
ローカルマシンからのデプロイ | Google Cloud Functions に関するドキュメント
詳しいオプションなどはこちら
gcloud functions deploy | Cloud SDK のドキュメント | Google Cloud
$ gcloud functions deploy collectHatenaCount --trigger-topic=memo-collector-hatena --runtime nodejs10 --region asia-northeast1 --set-env-vars BUCKET=memo-raw-data
--trigger-topic
でTopicを指定するとそのTopicにメッセージが送られたそのメッセージの内容を読み取って関数が実行される
事前に作成していたTopic名を指定する
環境変数の設定は--set-env-vars
で行う、環境によってバケット名を変えているので指定が必要
これで該当のPub/Sub Topicを購読する関数がデプロイできた
ほか
puppeteer使う場合はそれなりにメモリも消費するようでデフォルト(256MB)だとエラーが発生した
Error: memory limit exceeded. Function invocation was interrupted.
というエラーになってしまったのでdeploy時のオプションで対応
--memory=512MB
npmモジュールの扱い
package.jsonに記述したモジュールをよしなにインストールしてくれる模様
Node.js での依存関係の指定 | Google Cloud Functions に関するドキュメント
Lambdaだと大きめのモジュール入れる際は容量制限がある、そのためLayer使うにしてもそちらでも容量制限があるのでpuppeteer使うのすごく面倒だった記憶がある
一方Cloud Functionsはデプロイまでに意識することが少なく
- npm installしてローカルで開発
- 動作確認して
gcloud functions deploy
たたく - ちゃんと動く!
と、数ステップでデプロイまで行えたのでとてもやりやすい印象を持った
公式を見ると
注: Functions Framework を使用して、Node.js Cloud Functions のコンテナ化されたバージョンを Cloud Run にデプロイすることもできます。
ちょっと手を加えればCloud Runにもデプロイできるのか? ということで時間作って試してみようと思っています
CLIからメッセージを送る
クイックスタート: gcloud コマンドライン ツールの使用 | Cloud Pub/Sub ドキュメント | Google Cloud
動作確認のためCLIからPub/Subへメッセージを送ってみる(URLは別のURLにしています)
$ gcloud pubsub topics publish memo-collector-hatena --message '{"url":"https://example.com","service":"hatena"}'
無事保存されました
まとめ
- Functions Frameworkでローカル開発を簡単に行えた
- gcloudコマンドでCloud Functionsのデプロイまで簡単に行えた
- せっかくCloud Functionをローカルで開発できるようにしたがStorageに関しては開発用にバケット作る必要があるのでモックなどは今後調べて取り組む
- fsouza/fake-gcs-server: Google Cloud Storage emulator & testing library. をつかってみたがうまく使えなかったため断念→別途もう少し調べる
- 今の時点(2020-04-12)でnode10が最新なのは少し微妙感がある…
- 意識することがLambdaと比べて少ないので簡単な関数だったらCloud Functionsのほうが良いなと感じた
余談
Twitterやはてぶなどの数値はGAS経由で取得していたものの、いくつかエラーでずっと数値が取れない現象に陥ってしまいずっと放置していました
GASだとデバッグなどやりづらいなーと思っていたのとスプレッドシート運用もそろそろどうかなと思っていたのでこの機会に刷新しようと試み中
大分オーバースペック感はあるもののテーマ決めてそれに向けて開発するのは楽しいですね
最後に今回使用したコードを貼っておきます
shared-count/hatena at master · swfz/shared-count
リポジトリ自体はCloud Functions以外も試しているので興味があれば見ていただければと思います