Cloud Run Jobs と Cloud Scheduler のジョブ基盤を sidekiq-cron 風の設定ファイルで管理する
🌟 はじめに
Webアプリケーションでは、時間のかかる処理やバッチ処理を非同期で実行する必要があります。
私は元々 Rubyist であり、そういった非同期ジョブの実行には Ruby on Rails の世界で定番である Sidekiq を愛用していました。特に yaml 一つでジョブのスケジュールを管理できる sidekiq-cron の利便性は好みです 💎
本記事では、Google Cloud Platform (GCP) のマネージドサービスである Cloud Build、Cloud Run Jobs、Cloud Scheduler を組み合わせて、sidekiq-cron ライクな運用しやすいジョブ実行システムを構築する方法を紹介します ☁️
🏗️ システム概要
📐 アーキテクチャ
🧩 主要コンポーネント
コンポーネント | 役割 | 絵文字 |
---|---|---|
Cloud Scheduler | スケジュール管理 | ⏰ |
Cloud Run Jobs | コンテナベースのジョブ実行環境 | 🏃♂️ |
Cloud Build | CI/CDパイプライン | 🔧 |
Artifact Registry | コンテナイメージの保存 | 📦 |
Cloud Logging | 構造化ログの管理 | 📝 |
1. 🏃♂️ Cloud Run Jobs によるジョブ実行環境
⚙️ 基本設定
Cloud Run Jobs は、バッチ処理に特化したサーバーレスコンテナ実行環境です。以下のような YAML 設定でジョブを定義します:
apiVersion: run.googleapis.com/v1
kind: Job
metadata:
name: async-job-data-sync
labels:
app: async-jobs
job-type: data-sync
spec:
template:
spec:
template:
spec:
serviceAccountName: cloud-run-jobs-sa@PROJECT_ID.iam.gserviceaccount.com
timeoutSeconds: 300
containers:
- image: asia-northeast1-docker.pkg.dev/PROJECT_ID/REPO_NAME/app:latest
args: ['data-sync']
resources:
limits:
memory: 512Mi
cpu: '1'
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-secrets
key: DATABASE_URL
2. ⏰ Cloud Scheduler によるスケジュール管理
📅 Sidekiq-cron 風の設定
Sidekiq-cron と同様の YAML 形式でスケジュールを管理します:
# schedules.yml
daily-data-sync:
cron: '0 2 * * *' # 毎日2時
job: 'data-sync'
description: 'Daily data synchronization'
options:
timeout: '3600s'
memory: '1Gi'
cpu: '2'
weekly-reports:
cron: '0 6 * * 1' # 毎週月曜6時
job: 'generate-reports'
description: 'Weekly report generation'
options:
memory: '2Gi'
cpu: '2'
hourly-notifications:
cron: '0 * * * *' # 毎時
job: 'send-notifications'
description: 'Hourly notification processing'
# デフォルトリソース使用
🎯 デフォルトリソース制限
運用を簡素化するため、デフォルト値を設定してます。
これらを後半で紹介する Cloud Scheduler デプロイスクリプトの YAML解析とジョブ作成
セクションで自動的に設定してます。
- timeout: 300s (5分) ⏱️
- memory: 512Mi 💾
- cpu: 1vcpu 🖥️
3. 🔧 Cloud Build による CI/CD パイプライン
🔄 自動デプロイフロー
📋 Cloud Build 設定例
# cloudbuild.yaml
steps:
# 依存パッケージのインストール
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: 'bash'
args:
- '-c'
- |
apt-get update && apt-get install -y curl
curl -L https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o /usr/local/bin/yq
chmod +x /usr/local/bin/yq
# Docker イメージビルド
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- '${_AR_HOSTNAME}/${PROJECT_ID}/${REPO_NAME}/app:$COMMIT_SHA'
- '-t'
- '${_AR_HOSTNAME}/${PROJECT_ID}/${REPO_NAME}/app:latest'
- '.'
# イメージプッシュ
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- '${_AR_HOSTNAME}/${PROJECT_ID}/${REPO_NAME}/app:$COMMIT_SHA'
# Cloud Run Jobs 自動デプロイ
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: 'bash'
args:
- '-c'
- |
chmod +x scripts/deploy-cloud-run-jobs.sh
export PROJECT_ID=$PROJECT_ID
export REGION=asia-northeast1
export JOB_IMAGE=${_AR_HOSTNAME}/${PROJECT_ID}/${REPO_NAME}/app:$COMMIT_SHA
./scripts/deploy-cloud-run-jobs.sh
# Cloud Scheduler デプロイ
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: 'bash'
args:
- '-c'
- |
chmod +x scripts/deploy-schedules.sh
export PROJECT_ID=$PROJECT_ID
export REGION=asia-northeast1
./scripts/deploy-schedules.sh
substitutions:
_AR_HOSTNAME: asia-northeast1-docker.pkg.dev
_REGION: 'asia-northeast1'
options:
logging: CLOUD_LOGGING_ONLY
timeout: '1200s'
4. 🤖 自動デプロイスクリプト
🚀 Cloud Run Jobs 自動生成スクリプト
本記事では、アプリケーション自体には言及しませんが、実行コマンドに引数で実行したいジョブ名を渡すとアプリ内部のハンドラで適切なジョブが実行される様なアプリケーションを開発しました 💡
#!/bin/bash
# Cloud Run Job YAML 生成
create_job_yaml() {
local job_name=$1
cat << EOF
apiVersion: run.googleapis.com/v1
kind: Job
metadata:
name: async-job-${job_name}
labels:
app: async-jobs
job-type: ${job_name}
spec:
template:
spec:
template:
spec:
serviceAccountName: ${JOB_SA}
timeoutSeconds: 300
containers:
- image: ${JOB_IMAGE}
args: ["${job_name}"]
resources:
limits:
memory: 512Mi
cpu: "1"
EOF
}
# ジョブのデプロイ
deploy_job() {
local job_name=$1
local cloud_run_job_name="async-job-${job_name}"
local temp_file="/tmp/job-${job_name}.yaml"
echo "Creating Cloud Run Job for: ${job_name}"
create_job_yaml "$job_name" > "$temp_file"
if gcloud run jobs replace "$temp_file" --region="$REGION" --quiet; then
echo "✓ Successfully deployed ${cloud_run_job_name}"
else
echo "✗ Failed to deploy ${cloud_run_job_name}"
return 1
fi
rm -f "$temp_file"
}
# メイン処理
job_names=$(/path/to/app --list-jobs) # ジョブ名の一覧を取得
for job_name in $job_names; do
deploy_job "$job_name"
done
⏰ Cloud Scheduler デプロイスクリプト
YAML 設定から Cloud Scheduler ジョブを自動生成:
#!/bin/bash
# スケジュールジョブの作成
create_scheduler_job() {
local job_name=$1
local cron_schedule=$2
local job_command=$3
local description=$4
local timeout=$5
local memory=$6
local cpu=$7
local scheduler_job_name="async-job-${job_name}"
local cloud_run_job_name="async-job-${job_command}"
echo "Processing job: ${job_name}"
echo " Cron: ${cron_schedule}"
echo " Command: ${job_command}"
# 既存ジョブの削除
if gcloud scheduler jobs describe "${scheduler_job_name}" --location="${REGION}" &>/dev/null; then
echo " Job exists, updating..."
gcloud scheduler jobs delete "${scheduler_job_name}" --location="${REGION}" --quiet
fi
# 新しいジョブの作成
gcloud scheduler jobs create http ${scheduler_job_name} \
--location=${REGION} \
--schedule='${cron_schedule}' \
--time-zone='Asia/Tokyo' \
--uri="https://${REGION}-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/${PROJECT_ID}/jobs/${cloud_run_job_name}:run" \
--http-method=POST \
--oauth-service-account-email="cloud-run-jobs-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--headers='Content-Type=application/json' \
--message-body='{}' \
--description="${description}"
if [ $? -eq 0 ]; then
echo " ✓ Successfully created/updated ${scheduler_job_name}"
else
echo " ✗ Failed to create/update ${scheduler_job_name}"
return 1
fi
}
# YAML解析とジョブ作成
job_names=$(yq eval 'keys | .[]' "$SCHEDULES_FILE")
for job_name in $job_names; do
cron=$(yq eval ".${job_name}.cron" "$SCHEDULES_FILE")
job_command=$(yq eval ".${job_name}.job" "$SCHEDULES_FILE")
description=$(yq eval ".${job_name}.description" "$SCHEDULES_FILE")
timeout=$(yq eval ".${job_name}.options.timeout // \"300s\"" "$SCHEDULES_FILE")
memory=$(yq eval ".${job_name}.options.memory // \"512Mi\"" "$SCHEDULES_FILE")
cpu=$(yq eval ".${job_name}.options.cpu // \"1\"" "$SCHEDULES_FILE")
create_scheduler_job "$job_name" "$cron" "$job_command" "$description" "$timeout" "$memory" "$cpu"
done
5. 🐳 Docker コンテナ設定
アプリケーションに合わせて適切にイメージを構築してください 📦
6. 🎮 手動実行
インスタント実行したい場合は、gcloud コマンドや GCP コンソールから、Cloud Run Jobs を手動で実行可能です! ⚡
7. 📊 監視とログ管理
ジョブ実行に伴う通知関連は、アプリ内部から直接 Slack Webhook などで通知するのがリアルタイムにジョブの実行状況や進捗を追えるのでオススメです! 💬
📝 構造化ログ
JSON形式での構造化ログ出力:
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "info",
"job": "data-sync",
"message": "Job execution completed",
"duration": "2.5s",
"processed_records": 150
}
📈 メトリクス監視
Cloud Monitoring との連携:
- job_execution_duration_seconds: ジョブ実行時間 ⏱️
- job_execution_total: ジョブ実行回数 🔢
- job_execution_errors_total: ジョブエラー回数 ❌
- job_memory_usage_bytes: メモリ使用量 💾
🎉 まとめ
Google Cloud Platform のマネージドサービスを組み合わせることで、Sidekiq のような高機能なジョブ実行基盤を構築できます ✨
🌟 主な利点
- サーバーレス: インフラ管理が不要 🚫🖥️
- スケーラブル: 自動スケーリング対応 📈
- コスト効率: 実行時間に応じた従量課金 💰
- 運用性: 豊富な監視・ログ機能 📊
- CI/CD統合: 自動デプロイパイプライン 🔄
🎯 適用例
- データ処理: ETL処理、データ同期 🔄
- レポート生成: 定期的なレポート作成 📊
- 通知処理: メール・プッシュ通知の一括送信 📧
- クリーンアップ: 古いデータの削除 🧹
- 外部API連携: サードパーティサービスとの同期 🔗
この構成により、スケーラブルで運用しやすいジョブ実行基盤を構築し、アプリケーションのバックグラウンド処理を効率的に実行できました 🥳
Discussion