GCP Batchをterraformで定義する

2023-08-25

はじめに

こんにちは。株式会社 Belong で Data Platform チーム所属の taketsuru です。

重たいバッチ処理を GCP で動かす際に GCP Batch(以下 Batch と表記)を使おうと検討していました。

諸事情ありリリースには至っていませんが、Batch を terraform 管理する上で勉強になったのでまとめます。

きっかけ

少し前にオンプレから GKE に移行しましたが以下の問題が発生しました。

  • autopilot で課金を抑えているが思ったより高額
    • autopilot を有効にしてインスタンス課金は抑えていたつもりだがそれでも予想以上の課金額
  • もともと k8s に詳しい退職者が構築した仕組みを詳しくないメンバーで引き継ぎ
    • 習熟度を上げるよりは他の開発に時間を割いた方が良いと判断

なので他に使用できるものはないか検討する流れとなりました。

ちなみに、GKE 移行時点では後述する Cloud run jobs や Batch は tokyo リージョンでは使えなかったので検討対象外でした。

Batch とは

Batchは GCP のバッチ処理実行サービスです。

バッチ処理実行中のみインスタンスを起動し、処理が終わればインスタンスを停止します。

インスタンスは起動時間のみ、その他ネットワーク系も含めた課金となり Batch そのものの課金は発生しません。

コンソールを見てると実行時間中だけ GCE が起動しているのがわかります。

触ってて気づいた experimental 感

GCP コンソールと gcloud コマンドが噛み合わない箇所が多々ありました。

gcloud コマンドでしか定義できないオプションがあったり。

GCP コンソールの Batch 画面では単発 Batch の作成と実行履歴の表示のみでした。

実行履歴はデフォルトがログからの全件検索&逐次表示で、一番欲しいと思われる直近の実行履歴を見るのにも多少ラグがあります。

諸事情でリリース保留していますが本番運用はリスクがあるかもしれません。

競合検討

他にバッチ処理を実行するサービスとしては以下を検討しました。

  • cloud run jobs
    • cloud run のマネージド直並列実行
    • 元々コンテナ化していたものに http エンドポイントを追加するのは冗長感
    • 1 時間越えは preview
  • cloud build
    • 用途的には CI/CD ですがバッチ処理も実行可能
    • 試算したところ時間単価が GKE より高額になるので断念
  • app engine
    • バッチ処理に必要なメモリにマッチするインスタンスタイプがない

それぞれ引っかかったのはコード修正可否、課金、メモリでした。

全てクリアした Batch が最適と判断しました。

terraform で定義する

本題です。

まず公式 provider では対応していません。

いろいろググってたらissueがありました。

job resource だから個別に作るのはナンセンスとのこと。

でもだったらどうするのか書いておいてほしいですよね。

もっとググったら cloud scheduler からなんとかなるかなと希望が見えてきました。

  • 公式で cloud scheduler から shellscript を動作させるサンプル
  • shellscript の部分をコンテナ実行に書き換えれば何とかなるか?

つまり以下のようになります。

  • cloud scheduler job として定義
  • http request の payload でコンテナ実行させるバッチ処理の内容を定義

terraform のコードサンプルは以下のようになります。

