Lambda+Glue+Step Functionsの構成をTerraformでデプロイしてみた

Lambda+Glue+Step Functionsの構成をTerraformでデプロイしてみた

Clock Icon2025.03.14

データ事業本部のueharaです。

今回は、Lambda+Glue+Step Functionsの構成をTerraformでデプロイしてみたいと思います。

はじめに

これまで、Lambda+Glue+Step Functionsといった比較的軽量なETLでありがちな構成を

  • Serverless Framework
  • AWS SAM
  • AWS CDK

でそれぞれデプロイしてみるというブログを公開しました。

https://dev.classmethod.jp/articles/etl-workflow-sls-sam-deploy/

https://dev.classmethod.jp/articles/lambda-glue-etl-deploy-aws-cdk/

今回は上記のブログでもデプロイを実施した以下の構成をTerraformを用いてデプロイしたいと思います。

20240402_saml_sls_01r2

なお、構成図の内S3は既にAWS環境に存在するものとして以下説明を扱います。

フォルダ構成

今回のフォルダ構成は以下の通りです。

.
├── glue_scripts
│   └── test_glue.py
├── handler
│   └── test_func.py
└── main.tf

※この段階で terraform init はしていないので .terraform.terraform.lock.hcl は表示されておりません。

各ファイルの構成

glue/test_glue.py

こちらは前回/前々回と同じく、Glueを動かすためのサンプルスクリプトとなるため、スリープするだけで特段処理はしないものになります。

test_glue.py
import sys
import time

def main(argv):
    print("start")
    # sleep 5 minutes
    time.sleep(300)
    print("end")

main(sys.argv)

handler/test_func.py

Lambdaで動かすPythonスクリプトになります。こちらも検証用なので特に処理はしません。

test_func.py
import time

def lambda_handler(event, context):
    print("start")
    # sleep 1 minute
    time.sleep(60)
    print("end")

    return {"message": "success"}

main.tf

こちらが今回メインとなるLambda+Glue+Step Functionsの構成を記載したファイルです。

簡単化のため1つの .tf ファイルに記載をまとめています。

main.tf
provider "aws" {
  region = "ap-northeast-1"
}

# Lambda Function用のZIPファイル作成
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "${path.module}/handler"
  output_path = "${path.module}/lambda_function.zip"
}

# Lambda Function Role
resource "aws_iam_role" "lambda_role" {
  name = "uehara-terraform-test-lambda-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# Lambda基本実行ポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

# Lambda Function
resource "aws_lambda_function" "my_lambda" {
  function_name    = "uehara-terraform-test-lambda"
  role             = aws_iam_role.lambda_role.arn
  handler          = "test_func.lambda_handler"
  runtime          = "python3.9"
  filename         = data.archive_file.lambda_zip.output_path
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256
  timeout          = 180
  memory_size      = 128
  architectures    = ["x86_64"]
}

# glue-scriptsディレクトリ内のファイルリストを取得
locals {
  glue_script_files = fileset("${path.module}/glue_scripts", "**/*.py")
}

# すべてのGlueスクリプトをS3にアップロード
resource "aws_s3_object" "glue_scripts" {
  for_each = { for f in local.glue_script_files : f => f }

  bucket = "cm-da-uehara"
  key    = "glue-scripts/${each.value}"
  source = "${path.module}/glue_scripts/${each.value}"
}

# Glue Job Role
resource "aws_iam_role" "glue_role" {
  name = "uehara-terraform-test-glueJob-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "glue.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# Glue Service Roleポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "glue_service_role" {
  role       = aws_iam_role.glue_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
}

# S3 Full Accessポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "glue_s3_access" {
  role       = aws_iam_role.glue_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

# Glue Job
resource "aws_glue_job" "my_glue_job" {
  name              = "uehara-terraform-test-glueJob"
  role_arn          = aws_iam_role.glue_role.arn
  glue_version      = "3.0"
  max_capacity      = 0.0625
  max_retries       = 0
  execution_property {
    max_concurrent_runs = 3
  }

  command {
    name            = "pythonshell"
    script_location = "s3://cm-da-uehara/glue-scripts/test_glue.py"
    python_version  = "3.9"
  }

  default_arguments = {
    "library-set" = "analytics"
  }
}

# Step Functions Role
resource "aws_iam_role" "step_functions_role" {
  name = "uehara-terraform-test-sf-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "states.ap-northeast-1.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# Lambda実行ポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "step_functions_lambda_role" {
  role       = aws_iam_role.step_functions_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaRole"
}

# Glue実行ポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "step_functions_glue_role" {
  role       = aws_iam_role.step_functions_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
}

