2022.10.07

GCP Batchを使ってみた

こんにちは。次世代システム研究室のM.Mです。

WEBサービスの構築においては、Google App EngineやCloud Runなどを利用して、サーバーレスで実現させるケースは多くなっていると思います。
ただ、定期的に実行する必要があるバッチ処理をサーバーレスで実現したいとなった場合、どのようにすればよいのでしょうか?
Cloud Run、Pub/Subを駆使して実現するなど工夫すればできそうですが、サーバーレスのバッチ処理を実現するために、様々なクラウドサービスと連携しないといけないとなると、それだけ、学習・運用に対するコストが増えてしまいます。

簡単に実現する方法はないのか?
少し調べたところ、まだどちらもプレビュー段階(2022/10/03時点)ではありましたが、GCP Batch、Cloud Run jobsというサービスが見つかりました。

今回は、GCP Batchでどのようなことができるのか試してみました。

1. GCP Batchの概要

以下、Google CloudのブログにGCP Batchについての紹介がありました。
この後、実際にGCP Batchを使っていきますが、以下2つが主な実施内容となります。
  • バッチを実行する環境となるDockerイメージを用意する
  • GCP Batchのジョブ登録時に用意したDockerイメージを指定して登録する
作成したDockerイメージを指定してGCP Batchにジョブ登録することで、指定したDockerイメージのコンテナが立ち上がりバッチが実行されます。

2. GCP Batchの実行

pythonで書かれた簡単なスクリプトを実行するだけのdockerイメージを用意して実行してみます。
作成したファイルは、以下の3つです。
■test.py
import click
import time

@click.command()
@click.argument('code', type=int)
def exec_test(code):
    print('Input code: {}'.format(code))
    time.sleep(60)
    exit(code)

if __name__ == '__main__':
    exec_test()
今回、バッチとして実行するスクリプトになります。
失敗するケースも試したいので、引数codeに指定した値でexitするような実装にしています。
■requirements.txt
Click==7.0
GCP Batchで必要という訳ではなく、単純にtest.pyでclickモジュールを使いたかっただけです。
■Dockerfile
FROM centos:7

COPY ./requirements.txt ./

RUN yum update yum -y && \
    yum install -y python3 python3-devel && \
    yum install -y gcc-c++

RUN python3 -m pip install --no-cache-dir -r requirements.txt

RUN localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"

COPY ./test.py ./
RUN chmod +x ./test.py

ENTRYPOINT ["python3", "./test.py"]
CMD ["0"]
test.pyを実行するだけのDockerfileになります。

今回のGCP Batchとは関係がないですが、上記ファイルをCloud Buildでビルドして、Artifact Registryに登録するところも簡単に書いておきます。
Artifact Registryにリポジトリを作成する
gcloud artifacts repositories create mmtest --repository-format=docker --location=us-central1 --description="MM Test"

Cloud Buildを使ってビルド&dockerイメージを登録する
・cloudbuild.yamlの作成
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: [ 'build', '-t', 'us-central1-docker.pkg.dev/PROJECT_ID/mmtest/test-image:tag1', '.' ]
images:
- 'us-central1-docker.pkg.dev/PROJECT_ID/mmtest/test-image:tag1'
・ビルド&dockerイメージの登録
gcloud builds submit --region=us-central1 --config cloudbuild.yaml

登録されたか確認する
以下の図のようにGCP WEBコンソールでもリポジトリに登録されたか確認することができます。
今回、GCP Batchで利用するDockerイメージの作成ができました。
では実際に、GCP Batchにジョブを登録してバッチを実行してみます。

GCP Batchによるバッチの実行
まず、以下の図のようにGCP WEBコンソールの左メニューのコンピューティング欄にあるバッチを選択します。

続いて、表示された画面にある作成ボタンをクリックしてバッチジョブの作成に進みます。
すると、以下の図のように、Dockerイメージを入力する欄があるので、上記にて作成したDockerイメージを設定します。

作成を完了すると、以下の図のようにバッチジョブが作成されます。(ジョブ名をjob-mmtest01として登録しています。)
ステータスがScheduledになり、しばらく待つと、ステータスが「実行中」になり、「完了しました」に変わっていきます。

では、実行結果を確認します。

実行結果確認
以下の図のように、View Logsをクリックすると実行ログが確認できます。

また、以下の図のように実行ログからInput Code: 0と出力され、test.pyが実行されていることが分かります。

今回はバッチジョブ登録時、CMDに何も指定しなかったので、デフォルトの0で実行されていますが、バッチジョブ登録時に以下の図のようにCMDのオーバーライド設定で、値を変えて実行することもできます。

GCP Batchに、Dockerイメージを指定してジョブの作成をすることで、簡単にバッチを実行できることが分かりました。

3. GCP ワークフロー経由での実行

実際に運用するとなると上記のようにGCP WEBコンソールから作成するケースは少ないと思います。
今回は、GCP ワークフロー経由で実行してみます。

ワークフローの登録
まず、以下の図のようにGCP WEBコンソールの左メニューのアプリケーションの統合の欄にあるワークフローを選択します。

続いて、表示された画面にある作成ボタンをクリックしてワークフローの作成に進みます。
以下の図のように、ワークフロー名の入力、サービスアカウントを選択する必要があるのですが、ここで選択するサービスアカウントのロールには、バッチジョブの権限が必要なので注意が必要です。

次に、以下の図のように、ワークフローを定義するステップがあります。

