👷‍♂️

Cloud Run ジョブ ことはじめ

2023/12/10に公開

2023年は「Cloud Run を触って覚える」をテーマとした一人アドベントカレンダーを一人で開催しており、Cloud Run のさまざまな機能や、Cloud Run でよく使う構成などを実際の使い方と一緒にご紹介しています。

https://qiita.com/advent-calendar/2023/cloud-run

10日目は Cloud Run ジョブ についての概要と使い方のポイント、事例などをご紹介します。

Cloud Run の概要は技術評論社さまのブログ「gihyo.jp」に寄稿した記事で解説していますのでこちらもぜひご覧ください。

https://gihyo.jp/article/2023/10/modern-app-development-on-google-cloud-03

Cloud Run ジョブとは

Cloud Run ジョブは非同期タスクを実行するための機能です。Cloud Run サービスとは異なり、バッチ処理などのようなワークロードのための機能を備えています。2023年5月に GA (一般提供) になりました。

https://cloud.google.com/blog/ja/products/serverless/cloud-run-jobs-and-second-generation-execution-environment-ga/?hl=ja

Cloud Run ジョブが登場するまで、Cloud Run は主に Web アプリや API サーバーなどのような、HTTPS などのリクエストを受け付け、処理を行い、レスポンスを返すといったアプリケーションを動作させるためのサービスとして使われてきました。Cloud Run ジョブの登場によって、リクエストをリッスンする形式の「サービス」とバッチ処理などの非同期タスクを実行する形式の「ジョブ」の2種類の機能を提供するようになりました。

Cloud Run サービスと Cloud Run ジョブ

Cloud Run コンソールもアップデートされ「サービス」と「ジョブ」の両方を管理できるようになりました。

Cloud Run コンソール

Cloud Run ジョブの活用事例

Cloud Run ジョブをどのようなユースケースで利用できるか、カインズ様の活用事例が最近 (2023年12月に) 公開されました。

https://cloud.google.com/blog/ja/topics/customers/cainz-demand-forecasting-ai-with-vertex-forecast?hl=ja

こちらの事例では商品の AI による需要予測を Vertex Forecast で実施するにあたり、トレーニング データの前処理に Cloud Run ジョブを活用されています。直列で処理していたところを Cloud Run ジョブを使って並列処理に作り直したことによって、規模がスケールしてもトータルの処理時間に影響なくスケールすることができるように構成されています。

Cloud Run ジョブのリソース モデル

Cloud Run リソース モデルは次のようなリソース モデルで構成されています。

Cloud Run ジョブのリソース モデル

ジョブ

ジョブはルート リソースにあたり、どのようなタスクを実行するかの設定を管理します。タスクごとに起動するコンテナ イメージや起動するコンテナ インスタンスの CPU 上限 / メモリ上限、タスクの数、並列数、などを設定できます。

実行 (Execution)

実行は、ジョブを 1 回実行する上でのタスクのまとまりです。ジョブの設定に応じてタスクを実行し、全てのタスクが完了するまでの状態を管理します。1 度作成した実行は、何度でも再実行が可能です。

タスク

タスクはジョブに設定された数だけ実行されます。コンテナ インスタンスを 1:1 で用意し、コンテナ インスタンスごとの進捗を管理します。タスクがエラーで終了した場合は、タスク単位で再実行もできます。

コンテナ インスタンス

タスクと 1:1 で起動するコンテナ インスタンスです。ジョブに設定されたコンテナ イメージを元に、タスクの数だけ起動します。タスクごとにコンテナ イメージを変えることはできず、実行内では必ず同じコンテナ イメージを元にしたコンテナ インスタンスが起動します。

クイック スタート

Cloud Run ジョブのクイックスタートは主要な言語ごとに用意されています。ここでは下記のドキュメントで提供している Node.js のサンプル アプリケーションを Cloud Run ジョブで実行する手順を解説します。Google Cloud プロジェクトはすでに用意している前提で進めます。

https://cloud.google.com/run/docs/quickstarts/jobs/build-create-nodejs?hl=ja

サンプル アプリケーションの用意

まずはサンプル アプリケーションを用意します。次の GitHub リポジトリからクローンします。

git clone git@github.com:suwa-yuki/cloudrun-jobs-sample.git
cd cloudrun-jobs-sample

アプリケーション コードは main.js のみのシンプルな構成です。次のような処理になっています。

main.js
'use strict';
// [START cloudrun_jobs_quickstart]
// [START cloudrun_jobs_env_vars]
// Retrieve Job-defined env vars
const {CLOUD_RUN_TASK_INDEX = 0, CLOUD_RUN_TASK_ATTEMPT = 0} = process.env;
// Retrieve User-defined env vars
const {SLEEP_MS, FAIL_RATE} = process.env;
// [END cloudrun_jobs_env_vars]