resource "google_cloud_scheduler_job" "batch-job-invoker" {
  paused           = false
  name             = "batch-job-invoker"
  project          = local.project_id
  region           = local.region
  schedule         = "0 */2 * * *"
  time_zone        = "Asia/Tokyo"
  attempt_deadline = "180s"

  retry_config {
    max_doublings        = 5
    max_retry_duration   = "0s"
    max_backoff_duration = "3600s"
    min_backoff_duration = "5s"
  }

  http_target {
    http_method = "POST"
    uri = "https://batch.googleapis.com/v1/projects/${local.project_number}/locations/${local.region}/jobs"
    headers = {
      "Content-Type" = "application/json"
      "User-Agent"   = "Google-Cloud-Scheduler"
    }
    # Batch job definition
    body = base64encode(<<EOT
    {
      "name": "projects/${local.project_id}/locations/${local.region}/jobs/batch_test",
      "taskGroups":[
        {
          "taskCount": "1",
          "parallelism": "1",
          "taskSpec": {
            "computeResource": {
              "cpuMilli": "1000",
              "memoryMib": "512"
            },
            "runnables":{
              "container": {
                "imageUri": "alpine:latest",
                "entrypoint": "/bin/sh",
                "commands": [
                  "-c",
                  "echo Hello world!"
                ],
                "blockExternalNetwork": true
              }
            }
          }
        }
      ],
      "allocationPolicy": {
        "instances": [
          {
            "policy": {
              "provisioningModel": "STANDARD",
              "machineType": "e2-medium"
            }
          }
        ],
        "serviceAccount": {
          "email": "${local.batch_runner_email}"
        }
      },
      "labels": {
        "source": "for-cloud-batch"
      },
      "logsPolicy": {
        "destination": "CLOUD_LOGGING"
      }
    }
    EOT
    )
    oauth_token {
      scope                 = "https://www.googleapis.com/auth/cloud-platform"
      service_account_email = local.scheduler_email
    }
  }
}

http payload の定義は GCP コンソールから batch 定義 -> 同等のコマンドを表示とかbatch の REST API の documentとかから作成しました。

runnables が配列だとエラーになるなど若干違うっぽいです。

SA の権限

Batch 実行側では今のところ以下の権限が必要そうです。

  • roles/logging.logWriter
    • ログ出力に必須
  • roles/batch.agentReporter
    • batch の実行状況を GCP に通知するのに必須っぽい
  • roles/artifactregistry.reader
    • artifactregistry から自作 image を pull したいので必須

SA の権限に限らずclassmethod さんの記事が参考になりました。

scheduler 側の権限は現時点絞り込めてないです。

雑に roles/editor 付与してます。

絞り込もうとしてあれこれ試してますが scheduler 側からの http による batch request で PERMISSION DENIED を解消できません。

ハマったところ

http payload の定義

http payload 内の定義に誤りがあっても terraform apply できてしまいます。

その場合、GCP コンソールの実行履歴に表示されません。

logs から http payload が問題なかったかを確認する必要があります。

誤りがある場合は以下のようなエラーがログに記録されます。

何が誤ってるかは教えてくれないです。

{
  "@type":"type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished",
  "jobName":"projects/{project_name}/locations/{region}/jobs/{job_name}",
  "status":"INVALID_ARGUMENT",
  "targetType":"HTTP",
  "url":"https://batch.googleapis.com/v1/projects/{project_id}/locations/{region}/jobs"
}

誤りがない場合は実行待ち状態として スケジュール設定済み と表示されます。

cloud scheduler から http post する方式に慣れていないので、そこのハマりどころと Batch のハマりどころの切り分けが難しいです。

ネットワーク環境

taskGroup.taskSpec.runnalbes.blockExternalNetwork = true は外部ネットワークにアクセスできない挙動でした。

外部発アクセスをブロックしてくれるかと思ったらコンテナ発アクセスもブロックしてくれるようです。

ネットワークにアクセスしない場合は上記 flag = true で問題ないですが、外部ネットワークにアクセスする場合は異なります。

上記 flag = false かつ nat つき private subnet 内で実行させる必要があります。

  • 上記定義では省略しています
  • public subnet でもいいですができれば private subnet がいいなというのは理解していただけるはず

まとめ

Batch を terraform で定義しようとして試行錯誤した記録になります。

私自身がインフラは aws から入ったので GCP の文化に慣れていない自覚はあります。

それ故の引っ掛かりもあれば document が充実していないなと感じたところもあります。

使いこなせば便利ではあるので今後もキャッチアップを続けていきたいです。

弊社 Belong では一緒にサービスを育てる仲間を募集しています。 もし弊社に興味を持っていただけたら <エンジニアリングチーム紹介ページ> をご覧いただけたら幸いです。