ここでは、以下の内容を入力します。
main:
  params: [args]
  steps:
    - init:
        assign:
          - projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
          - region: "us-central1"
          - batchApi: "batch.googleapis.com/v1"
          - batchApiUrl: ${"https://" + batchApi + "/projects/" + projectId + "/locations/" + region + "/jobs"}
          - imageUri: ${region + "-docker.pkg.dev/" + projectId + "/mmtest/test-image:tag1"}
          - jobId: ${"job-mmtest-" + string(int(sys.now()))}
    - logCreateBatchJob:
        call: sys.log
        args:
          data: ${"Creating and running the batch job " + jobId}
    - createAndRunBatchJob:
        call: http.post
        args:
          url: ${batchApiUrl}
          query:
            job_id: ${jobId}
          headers:
            Content-Type: application/json
          auth:
            type: OAuth2
          body:
            taskGroups:
              taskSpec:
                runnables:
                  - container:
                      imageUri: ${imageUri}
                computeResource:
                  cpuMilli: 500
                  memoryMib: 500
              taskCount: 1
              parallelism: 1
            logsPolicy:
              destination: CLOUD_LOGGING
        result: createAndRunBatchJobResponse
    - getJob:
        call: http.get
        args:
          url: ${batchApiUrl + "/" + jobId}
          auth:
            type: OAuth2
        result: getJobResult
    - logState:
        call: sys.log
        args:
          data: ${"Current job state " + getJobResult.body.status.state}
    - checkState:
        switch:
          - condition: ${getJobResult.body.status.state == "SUCCEEDED"}
            next: returnSuccessResult
          - condition: ${getJobResult.body.status.state == "FAILED"}
            next: returnErrorResult
        next: sleep
    - sleep:
        call: sys.sleep
        args:
          seconds: 10
        next: getJob
    - returnSuccessResult:
        return:
          jobId: ${jobId}
          message: "mmtest success"
          res1: ${getJobResult}
          res2: ${createAndRunBatchJobResponse}
    - returnErrorResult:
        return:
          jobId: ${jobId}
          message: "mmtest error"
          res1: ${getJobResult}
          res2: ${createAndRunBatchJobResponse}
    - failExecution:
        raise:
          message: ${"The underlying batch job " + jobId + " failed"}
31行目のimageUriにて、作成したDockerイメージを指定しています。
また、52-56行目で、実行結果によって処理を振り分けています。
バッチ実行が成功すると、66行目にあるようにmmtest successというメッセージ、失敗すると72行目にあるようにmmtest errorというメッセージを設定するようにしています。

ワークフローの作成を完了すると、実行できる状態になるので実行します。

実行結果確認
実行すると、以下の図のように、ワークフロー経由でバッチジョブが登録されていることが分かります。

また、GCP Batchのジョブ一覧をみても、以下の図のようにjob-mmtest-1664433066が登録され、実行中になっていることが分かります。

実行完了後、ワークフローの実行結果を確認すると、以下の図のように、messageの値がmmtest successとなっているのが分かります。
実行された結果、ステータスがSUCCEEDEDだったので、returnSuccessResultに進み、mmtest successがmessageに設定されたことになります。

では、エラーになるケースも試してみます。
先ほどの成功ケースとの違いは、上記ワークフローの定義の際に記載したcontainerの部分に、以下のようにcommandsを追加するだけです。
                  - container:
                      imageUri: ${imageUri}
                      commands: ["1"]

実行するtest.pyは、引数に指定した値でexitで終了するようにしているので、上記だとexit(1)で終了することになりエラーとなるはずです。
実行すると、意図した通り、以下の図のようにmessageにmmtest errorと設定され、エラーとして判定されたことが分かります。
ステータスがFAILEDになり、returnErrorResultにて、messageにmmtest errorが設定されたことになります。

また、以下の図のように、GCP Batchの一覧でも失敗したことが分かります。

ワークフロー経由でGCP Batchのジョブの登録ができ、またバッチの実行結果によって後続処理を分岐させることが可能なことも分かりました。

4. GCP Cloud Schedulerでの定期実行

最後に、上記にて作成したワークフローを定期実行するようにします。
ワークフローを実行するサービスアカウントには、バッチジョブのロールだけでなく、ワークフローの実行ロールも必要になるので注意が必要です。

では、先ほど登録したワークフロー(workflow-mmtest01)を定期的に実行するように設定していきます。

ワークフローをスケジュール実行するように変更する
ワークフローの画面からworkflow-mmtest01選択し、編集をクリックします
以下のような画面がでるので、新しいトリガーを追加、Cloud Schedulerを選択します。

そして、以下のようにスケジュールの定義をします。
今回は10分間隔にしました。

しばらく放置して、ワークフローの詳細を確認すると、以下の図のように10分間隔で実行されていることが分かります。
(一番下の実行結果はスケジュール登録前に手動で実行したものなので10分間隔にはなっていません)

Google Scheduler、ワークフローを使えば、定期実行が可能なことが分かりました。

5. まとめ

GCP Batchにてサーバーレスでバッチの実行をすることができました。
ただ結局、定期実行したいとなると、ワークフローやスケジュールとの連携が必要になり、ワークフローの記載方法など別途理解する必要がでてきます。
もっと簡単に定期実行させたいのですが。
まだプレビュー段階なのでどうなるか分かりませんが、ワークフロー経由じゃなく、直接Google Schedulerと連携できるようになればいいのになと感じました。
Cloud Run jobsはまだ試していないので、気が変わらなければ、次回試してみたいと思います。

最後に、次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事