// Define main script
const main = async () => {
  console.log(
    `Starting Task #${CLOUD_RUN_TASK_INDEX}, Attempt #${CLOUD_RUN_TASK_ATTEMPT}...`
  );
  // Simulate work
  if (SLEEP_MS) {
    await sleep(SLEEP_MS);
  }
  // Simulate errors
  if (FAIL_RATE) {
    try {
      randomFailure(FAIL_RATE);
    } catch (err) {
      err.message = `Task #${CLOUD_RUN_TASK_INDEX}, Attempt #${CLOUD_RUN_TASK_ATTEMPT} failed.\n\n${err.message}`;
      throw err;
    }
  }
  console.log(`Completed Task #${CLOUD_RUN_TASK_INDEX}.`);
};

// Wait for a specific amount of time
const sleep = ms => {
  return new Promise(resolve => setTimeout(resolve, ms));
};

// Throw an error based on fail rate
const randomFailure = rate => {
  rate = parseFloat(rate);
  if (!rate || rate < 0 || rate > 1) {
    console.warn(
      `Invalid FAIL_RATE env var value: ${rate}. Must be a float between 0 and 1 inclusive.`
    );
    return;
  }

  const randomFailure = Math.random();
  if (randomFailure < rate) {
    throw new Error('Task failed.');
  }
};

// Start script
main().catch(err => {
  console.error(err);
  // [START cloudrun_jobs_exit_process]
  process.exit(1); // Retry Job Task by exiting the process
  // [END cloudrun_jobs_exit_process]
});
// [END cloudrun_jobs_quickstart]

タスクが何番目に実行されているかはタスク実行時に環境変数として CLOUD_RUN_TASK_INDEX に渡されるため、コンテナ インスタンスごと (タスクごと) に異なる処理が行えるようになっています。また CLOUD_RUN_TASK_ATTEMPT には該当のタスクの再実行回数が記録されています。

このサンプルでは SLEEP_MSFAIL_RATE がユーザー定義の環境変数として設定されると、スリープしたり、ランダムでエラーにしたりすることができます。少し時間のかかる処理やエラー処理を再現したい場合はジョブに設定します。

コンテナ イメージの作成

Dockerfile は次のようになっています。Node.js アプリケーションを動かすために必要な最低限の処理が定義されています。

Dockerfile
FROM node:16
WORKDIR /app
COPY . .
RUN npm install
CMD [ "npm", "start" ]

次のコマンドで Artifact Registry にコンテナ イメージをプッシュします。PROJECT_ID は自分の Google Cloud プロジェクトに置き換えて実行します。

gcloud builds submit --region=asia-northeast1 --tag gcr.io/PROJECT_ID/coudrun-jobs-sample .

Cloud Run ジョブの作成と実行

コンソールから Cloud Run ジョブを作成します。まず Cloud Run のコンソールを開きます。

https://console.cloud.google.com/run

[ジョブを作成] をクリックします。

ジョブの作成

作成する Cloud Job の設定を、次のように設定します。

Cloud Job の設定

[コンテナ イメージの URL] は、先ほどプッシュしたコンテナ イメージを指定します。コンテナ イメージを設定すると [ジョブ名] はイメージ名が自動的に設定されます。

[リージョン] は asia-northeast1 (東京) に設定します。

[タスク数] はジョブで実行する任意のタスク数を設定します。ここでは 20 に設定します。サンプル アプリケーションはタスク数を何個に設定しても動くようになっているので、好きに設定してみてください。

[すぐにジョブを実行する] にチェックを入れると、作成時にジョブが 1 回実行されます。

ジョブの作成

数秒経つと、全てのタスクが完了し、実行が完了します。実行の詳細を見てみると、全てのタスクが完了していることが確認でき、各タスクのログを見てみるとタスクに割り振られた実行番号が読めていることが確認できます。

各タスクの確認

最近のアップデート

Cloud Run ジョブで使えるようになった、特に便利な最近のアップデートをご紹介します。

ジョブの実行時間が 24 時間まで指定可能に

リリースノート

Cloud Run ジョブはリリース当初、タスクが処理できる処理時間は最大 1 時間でしたが、最大 24 時間まで指定できるようになりました。1 時間を超えるヘビーなタスクも Cloud Run ジョブで実行できます。

https://cloud.google.com/run/docs/configuring/task-timeout?hl=ja#long-task-timeout

ジョブ設定が動的に変更可能に

リリースノート

環境変数やタスクの実行数、タイムアウト、引数などのジョブの設定が、実行時にオーバーライドできるようになりました。動的に変更できるようになったため、設定値に必要な情報を動的に取得しながら実行するといったパイプラインを組めるようになりました。

https://cloud.google.com/run/docs/execute/jobs?hl=ja#override-job-configuration

参考情報

Cloud Run ジョブについては次の記事も参考になります。こちらもあわせて読むとより理解できると思います。

まとめ

Cloud Run ジョブを使うと、Cloud Run の得意とする コンテナをスケールさせる 特長を活かしたバッチ処理などの非同期処理を構成できます。

バッチ処理をクラウド化したとしても「処理完了にものすごく時間がかかる」や「進捗がわかりづらい」「途中でコケたら個別に再実行できない」などといった課題を抱えている場合も多いです。Cloud Run ジョブを使うことで、そういった課題を解消する構成にモダナイズできる可能性があります。ぜひ導入を検討してみてください。

Google Cloud Japan

Discussion