こんにちは、川田です。今回は Google Cloud の Batch を触ってみた作業ログを記載します。
実施してみたこと
Cloud Storage 上に置かれたファイルをダウンロードして、zip 圧縮し、Cloud Storage 上に置き直す、という単純な Batch ジョブを作成しています。作業時、以下の設定/実行内容について確認しています。
- 利用する Compute Engine をコンフィグファイルにて管理する
- Compute Engine には外部 IP アドレスを付与せず、限定公開の Google アクセスを利用させる
- ジョブで実行されるコードは、コンテナにて定義する
- コンテナ環境変数を利用する
- 実行されるコンテナ上プロセスに引数を渡す
- ジョブの実行を gcloud コマンドにて行う
環境
- Google Cloud SDK 433.0.0
事前の準備
必要となる諸々の準備を行います。
VPC を作成
Compute Engine を起動させる VPC を作成します。
$ gcloud compute networks create batch-vpc --subnet-mode=custom
gcloud compute networks create
サブネットを作成します。前述の通り、作成するサブネットは限定公開の google アクセスを有効にしておきます。
$ gcloud compute networks subnets create batch-subnet \
--region=us-central1 \
--network=batch-vpc \
--range=10.10.0.0/24 \
--enable-private-ip-google-access
gcloud compute networks subnets create
コンテナイメージを作成
ジョブとして実行するコンテナイメージを作成します。
以下がディレクトリ構成です。
.
├── app
│ └── main.py
├── Dockerfile
└── requirements.txt
app/main.py
前述の通り、「Cloud Storage 上に置かれたファイルをダウンロードして、zip 圧縮し、Cloud Storage 上に置き直す」という Python のコードです。アクセスする Cloud Storage のバケット名をコンテナ環境変数より取得します。ダウンロードするファイル名を、実行引数より受け取るようにしています。
import argparse
import os
import zipfile
from google.cloud import storage
TMPDIR = "/tmp/batch"
BUCKET_NAME = os.environ["BUCKET_NAME"]
gcs_client = storage.Client()
def main(args: argparse.Namespace):
os.makedirs(TMPDIR)
bucket = gcs_client.bucket(BUCKET_NAME)
dl_blob = bucket.blob(f"batch/dl/{args.file_name}")
dl_blob.download_to_filename(f"{TMPDIR}/{args.file_name}")
with zipfile.ZipFile(f"{TMPDIR}/{args.file_name}.zip", "w", compression=zipfile.ZIP_DEFLATED, compresslevel=3) as zf:
zf.write(f"{TMPDIR}/{args.file_name}", arcname=args.file_name)
ul_blob = bucket.blob(f"batch/ul/{args.file_name}.zip")
ul_blob.upload_from_filename(f"{TMPDIR}/{args.file_name}.zip")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--file_name", type=str, required=False)
args = parser.parse_args()
main(args)
Dockerfile
FROM python:3.11.3-slim
WORKDIR /
COPY . .
RUN pip install --no-cache-dir --requirement requirements.txt
ENTRYPOINT ["python", "-m", "app.main"]
requirements.txt
google-cloud-storage==2.9.0
Artifact Repository に新規リポジトリを作成して、
$ gcloud artifacts repositories create batch \
--repository-format=docker \
--location=us-central1 \
--description="sample"
gcloud artifacts repositories create
Cloud Build にてビルドします。
$ gcloud builds submit --tag us-central1-docker.pkg.dev/PROJECT_ID/batch/app .
サービスアカウントを作成
Compute Engine に付与するサービスアカウントを作成します。
$ gcloud iam service-accounts create sa-batch --display-name="Batch"
gcloud iam service-accounts create
作成したサービスアカウントに、必要となる事前定義ロールを付与します。
$ gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:sa-batch@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/logging.logWriter"
$ gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:sa-batch@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/artifactregistry.reader"
$ gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:sa-batch@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/batch.agentReporter"
$ gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:sa-batch@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
gcloud projects add-iam-policy-binding
以下の事前定義ロールを利用しています。
ロール名 | 付与理由 |
---|---|
roles/logging.logWriter | Cloud Logging 出力用 |
roles/artifactregistry.reader | コンテナイメージの pull 用 |
roles/batch.agentReporter | Compute Engine が Batch のサービスエージェントをやり取りする際に必要となる |
roles/storage.objectAdmin | コンテナ内のコードで Cloud Storage にアクセスするため |
コンテナジョブを利用する場合、上位3つのロールは必須になるとものと考えています。
Batch ジョブを実行
必要となる設定ファイルを用意して、ジョブを実行してみます。
必要ファイルの作成(ジョブ定義ファイル)
ジョブの構成を定義した JSON ファイルを作成します。
job.json
{
"taskGroups": [
{
"taskSpec": {
"computeResource": {
"cpuMilli": "2000",
"memoryMib": "8000"
},
"maxRetryCount": 1,
"maxRunDuration": "1800s"
},
"taskCount": 1,
"parallelism": 1,
"taskEnvironments": [
{
"variables": {
"BUCKET_NAME": "xxxx"
}
}
]
}
],
"allocationPolicy": {
"location": {
"allowedLocations": ["regions/us-central1"]
},
"instances": [
{
"policy": {
"machineType": "e2-standard-2",
"provisioningModel": "STANDARD",
"bootDisk": {
"type": "pd-standard",
"sizeGb": 30,
"image": "batch-cos"
}
}
}
],
"serviceAccount": {
"email": "sa-batch@PROJECT_ID.iam.gserviceaccount.com"
},
"labels": {
"gce": "batch-instance"
},
"network": {
"networkInterfaces": [
{
"network": "projects/PROJECT_ID/global/networks/batch-vpc",
"subnetwork": "projects/PROJECT_ID/regions/us-central1/subnetworks/batch-subnet",
"noExternalIpAddress": true
}
]
}
},
"labels": {
"batch": "sample"
},
"logsPolicy": {
"destination": "CLOUD_LOGGING"
}
}
いろいろ書いていますが、.taskGroups[]
に実行したいジョブの情報を定義し、.allocationPolicy
に作成したい Compute Engine の情報を定義します。projects.locations.jobs
の REST API Resource がリファレンスとなるため、そちらを眺めつつ必要な情報を埋めていきます。
Google Cloud | REST Resource: projects.locations.jobs
.taskGroups[].taskEnvironments[]
に設定した値が、コンテナの環境変数として渡されます。.allocationPolicy.network.networkInterfaces[]
に設定した値が、Compute Engine の起動するネットワーク情報となり、noExternalIpAddress
の値を true とすることで、外部 IP アドレスを利用しなくなります。
必要ファイルの作成(実行引数の定義ファイル)
コンテナ上プロセスの、実行引数となる値をファイルに定義します。イメージとしては、Dockerfile の CMD 命令文に記載したい内容を記述することになり、exec 形式で書く場合の要素を、1 行ずつファイルに記述することになります。
cmd.text
-m
app.main
--file_name
test-data
前述の Dockerfile 内では ENTRYPOINT を指定していましたが、実際には、Batch ジョブを実行する際に ENTRYPOINT の値を上書きすることになります(後述されます)。ENTRYPOINT の値は Python の実行 EXE の値に上書きすることになるので、上記ファイルの情報を含めると python -m app.main --file_name test-data
という内容が、コンテナ上の起動プロセスコマンドになります。
ジョブを実行
gcloud コマンドを利用してジョブを実行します。今回は、以下の bash スクリプトを作成して、ジョブの実行と結果を併せて確認できるようにしています。
#!/bin/bash
set -eu
now=$(date +%Y%m%d%H%M%S)
gcloud beta batch jobs submit sample-job-"${now}" \
--location us-central1 \
--config ./job.json \
--container-image-uri "us-central1-docker.pkg.dev/PROJECT_ID/batch/app:latest" \
--container-entrypoint python \
--container-commands-file ./cmd.text
while true;
do
status=$(gcloud beta batch jobs describe sample-job-"${now}" --location us-central1 --format="value(status.state.scope())")
[[ ${status} = "SUCCEEDED" ]] || [[ ${status} = "FAILED" ]] && break
printf .
sleep 10
done
echo "finished. status: ${status}"
exit
gcloud beta batch jobs submit
がジョブを実行するコマンドです。--config
オプションに作成したジョブ定義ファイルを指定しています。--container-xxx
ではじまるオプションで、利用したいコンテナ情報を指定しています。ここで指定した値は Dockerfile 内で定義された値を上書きすることになります。--container-entrypoint
オプションでは、Dockerfile の ENTRYPOINT 命令文で書くような shell 形式の値を設定することはできず、単純に実行ファイルの値のみを設定する必要があります。--container-commands-file
オプションにて作成した実行引数の定義ファイルを指定しています。
submit コマンドのレスポンスでは、ジョブを投入した結果のみが返されるため、gcloud beta batch jobs describe
コマンドをループさせて、実行結果を確認しています。
gcloud beta batch jobs describe
スクリプトの出力内容はこんな感じです。
Job sample-job-2023060-5d49d5f8-8e32-4f1b0 was successfully submitted.
(中略)
.....................finished. status: SUCCEEDED
その他
コンテナ上の標準出力/エラー出力のログは、Cloud Logging へ自動的に連携してくれます。
また、以下のようなジョブのステータスの履歴も記録してくれます。
$ gcloud beta batch jobs describe sample-job-20230601012902 --location us-central1 --format="value(status.statusEvents[].scope())"
[
{
"description": "Job state is set from QUEUED to SCHEDULED for job projects/123456789012/locations/us-central1/jobs/sample-job-20230601012902.",
"eventTime": "2023-05-31T16:29:09.249751384Z",
"type": "STATUS_CHANGED"
},
{
"description": "Job state is set from SCHEDULED to RUNNING for job projects/123456789012/locations/us-central1/jobs/sample-job-20230601012902.",
"eventTime": "2023-05-31T16:30:25.876520330Z",
"type": "STATUS_CHANGED"
},
{
"description": "Job state is set from RUNNING to SUCCEEDED for job projects/123456789012/locations/us-central1/jobs/sample-job-20230601012902.",
"eventTime": "2023-05-31T16:32:48.154303080Z",
"type": "STATUS_CHANGED"
}
]
動作は未確認ですが、ジョブ定義ファイルの JobNotification では Cloud Pub/Sub の topic を指定することができ、上記のステータスの情報を Publish してくれるようです。
Google Cloud | REST Resource: projects.locations.jobs jobnotification
追記:ローカル SSD を利用したパターンの記事を投稿しています。