GCP「Cloud Functions」を使ってみる

 さて、11月にブログを始めたものの、1回しか書かないまま1ヶ月以上が経ってしまいました。まさに「一日坊主」です・・・

 これはいかんということで、とりあえず前回のブログで宿題にしていたところを片付けます。

前回のあらすじ

 加工元のデータのダウンロード (および文字コード変換) を自動実行する仕組みがあれば、一連の作業を完全自動実行できそうだと思いました。・・・が、その場では具体的な方法まで手が回らずタイムアップとなってしまいました。
 家に帰ってから続きを考えてみましたので、その結果は次のブログエントリで書こうと思います。

 サイトからのダウンロードも文字コード変換もプログラムコードで書くのはそう難しくはないでしょうから、GCP の FaaS サービスである「Google Cloud Functions」を使えば自動化は実現できそうです。

 ところが、Google Cloud Functions にも AWS Lambda のようにスケジュール実行の機能があるのだろうと思いきや、そういう機能は用意されていませんでした。

Cloud Functions で定期的に処理を実行させるには?

 さてさて、どうしたものか・・・
 そこでグーグル先生に聞いたところ、あっさり答が出てきました。

Google Developers Japan: Cloud Functions for Firebase でジョブをスケジューリング(cron)する

 どうやら「Google App Engine」と「Google Cloud Pub/Sub」を組み合わせることにより実現できるようです。

 「App Engine (GAE)」は GCP の PaaS サービスで、cron によるスケジュール実行/バッチ処理の機能を持っています。
 「Cloud Pub/Sub」はメッセージングサービスで、AWS で言うと SNS (Simple Notification Service) に相当します。

f:id:greenwillow:20171125182410p:plain

 GAE のアプリで Pub/Sub へのメッセージ発行を行う処理を記述し、cron でスケジュール実行させます。
 「Cloud Functions」は関数を実行する契機 (トリガ) として「Cloud Pub/Sub にメッセージが届いた時」を指定できるため、上の図のように連係させることで Cloud Functions のスケジュール実行が実現できるという訳です。

GAE アプリの準備

 GAE で動作させるアプリは、今回は Google 謹製のものを利用しました。

GitHub - GoogleCloudPlatform/reliable-task-scheduling-compute-engine-sample

 readme には「Google Compute Engine のタスクスケジューリング」と書かれていますが、Cloud Pub/Sub から先の連係先が Google Compute Engine (GCE) か Cloud Functions かという違いだけなので、実は GAE 部分はそのまま流用できます。

 リポジトリを clone したら、gae ディレクトリ内の cron.yam を確認します。デフォルトでは「1分毎」に起動するよう記述されています。

cron:
- description: test task
  url: /events/test
  schedule: every 1 minutes

 これを、例えば「毎日 00:05」とするには以下のようにします。時刻を明示する場合は、併せてタイムゾーンも記述します。

cron:
- description: test task
  url: /events/test
  schedule: every day 00:05
  timezone: Asia/Tokyo

 アプリを GAE へデプロイするには、以下のようにコマンドを実行します。

gcloud app create --region=us-central
gcloud app deploy gae/app.yaml gae/cron.yaml

 これで、指定した時刻になると自動的に GAE でアプリが実行され、Cloud Pub/Sub に向かってメッセージを発行します。

Cloud Storage のバケットを作成する

 さて、GAE 側の準備ができたので、次は Cloud Functions の準備・・・と行きたいところですが、その前に「ダウンロードした CSV ファイルの格納先」となる「Google Cloud Storage」の「バケット (≒フォルダ)」を作成します。(ここは手作業の時と同じです)
 バケットの作成は GUICLI どちらでも可能ですが、ここでは CLI で行ってみます。AWS の S3 と同じく、バケット名はグローバルで一意な文字列でなければならないことに注意しましょう。

gsutil mb gs://testproject-123456-kankyo

Cloud Functions のコードを書く

 Cloud Functions でファイルのダウンロードとコード変換を行うサンプルコードを書きました。

GitHub - hideakiaoyagi/gcp-cloud-functions-filedownload-sample

 コードの内容は、前回のブログ (ハンズオン参加記) で手作業で行っていた「指定した URL から CSV をダウンロードする」→「文字列を SJIS から utf-8 へ変換する」を単純にコード化したものです。
 6行目の const bucketname = '[Your Bucket Name]'; の部分には、さきほど作成した CSV ダウンロード先のバケット名を記述します。
 その他の特筆事項としては、処理を終了する時に callback(); と記述することです。AWS Lambda だと context.done(); とか書くのと同じような感じでしょうか。

 使用するパッケージを package.json へ記述します。Cloud Storage へアクセスするために「@google-cloud/storage」パッケージを使います。

Cloud Functions をデプロイする

 Cloud Functions は、コードのデプロイ先として Cound Storage のバケットを使います。ここでは、CSV ダウンロード先のバケットと同様に CLI で作成します。

gsutil mb gs://testproject-123456-functions

 バケットを作成したら、Cloud Functions をデプロイします。

gcloud beta functions deploy downloadfile --stage-bucket testproject-123456-functions --trigger-topic test

 downloadfile は、デプロイするファイル (今回の場合 index.jspackage.json) が格納されたディレクトリを指定し、これは作成する Fucntion の名前にもなります。
 --stage-bucket で、デプロイ先のバケット名を指定します。
 --trigger-topic は「Pub/Sub をトリガに実行する」ことを意味し、続く文字列はメッセージの識別名となります。(GAE 側で指定したメッセージ識別名と同じものにする)

おわりに

 これで、定期的にサイトからデータをダウンロードする仕組みができました。
 後は、Dataprep 側のスケジューリングで例えば「毎日 00:10」等を指定すれば、ダウンロードされたファイルを Dataprep が引き継いで処理することができます。

 今回調べてみて、Google Cloud Functions が cron 実行の仕組みを持っていないことが少し意外でしたが、もしかすると正式リリース時には実装されてくるのかもしれませんね。