# Step Functions State Machine
resource "aws_sfn_state_machine" "my_step_functions" {
  name     = "uehara-terraform-test-sf"
  role_arn = aws_iam_role.step_functions_role.arn

  definition = jsonencode({
    Comment = "Test Step Functions"
    StartAt = "InvokeLambda"
    States = {
      InvokeLambda = {
        Type     = "Task"
        Resource = aws_lambda_function.my_lambda.arn
        Next     = "InvokeGlueJob"
      }
      InvokeGlueJob = {
        Type     = "Task"
        Resource = "arn:aws:states:::glue:startJobRun.sync"
        Parameters = {
          JobName = aws_glue_job.my_glue_job.name
        }
        End = true
      }
    }
  })
}

# EventBridge Role
resource "aws_iam_role" "eventbridge_role" {
  name = "uehara-terraform-test-eventbridge-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "events.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# EventBridge Rule
resource "aws_cloudwatch_event_rule" "s3_event_rule" {
  name        = "uehara-terraform-test-sf-event"
  description = "Rule to trigger Step Functions on S3 object creation"

  event_pattern = jsonencode({
    source      = ["aws.s3"]
    "detail-type" = ["Object Created"]
    detail = {
      bucket = {
        name = ["cm-da-uehara"]
      }
      object = {
        key = [{
          prefix = "tmp/"
        }]
      }
    }
  })
}

# EventBridgeがStep Functionsを実行するためのポリシー
resource "aws_iam_policy" "eventbridge_to_sfn_policy" {
  name        = "eventbridge-to-sfn-policy"
  description = "Allow EventBridge to start Step Functions execution"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = "states:StartExecution"
        Resource = aws_sfn_state_machine.my_step_functions.arn
      }
    ]
  })
}

# ポリシーをEventBridgeロールにアタッチ
resource "aws_iam_role_policy_attachment" "eventbridge_to_sfn_attachment" {
  role       = aws_iam_role.eventbridge_role.name
  policy_arn = aws_iam_policy.eventbridge_to_sfn_policy.arn
}

# EventBridge Target
resource "aws_cloudwatch_event_target" "step_functions_target" {
  rule      = aws_cloudwatch_event_rule.s3_event_rule.name
  arn       = aws_sfn_state_machine.my_step_functions.arn
  role_arn  = aws_iam_role.eventbridge_role.arn
}

冒頭で紹介したブログの内容と同じように、S3バケット(上記だと cm-da-uehara )に /tmp プレフィックスを持つオブジェクトが作成されるとEventBridge経由でStep Functionsが起動するというものになっています。

それぞれの記述内容については、コメントで記載している通りです。

LambdaやGlueのスクリプトをデプロイするための処理も、同じく main.tf の中に記載をしております。

デプロイ

ファイルの準備ができたので、デプロイを行います。

まず、初期化を行います。

$ terraform init

実行後 Terraform has been successfully initialized! と表示されていれば成功です。

初期化が完了したら、以下コマンドでデプロイを行います。

$ terraform apply --auto-approve  

※注:AWSアカウントでMFA認証を有効化している場合は、以下を参考に ./aws/config を設定しておいて下さい。

https://dev.classmethod.jp/articles/terraform-mfa-assumerole-export-credentials/

デプロイが完了すると、以下のようなStep Functionsが確認できるかと思います。

20250314_tf_01

実行

指定したS3バケットに /tmp プレフィックスを持つオブジェクトをPutすると、EventBridge経由でStep Functionsが起動するかと思います。

LambdaとGlueを実行するそれぞれのステートが以下のように正常終了していれば成功です。

20250314_tf_02

最後に

今回は、Lambda+Glue+Step Functionsの構成をTerraformでデプロイしてみました。

参考になりましたら幸いです。

参考文献

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.