Terraformを使ってEventBridgeからLambdaを起動するようにAWSリソース作成してみた

TerraformでAWSリソース作成時にEventBridgeとLambda連携をする方法が知りたかったのでやってみました!
2023.08.08

みなさんこんにちは!

クルトンです。

今回は、以前書いたブログの内容をTerraformを使ってIaC化していきます。

処理内容は上記ブログと同じく、次のようなものを作成します。

  1. S3へファイルアップロード
  2. EventBridgeが起動
  3. Lambdaでアップロードされたファイル名をprintする(Pythonコード)

Terraformの環境構築について

リソース作成についてはTerraformを使用しています。Terraformを使ってのリソース作成をした事が無い方は、環境構築から始めてみてください。

参考になるのが次のブログです。

Mac環境お使いの方向け

Windows環境お使いの方向け

MFA設定しているIAMを使いたい方へ

Terraformにおいて、MFAを設定しているIAMを使いたい方は次のブログが参考になります。(リンク先はMac環境で設定していますがaws-vaultはWindowsでも使えます。)

作成するファイルについて

フォルダ構成としては次のようになります。

.
├── lambda_function.py
└── main.tf

main.tfファイルでAWSリソース作成に必要な情報を書きます。lambda_function.pyではLambda関数のコード内容を記述します。

それでは早速、lambda_function.py から作成していきます。

lambda_function.pyを作成

以前マネコンから作成した時と同じコードを書いています。発火条件にしている内容を受け取って処理できているか、print関数でCloudWatch logsへ出力して確認します。

import json

def lambda_handler(event, context):
    print(event)

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

次に main.tf を作成していきます。

main.tfを作成

まずは各AWSリソース作成前に必要な設定やlocalsブロックを使った変数の定義をします。prefixとlambda_funcへお好きなお名前をご記入ください。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
    archive = {
      source  = "hashicorp/archive"
      version = "2.4.0"
    }
  }
  required_version = ">= 1.2.0"
}

locals {
  prefix = "お好きな名前をご記入ください" # どのリソースを作ったか分かりやすくするために、先頭文字列を設定

  # S3
  bucket_name = "${local.prefix}-bucket"

  # EventBridge
  event_rule               = "${local.prefix}-event-rule"
  eventbridge_service_role = "${local.prefix}-eventbridge-service-role"

  # Lambda
  lambda_service_role_name = "${local.prefix}-lambda-service-role"
  lambda_file_name         = "lambda_function"
  lambda_func              = "お好きな名前をご記入ください"
  lambda_runtime           = "python3.10"
  entry_point              = "lambda_handler"
  output_source_dir        = "archive/${local.lambda_file_name}.zip"
}

次の3つについて記述します。

  1. S3
  2. Lambda
  3. EventBridge

S3の設定

  • aws_s3_bucket
    • S3バケットの設定をします。名前の設定をすればOKです。
  • aws_s3_object
    • フォルダ作成をします。
  • aws_s3_bucket_notification
    • コメントにも書いておりますが、EventBridgeとS3を連携させるために必要な設定をします。
# S3バケット作成
resource "aws_s3_bucket" "bucket" {
  bucket = local.bucket_name
}
resource "aws_s3_object" "input_folder" {
  bucket = aws_s3_bucket.bucket.id
  key    = "input-file/"
}

# EventBridgeへ通知を送るのに必要
resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket      = aws_s3_bucket.bucket.bucket
  eventbridge = true
}

Lambdaの設定

  • aws_iam_policy
    • dataの方では、CloudWatchへ書き込みをするために必要なAWS定義済みの権限を指定し、resouceの方でdataで設定した内容を割り当てるようにしています。
  • aws_iam_role_policy_attachment
    • aws_iam_policyでAWSLambdaBasicExecutionRoleを付与したポリシーを作成しているので、Lambda用に作っているIAMロールへ割り当てています。
  • aws_iam_policy_document
    • サービスロールとしての設定が必要で、 identifiers = ["lambda.amazonaws.com"]という設定が必要です。
  • aws_iam_role
    • サービスロール本体です。aws_iam_policy_documentで設定した内容を渡してIAMロール作成を行ないます。
  • archive_file
    • ローカルで作ったlambda_function.pyファイルを使ってzipファイルを作成します。作成場所はoutput_pathで定義した場所になります。
  • aws_cloudwatch_log_group
    • どのロググループにログを残すか明示的に示します。
  • aws_lambda_permission
    • CloudWatchログへLambdaがログ情報を渡せるように、許可を出すために設定します。
  • aws_lambda_function
    • Lambda関数の設定を行ないます。今回は、archive_fileで作成したzipファイルを使ってLambda関数のコードを設定しております。
    • コンテナを使う場合はimage_uriを、S3にあるコードを使う場合はs3_bucketを、ローカルマシンにあるファイルを使う場合は filenameで設定します。(今回はfilenameを使っています。)
data "aws_iam_policy" "iam_policy_AWSLambdaBasicExecutionRole" {
  arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_policy" "iam_policy_AWSLambdaBasicExecutionRole" {
  name   = local.lambda_service_role_name
  policy = data.aws_iam_policy.iam_policy_AWSLambdaBasicExecutionRole.policy
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.iam_role_for_lambda.name
  policy_arn = aws_iam_policy.iam_policy_AWSLambdaBasicExecutionRole.arn
}

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "iam_role_for_lambda" {
  name               = "iam_for_lambda"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "archive_file" "function_info" {
  type        = "zip"
  source_file = "${local.lambda_file_name}.py"
  output_path = local.output_source_dir
}

resource "aws_cloudwatch_log_group" "cloudwatch_log" {
  name = "/aws/lambda/${local.lambda_func}"
}

resource "aws_lambda_permission" "logging" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.test_lambda.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.eventbridge_rule.arn
}

