🐝

AWSサーバーレス環境でのバッチ構成パターン集

2023/12/17に公開

こんにちは!インフラエンジニアの@Zepprix です。
本記事は SimpleForm Advent Calendar 2023 の17日目です。

サーバーレス環境でバッチを実装する際、AWS では複数の設計を検討できると思います。本記事ではいくつかの実装例を挙げて比較検討していきます。ユースケースとしては DB のマイグレーションの実行環境等を想定しています。

背景

当社ではアプリケーションが利用するデータベースとして Aurora MySQL を利用しており、
スキーマ更新等のマイグレーションの実行は運用していく上で不可欠な要素です。現状、開発者が手動でコマンドを叩く形で実行しているのですが、検証環境など運用する環境が増えるにつれて、CD パイプラインに乗せて自動化する機運が高まっています。

今後実装を進めていく際の技術選定の一助となればと思い、本記事でいくつかの構成パターンを比較検討してみることにしました。分かりやすいユースケースとして DB マイグレーションを取り上げていますが、要は DB に接続して処理を行うバッチ、ということですのでこれ以外の用途にも応用できる話かと思います。

当社のDB周りの構成

構成図

  • Web アプリケーションを ECS Fargate で運用
  • Fargate 以外にもクローリングやバックエンド API など複数の Lambda 関数が存在
  • private subnet に RDS を配置して RDS Proxy 経由で各サーバーから接続
  • いわゆる踏み台サーバーは設置しておらず、運用メンバーは VPN 経由で個人 PC から DB に接続してマイグレーションを実行
  • マイグレーションは Alembic を利用しており、Python の環境構築が必要
  • CI/CD は基本的に GitHub Actions

上記を前提条件とした上で、既存の CD パイプラインからバッチをキックする例をいくつかご紹介します。RDS を用いたサーバーレス環境としてはよくある前提条件かと思いますので、当社以外でも活用できる話になっているかと思います。

構成パターン

DB 接続を伴うバッチの実行環境としては以下の要件があります。

  • VPC 上に環境構築して RDS に接続できること
  • マイグレーションコマンドなどを実行するためのランタイム環境(Python など)を構築できること
  • API コールなど任意のタイミングで処理をキックできること
  • DB パスワードなどを環境変数として設定できること

上記の要件を満たす選択肢として以下の4つのサービスを挙げてみました。

  • ECS(Fargate)
  • Lambda
  • CodeBuild
  • AWS Batch

以後、それぞれの特徴と簡単な実装例を個別に解説していきます。

ECS(Fargate)

RunTask API を使うと一時的に ECS タスクを立ち上げてコマンドを実行することができます。タスク定義についてはマイグレーション専用のものを用意しても良いですが、アプリケーション等に利用しているものをコマンドだけ上書きして流用することも可能です。以下はその例です。

TASK_FAMILY_NAME="<ECS タスクのfamily名>"
CLUSTER_NAME="<ECSクラスター名>"
SUBNET_ID="<DBにアクセス可能なサブネットID>"
SECURITY_GROUP_ID="<DB へのアクセス許可済のSecurity Group ID>"
CONTAINER_NAME="<利用するECSタスク定義内のコンテナ名>"
MIGRATION_COMMAND="<マイグレーション実行コマンド>"

TASK_DEFINITION_ARN=$(aws ecs list-task-definitions \
  --family-prefix "${TASK_FAMILY_NAME}" \
  --status ACTIVE \
  --sort DESC \
  --query "taskDefinitionArns[0]" \
  --no-paginate \
  --output text)

# タスク実行
RUN_TASK_RESP=$(aws ecs run-task \
  --cluster "${CLUSTER_NAME}" \
  --count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[${SUBNET_ID}],securityGroups=[${SECURITY_GROUP_ID}],assignPublicIp=DISABLED}" \
  --overrides "{
      \"containerOverrides\": [{\"name\": \"${CONTAINER_NAME}\", \"command\": [${MIGRATION_COMMAND}]
    }"\
  --task-definition "${TASK_DEFINITION_ARN}" \
  --query taskArns[0] \
  --output text)

# タスクの終了まで待機
aws ecs wait tasks-stopped \
  --tasks "${TASK_ARN}" \
  --cluster "${CLUSTER_NAME}"

Lambda

マイグレーション用の関数に VPC を設定して AWS CLI で処理をキックするパターンです。

aws lambda invoke --function-name "<関数名>" response.json

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-sync.html

実行結果は response.json に書き込まれます。ただし最大15分でタイムアウトするので重い処理が入る場合は注意です。

CodeBuild

