CUBE SUGAR CONTAINER

技術系のこと書きます。

GCP: Cloud Functions を Cloud Scheduler から定期実行する

何らかの処理を定期的に実行したくなる場面は多い。 トラディショナルなやり方であれば、仮想マシンを用意して cron などで処理を呼び出すと思う。 もちろん、それでも良いんだけど、よりシンプルに実装したい気持ちが出てくる。 具体的にはマシンの管理をなくした、いわゆるサーバレス・コンピューティングで楽がしたくなる。

Google Cloud であれば、このようなニーズに対して以下のサービスを組み合わせるのが良いようだ。

  • Cloud Functions

    • サーバレスで特定の処理 (関数) を呼び出すためのサービス
  • Cloud Scheduler

    • フルマネージドな cron ジョブを提供するサービス
  • Cloud Pub/Sub

    • 非同期のスケーラブルなメッセージングを提供するサービス

利用の流れは次のとおり。 まず、Cloud Functions で定期的に実行したい何らかの処理を定義する。 その際、Cloud Pub/Sub にメッセージが到達したタイミングで処理が実行されるように設定する。 そして、Cloud Scheduler から特定のタイミングで Cloud Pub/Sub にメッセージを送ることになる。

今回は、サービスを組み合わせて 1 分ごとに Cloud Functions を実行させてみよう。 操作は、基本的に Google Cloud SDK の gcloud コマンドから実施する。 なお、操作の対象となる Google Cloud API が有効化されていない場合には、別途有効化するかを確認する表示が出ることもある。

使った環境は次のとおり。

$ sw_vers              
ProductName:        macOS
ProductVersion:     13.5
BuildVersion:       22G74
$ gcloud version             
Google Cloud SDK 441.0.0
bq 2.0.95
core 2023.07.28
gcloud-crc32c 1.0.0
gsutil 5.25

もくじ

下準備

まずは下準備として Google Cloud SDK をインストールしておく。

$ brew install google-cloud-sdk  

gcloud コマンドが使えるようになるのでログインする。

$ gcloud auth login

操作するプロジェクトを指定する。 なお、プロジェクトはあらかじめ作成しておく。

$ gcloud config set project <project-name>

Cloud Pub/Sub のトピックを作成する

まずは Cloud Scheduler と Cloud Functions の間をつなぐ Cloud Pub/Sub のトピックを作成する。 名前は分かりやすければ何でも構わない。 今回は example-pubsub-topic という名前にした。

$ gcloud pubsub topics create example-pubsub-topic

Cloud Functions を Python で作成する

続いて Cloud Functions を作成する。 プログラミング言語として今回は Python を利用する。

まずは必要なファイル一式を収めるディレクトリを作成する。 ここでは helloworld という名前にした。

$ mkdir helloworld

続いて肝心の実行される処理を定義する。 以下では main.py というモジュール名で作成している。 処理の本体は main() 関数で、この関数がイベントハンドラとして呼び出される。 ただし、中身の処理はログを 1 行出力しているだけ。 なお、ログの出力先を Cloud Logging にするために google-cloud-logging パッケージを利用している。

$ cat << 'EOF' > helloworld/main.py
import logging

from google.cloud import logging as gcloud_logging


# Cloud Logging にログを出力できるようセットアップする
gcloud_logging.Client().setup_logging()
LOG = logging.getLogger(__name__)


def main(event, context):
    # Cloud Functions のイベントハンドラでログを出力する
    LOG.info("Hello, World!")

EOF

処理が定義できたら必要なパッケージを requirements.txt で定義する。 ここでは、先ほどの処理の中で利用していた google-cloud-logging をインストールしている。

$ cat << 'EOF' > helloworld/requirements.txt
google-cloud-logging
EOF

ここまでで、ディレクトリの構成は次のようになっている。

$ tree helloworld 
helloworld
├── main.py
└── requirements.txt

1 directory, 2 files

これで Cloud Functions に必要な準備が整った。

Cloud Functions をデプロイする

続いて Cloud Functions をデプロイする。 デプロイには gcloud functions deploy コマンドを使う。

以下では helloworld という名前で Cloud Functions をデプロイしている。

$ gcloud functions deploy helloworld \
  --gen2 \
  --no-allow-unauthenticated \
  --runtime python310 \
  --memory 128Mi \
  --region asia-east1 \
  --trigger-topic example-pubsub-topic \
  --source helloworld \
  --entry-point main

オプションについては次のような意味になる。

  • --gen2

    • 現在 (2023-08) の Cloud Functions には第 1 世代と第 2 世代があり、後者を利用するために指定している
  • --no-allow-unauthenticated

    • 任意のユーザが呼び出しできないように指定している
  • --runtime python310

    • Python 3.10 / Ubuntu 22.04 LTS の環境で実行されるように指定している
  • --memory 128Mi

    • ランタイムが利用できるメモリのサイズを指定している
  • --region asia-east1

    • デプロイ先のリージョンを指定している
  • --trigger-topic example-pubsub-topic

    • メッセージが到着した際に実行されるトピックを指定している
  • --source helloworld

    • デプロイするディレクトリを指定している
  • --entry-point main

    • イベントハンドラの関数名を指定している