resource "aws_lambda_function" "test_lambda" {
  function_name = local.lambda_func
  role          = aws_iam_role.iam_role_for_lambda.arn
  filename      = data.archive_file.function_info.output_path
  handler       = "${local.lambda_file_name}.${local.entry_point}"
  runtime       = local.lambda_runtime

  source_code_hash = data.archive_file.function_info.output_base64sha256

  depends_on = [aws_iam_role_policy_attachment.lambda_policy, aws_cloudwatch_log_group.cloudwatch_log]
}

ここで注意が必要なのがresource "aws_lambda_function"のhandlerです。ファイル名(拡張子なし).関数名を記載する必要があります。拡張子を入れてAWSリソース作成をしても、エラーが出ないため、気を付ける必要があります。

EventBridgeの設定

  • aws_cloudwatch_event_rule
    • 指定フォルダでファイルアップロードのイベントが起きた場合に起動するよう設定しています。
  • aws_cloudwatch_event_target
    • EventBridge起動後にどのAWSサービスを動かすのか設定します。
    • 前回Terraformを使ったブログと同じように設定するとエラーが発生します。role_arnを取り除く必要があります。
  • aws_iam_policy_document
    • EventBridgeを動かすためにidentifiers = ["events.amazonaws.com"]を設定する必要があります。
  • aws_iam_role
    • aws_iam_policy_documentを渡して作成する、IAMロール本体の設定をします。
resource "aws_cloudwatch_event_rule" "eventbridge_rule" {
  name        = local.event_rule
  description = "S3の特定フォルダ配下にファイルアップロードしたときに、Lambdaを起動するルールをIaC化"

  event_pattern = jsonencode({
    "detail-type" : ["Object Created"],
    "source" : ["aws.s3"],
    "detail" : {
      "bucket" : {
        "name" : [local.bucket_name]
      },
      "object" : {
        "key" : [{
          "prefix" : "input-file/"
        }]
      }
    }
  })
}

resource "aws_cloudwatch_event_target" "eventbridge_target" {
  rule = aws_cloudwatch_event_rule.eventbridge_rule.name

  arn = aws_lambda_function.test_lambda.arn
  input_transformer {
    input_paths = {
      "input_bucket_name" : "$.detail.bucket.name",
      "input_s3_key" : "$.detail.object.key"
    }
    input_template = <<TEMPLATE
{"Parameters": {"input_bucket_name":"<input_bucket_name>", "input_s3_key":"<input_s3_key>"}}
    TEMPLATE
  }
  # role_arn = aws_iam_role.eventbridge_service_role.arn # 設定するとエラーが発生する
}

data "aws_iam_policy_document" "eventbridge_policy" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }
    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "eventbridge_service_role" {
  name               = local.eventbridge_service_role
  assume_role_policy = data.aws_iam_policy_document.eventbridge_policy.json
}

リソース aws_cloudwatch_event_targetrole_arnを設定したままterraform applyコマンドを実行すると、次のようなエラーが発生する可能性があります。

Error: creating EventBridge Target (リソース名): ValidationException: RoleArn is not supported for target arn:aws:lambda:リージョン名:アカウントID:function:関数名.

上記のようなエラーが出た場合は、role_arn が設定されていないかご確認ください。設定されている場合は削除するとエラーが解決します。

2つのファイル作成後にAWSリソースの作成

次のコマンドをCLIで実行し、実際にAWSリソースを作成します。

  1. terraform init
    • tfファイルを見ながら必要な処理をする。(providerで使われているものをインストールするなど。)
  2. terraform apply
    • 実際にAWSリソースの作成をする

本ブログでは動作確認したファイルを記載しているので、2つのコマンドでOKです。もしtfファイルを新たに作る場合は、以下のコマンドを実行しながら進める事になるかと思います。

  1. terraform fmt
    • tfファイルのコード整形をしてくれる
  2. terraform validate
    • 実行するtfファイルで書き方に問題がないか確認してくれる
  3. terraform plan
    • tfファイルで作成するリソースがどういったものになるか確認できる

AWSリソースが作成されると次の表記がされます。

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

AWSリソースの作成が完了したら、実際に動かしてみましょう!

実際に動かしてみた

AWSリソース作成が完了した後に、動作確認をマネージメントコンソールでやってみます。

まずはS3を開き、作成したフォルダへファイルアップロードをします。

S3_upload_for_booting_AWSBatch

ファイルアップロード完了後に作成したLambdaのページを開きます。関数の名前で検索してください。

Lambdaの関数名のページにおいて、モニタリングと書かれているタブをクリック後に現れる、CloudWatch ログを表示ボタンをクリックしてください。

Lambda_monitoring_tab_cloudwatch_logs

CloudWatchログのストリームを確認すると、次のようにS3にアップロードしたファイル名などが受け取れている事を確認できます。

{'Parameters': {'input_bucket_name': '<EventBridgeの発火条件となっているバケット名>', 'input_s3_key': '<発火条件にしたフォルダ名>/<アップロードしたファイル名>'}}

終わりに

今回は、以前マネージメントコンソールで作成した内容を、Terraformを使って作成してみました。EventBridgeとLambdaの連携について、Terraformではどのように記述をするのか知りたかったので良い勉強になりました。

他のAWSリソースをTerraformで作成する方法についても、ブログ化してみたいです。

今回はここまで。

それでは、また!

参考にしたサイト