😺

Amazon EventBridge を利用した S3 イベントハンドリング

2024/04/13に公開

概要

AWSを使用している際に、S3にファイルをアップロードし、そのファイルに対して何かしらの処理を行いたいシチュエーションはよくあります。
EventBridgeを利用することで、イベント駆動型でLambda関数やECSタスクを動的に起動することが可能です。
この記事では、EventBridge により S3 へのファイルアップロードを契機に ECS タスクを自動的に起動させるプロセスを IaC(Terraform) で実装します。

EventBridge について

EventBridge はジョブ実行に関するビルディングブロックと言えます。
異なるAWSサービスやサードパーティのアプリケーション間でイベントを効果的にルーティングし、イベントに基づいて自動的にアクションをトリガーすることができます。

具体的には以下のようなことを行います。

  • 異なるソースからのイベントを収集し、一元的に管理する
  • イベントに基づいて特定のAWSサービスやカスタムアプリケーションにアクションを指示する

今回のケースでは、S3にファイルがアップロードされるというイベントが発生すると、それを契機にAWS CloudWatchやAmazon ECSなどのサービスにイベントを伝達し、異なるサービス間での連携を自動化します。

EventBridge のルールとターゲット設定

前節で触れたイベントルーティングは、具体的にはEventBridgeのルールとターゲットを設定することで実現されます。

ルールの定義

EventBridgeのルールは、イベントパターンやスケジュールに基づいて設定され、特定のイベントが発生した際に反応します。今回のケースでは、S3へのファイルアップロードをイベントのトリガーとして使用します。
ルールはイベントパターンやスケジュールに基づいて設定され、特定のイベントが発生した際に反応します。

Terraformを使用して、イベント駆動型のS3バケットを設定する方法を以下に示します。
まず、S3バケットを作成し、EventBridgeへのイベント通知を有効にします。

s3.tf
resource "random_id" "suffix" {
  byte_length = 4
}

resource "aws_s3_bucket" "event_receiver_bucket" {
  # バケット名の一意性を担保するためにランダムな数値を付与
  bucket = "s3-put-event-example-backet-${random_id.suffix.hex}"
  acl    = "private"
}

resource "aws_s3_bucket_notification" "event_receiver_bucket_notification" {
  bucket      = aws_s3_bucket.event_receiver_bucket.id
  eventbridge = true
}

このコードにより生成されたバケットでは、以下のように Amazon EventBridge の通知が有効になっているはずです。

次に、EventBridgeのルールを設定します。このルールは、指定されたS3バケットにファイルがアップロードされたときにマッチします。[1]

eventbridge.tf
resource "aws_cloudwatch_event_rule" "s3_put_event_rule" {
  name = "s3-put-event-rule"
  event_pattern = jsonencode({
    "source" : ["aws.s3"],
    "detail-type" : ["Object Created"],
    "detail" : {
      "bucket" : {
        "name" : [aws_s3_bucket.event_receiver_bucket.bucket]
      }
    }
  })
}

これをデプロイすると EventBridge のルール一覧に s3-put-event-rule という名前のルールが生成されているはずです。

イベントパターンで、先ほど生成したバケットとの紐付けができていればOKです。

この設定により、ファイルがS3バケットにアップロードされるたびにEventBridgeがこのイベントをキャッチし、設定されたターゲット(この例では後述するECSタスク)に対してアクションをトリガーします。

ターゲットの定義

前節で設定したルールによりキャッチされたイベントをに対して直接アクションを取る「ターゲット」を設定します。
ここでは S3 バケットにファイルがアップロードされた際に、そのイベント情報を環境変数に設定した上で、ECSタスクを実行します。

以下の Terraform コードは、ECS タスクの実行ログを保存するための CloudWatch Logs グループを作成します。

cloudwatch.tf
resource "aws_cloudwatch_log_group" "eventbridge_ecs_task_target_logs" {
  name = "/aws/eventbridge/target-ecs-task-logs"
}

ECS タスクを起動するための EventBridge ターゲットを以下のように定義します。
この設定により、S3 へのファイルアップロードイベントが発生するたびに指定された ECS タスクが起動されます。

eventbridge.tf
resource "aws_cloudwatch_event_target" "s3_put_event_ecs_target" {
  rule      = aws_cloudwatch_event_rule.s3_put_event_rule.name
  target_id = "ecs-task-target"
  arn       = aws_ecs_cluster.event_handler_cluster.arn
  role_arn  = aws_iam_role.eventbridge_role.arn

  input_transformer {
    input_paths = {
      "bucketName" : "$.detail.bucket.name",
      "objectKey" : "$.detail.object.key"
    }
    input_template = <<TEMPLATE
{
  "containerOverrides": [
    {
      "name": "s3-event-handle-task-container",
      "environment": [
        { "name": "BUCKET_NAME", "value": <bucketName> },
        { "name": "OBJECT_KEY", "value": <objectKey> }
      ]
    }
  ]
}
TEMPLATE
  }

  ecs_target {
    task_definition_arn = aws_ecs_task_definition.s3_event_handle_task.arn
    task_count          = 1
    launch_type         = "FARGATE"

    network_configuration {
      subnets          = [aws_subnet.private.id]
      security_groups  = [aws_security_group.ecs_security_group.id]
      assign_public_ip = false
    }
  }
}

以下の ECS タスク定義では、アップロードされたファイルに関する情報を環境変数として設定し、それをログとして出力します。

ecs.tf
resource "aws_ecs_cluster" "event_handler_cluster" {
  name = "event-handler-cluster"
}

resource "aws_ecs_task_definition" "s3_event_handle_task" {
  family                   = "s3-event-handle-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_execution_role.arn
  task_role_arn            = aws_iam_role.ecs_task_role.arn

  container_definitions = jsonencode([
    {
      name      = "s3-event-handle-task-container"
      image     = "alpine:latest"
      essential = true
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = aws_cloudwatch_log_group.eventbridge_ecs_task_target_logs.name
          awslogs-region        = "ap-northeast-1"
          awslogs-stream-prefix = "ecs"
        }
      }
      command = [
        "/bin/sh",
        "-c",
        "echo $BUCKET_NAME && echo $OBJECT_KEY && sleep 60"
      ]
    }
  ])
}

その他ネットワークやセキュリティの設定諸々を含めた全体的なコードは以下のリポジトリに置いてあります。
https://github.com/k0kishima/terraform-s3-eventbridge-example

これらをデプロイすると、S3 へのファイルアップロードイベントが発生するたびに、ECS タスクが実行されるようになります。

今回は簡易的に CloudWatch に環境変数の値をログとして出すようにしています。
試しに EventBridge と連携しているS3バケットに適当なファイルをアップロードしてみてください。

以下のようにコンテナが起動し、ログが出力されているはずです。

脚注
  1. Terraform でリソース名のプレフィックスが aws_cloudwatch_event_ になっている理由は、EventBridge は CloudWatch Events というサービスが発展したものである経緯からのようです ↩︎

Discussion