デプロイが成功すると gcloud functions list コマンドで確認できる。 もしエラーになったときはログなどから原因を調査する。

$ gcloud functions list
NAME        STATE   TRIGGER                      REGION      ENVIRONMENT
helloworld  ACTIVE  topic: example-pubsub-topic  asia-east1  2nd gen

デプロイされた時点で Cloud Functions のログが gcloud functions logs read コマンドで確認できる。

$ gcloud functions logs read helloworld --region asia-east1
LEVEL    NAME        TIME_UTC                 LOG
I        helloworld  2023-08-06 15:48:33.154  Default STARTUP TCP probe succeeded after 1 attempt for container "helloworld-1" on port 8080.

デプロイできたら、手動で Cloud Pub/Sub にメッセージを送って Cloud Functions を実行してみる。

$ gcloud pubsub topics publish projects/$(gcloud config get-value project)/topics/example-pubsub-topic --message "-"

うまくいけばメッセージの到着によって Cloud Functions が実行される。 次のようにログが出力されることを確認しよう。 なお、空白のログは Cloud Functions が起動されたことを示しているらしい。 空白のログが 2 回出るのはデプロイした後の初回の起動時だけのようだ。

$ gcloud functions logs read helloworld --region asia-east1
LEVEL    NAME        TIME_UTC                 LOG
I        helloworld  2023-08-06 15:50:14.490  Hello, World!
I        helloworld  2023-08-06 15:50:14.395
I        helloworld  2023-08-06 15:50:14.260
I        helloworld  2023-08-06 15:48:33.154  Default STARTUP TCP probe succeeded after 1 attempt for container "helloworld-1" on port 8080.

これで Cloud Functions が Cloud Pub/Sub のメッセージが到着した際に想定通り実行されることが確認できた。

Cloud Scheduler のジョブを作成する

最後に Cloud Scheduler を設定する。 まず、現在のジョブを gcloud scheduler jobs list で確認する。 ここでは何も設定されていない。

$ gcloud scheduler jobs list --location=asia-east1
Listed 0 items.

続いて Cloud Scheduler のジョブを作成する。 ジョブのタイプとして pubsub を指定する。 ここではジョブの名前に helloworld を指定している。

$ gcloud scheduler jobs create pubsub helloworld \
    --location asia-east1 \
    --schedule "* * * * *" \
    --topic "projects/$(gcloud config get-value project)/topics/example-pubsub-topic" \
    --message-body "-"

指定しているオプションの意味は次のとおり。

  • --location asia-east1

    • ジョブを作成するリージョンを指定している
  • --schedule "* * * * *"

    • UNIX cron の書式でスケジュールが実行されるタイミングを指定している
  • --topic "projects/$(gcloud config get-value project)/topics/example-pubsub-topic"

    • メッセージを送信する Cloud Pub/Sub のトピックを指定している
  • --message-body "-"

    • メッセージの内容を指定している (今回、中身は使っていないためダミー)

ちなみに UNIX cron の書式は以下のようなサービスで確認すると効率が良い。

crontab.guru

うまくいけば次のようにジョブが作成される。 これで 1 分ごとに Cloud Pub/Sub にメッセージが送信される。

$ gcloud scheduler jobs list --location=asia-east1
ID          LOCATION    SCHEDULE (TZ)        TARGET_TYPE  STATE
helloworld  asia-east1  * * * * * (Etc/UTC)  Pub/Sub      ENABLED

少し待って Cloud Functions のログを確認してみよう。 次のように 1 分ごとに処理が実行されてログが出力されていれば上手くいっている。

$ gcloud functions logs read helloworld --region asia-east1 | head -n 6 
LEVEL    NAME        TIME_UTC                 LOG
I        helloworld  2023-08-06 15:58:03.741  Hello, World!
I        helloworld  2023-08-06 15:58:03.718
I        helloworld  2023-08-06 15:57:04.491  Hello, World!
I        helloworld  2023-08-06 15:57:04.427
I        helloworld  2023-08-06 15:50:14.490  Hello, World!

後片付け

動作の確認が終わったら後片付けしよう。

まずは Cloud Scheduler のジョブを削除する。

$ gcloud scheduler jobs delete helloworld --location=asia-east1

続いて Cloud Functions の定義を削除する。

$ gcloud functions delete helloworld --region asia-east1

そして Cloud Pub/Sub のトピックを削除する。

$ gcloud pubsub topics delete example-pubsub-topic

また、Cloud Functions のファイル群は Cloud Storage にアップロードされる。 今回の構成であれば、次のような名前のバケットが作成されているはず。

  • gcf-v2-sources-<project-id>-<region>
  • gcf-v2-uploads-<project-id>-<region>

そこで、まずは次のようにバケットを確認する。

$ gcloud storage buckets list | grep name

バケットが確認できたら、次のようにして削除する。

$ gcloud storage rm -r gs://<bucket-name> 

いじょう。

まとめ

今回は Cloud Functions を Cloud Scheduler から定期実行する方法を試してみた。 また、一連の操作は基本的に Google Cloud SDK の CLI で実施した。