ちょっと変化球かもしれませんね(笑)
一般的にはソースコードのビルド実行環境として利用することが多いと思いますが、実は VPC を設定して RDS に直にアクセスできるように構成することが可能です。ジョブの設定は buildspec.yaml で記述しますが、VPC はプロジェクト作成時にコンソール上で設定します。

Terraform で構築する場合の記述例は以下の通りです。

resource "aws_codebuild_project" "test" {
  name          = "test-project"

  vpc_config {
    vpc_id = aws_vpc.example.id

    subnets = [
      aws_subnet.example1.id,
      aws_subnet.example2.id,
    ]

    security_group_ids = [
      aws_security_group.example1.id,
    ]
  }
}

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project

既にソースコードのビルド、デプロイを行うワークフローがある場合はマイグレーションコマンドを追加するだけですし、GitHub Action など別の環境から AWS CLI で CodeBuild のジョブをキックすることもできます。

PROJECT_NAME="<CodeBuildのプロジェクト名>"

# 処理のキック
BUILD_RESP=$(aws codebuild start-build --project-name "${PROJECT_NAME}")
BUILD_ID=$(echo ${BUILD_RESP} | jq -r '.build.id')

# 実行結果の取得
aws codebuild batch-get-builds \
  --ids "${BUILD_ID}" \
  --output json \
  jq -r '.builds[0].buildStatus'
#=> "SUCCEEDED"

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/run-build-cli.html
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/getting-started-cli-monitor-build.html

SecretManager や SSM パラメータストアとの連携もサポートされているので DB 接続情報の管理も簡単です。また、ジョブのタイムアウトは最大8時間なので比較的重い処理でも安心です。実はバッチの実行環境としても結構優秀なんですね。

AWS Batch

バッチ処理に特化したマネージドサービスで、Fargate、EC2、EKS のいずれかをコンピューティング環境として利用します。それぞれをラップしたバッチ用のサービスという感じですね。環境構築後、以下のように処理をキックできます。

JOB_NAME="<ジョブの名前>"
JOB_QUEUE="<ジョブキューの名前 or ARN>"
JOB_DEFINITION="<ジョブキューの名前 or ARN>"

aws batch submit-job \
  --job-name "${JOB_NAME}" \
  --job-queue "${JOB_QUEUE}" \
  --job-definition "${JOB_DEFINITION}"

https://docs.aws.amazon.com/cli/latest/reference/batch/submit-job.html

ここまでに紹介した構成ですと処理をキックした直後に環境が立ち上がりますが、AWS Batch の場合はキューがセットになっているのが特徴です。要は本来、Fargate とかの前段に SQS を置くべきところを AWS Batch だけで完結できるということですね。

故に大量にリクエストしても逐次処理してくれる利点がありますが、DB マイグレーション用の環境としてはちょっと大袈裟かもしれません。AWS におけるバッチ処理に特化したサービスということで、参考までに挙げさせて頂きました。

おわりに

今回紹介した構成パターンを料金という軸も加えて比較した表が以下になります。

サービス名 コンピューティング料金 最大実行時間 構築に必要な工数
ECS(Fargate) 1vCPU: $0.05056 + 1GB: $0.00553 = $0.05609 / h
※ Fargate Spot を利用できれば最大70%割引
なし
(クラスタ、タスク定義など設定項目が多い)
Lambda 1GB: $0.06012 /h 15分
CodeBuild lambda.x86-64.1GB: $0.072 / h 8時間
AWS Batch AWS Batch自体は無料
Fargateなど利用したコンピューティング環境に課金される
なし
(コンピューティング環境、ジョブ定義など設定項目が多い)

選定する際のポイントをざっくりまとめるとこんな感じでしょうか。

  • 料金的には ECS(Fargate) が最適だが、Lambda と比べると起動が遅くて構築にも手間がかかる
  • Lambda は起動が比較的高速で設定が簡単だが、15 分でタイムアウトするので注意
  • 既存の CD パイプラインで CodeBuild を利用している場合は VPC を設定して直にコマンドを叩くのが手っ取り早い(ただし、料金は他よりも高い)
  • 大量リクエストを捌く必要がある場合は AWS Batch、Lambda を使いたい場合は自前で SQS を前段に設置

例えば当社で DB マイグレーション実行環境を選定する場合、

  • インフラコスト最適化の優先度が比較的高い
  • 既に ECS で運用しているサービスがあるため実装コストが低い

という背景から、ECS(Fargate) が最適と言えるかもしれません。いい感じに運用を自動化してトイルを削減していきたいですね!本記事が少しでも参考になれば幸いです。

Discussion