【Terraform】Terraform ~ AWS Lambda / あれこれ ~

■ はじめに

https://dk521123.hatenablog.com/entry/2024/05/30/010920

の続き。

今回は、前回の AWS Lambda の Hello worldを元に
色々と拡張してみる

目次

【1】Lambdaレイアーを付与
 0)使用するAPI
 1)フォルダ構成
 2)サンプル
【2】S3バケットのイベントトリガー
 0)使用するAPI
 1)フォルダ構成
 2)サンプル
【3】Lambda に AWS roleを付与
 1)フォルダ構成
 2)サンプル

【1】Lambdaレイアーを付与

0)使用するAPI

aws_lambda_layer_version

* Lambda レイヤー を作成

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

1)フォルダ構成

├── outputs
│   ├── layer/
│   ├── venv/
│   └── zipfile/
├── lambda
│   ├── requirements.txt
│   └── main.py
└── terraform
    └── main.tf

2)サンプル

* main.py は、以下の関連記事を参照のこと

Lambda ~ Python / 外部モジュール追加 ~
https://dk521123.hatenablog.com/entry/2024/05/25/005456

main.tf

#===============================
# locals
#===============================
locals {
  lambda_layer_name = "demo-lambda-layer"
  lambda_function_name = "demo-lambda"
  # Need to modify, otherwise error 'AccessDeniedException: Cross-account pass role is not allowed'
  iam_arn = "arn:aws:iam::123456789012:role/your-role"
}

#===============================
# Layer
#===============================

# Step1: Lambdaレイアーの実ファイル作成準備
resource "null_resource" "prepare_to_make_demo_layer_file" {
  triggers = {
    "requirements_diff" = filebase64("${path.module}/../lambda/requirements.txt")
  }

  provisioner "local-exec" {
    command = <<-EOF
      rm -rf ${path.module}/../outputs/layer/ &&
      pip install -r ${path.module}/../lambda/requirements.txt -t ${path.module}/../outputs/layer/python
    EOF

    on_failure = fail
  }
}

# Step2: Lambdaレイアーの実ファイル作成
data "archive_file" "make_demo_layer_file" {
  depends_on = [
    null_resource.prepare_to_make_demo_layer_file,
  ]

  type        = "zip"
  source_dir  = "../outputs/layer"
  output_path = "../outputs/zipfile/demo_layer.zip"
}

# Step3: Lambdaレイアー作成
resource "aws_lambda_layer_version" "demo_layer" {
  layer_name = local.lambda_layer_name

  filename         = data.archive_file.example_layer.output_path
  source_code_hash = data.archive_file.example_layer.output_base64sha256

  compatible_runtimes = ["python3.12"]
}

#===============================
# Lambda
#===============================
# Step1: Lambda関数のファイルをZIP圧縮
data "archive_file" "demo_zip" {
  type        = "zip"
  source_dir  = "../scripts"
  output_path = "../outputs/zipfile/demo_lambda.zip"
}

# Step2: cloudwatchのロググループ
resource "aws_cloudwatch_log_group" "demo_log_group" {
  name = "/aws/lambda/${aws_lambda_function.demo_lambda.function_name}"
  retention_in_days = 30
}

# Step3: Lambda関数作成
resource "aws_lambda_function" "demo_lambda" {
  function_name    = local.lambda_function_name
  filename         = data.archive_file.demo_zip.output_path
  role             =  local.iam_arn
  handler          = "main.handler"
  source_code_hash = data.archive_file.example_function.output_base64sha256
  runtime          = "python3.12"

  memory_size = 128
  timeout     = 60

 # ★ここで指定している★
  layers = [aws_lambda_layer_version.demo_layer.arn]
}

requirements.txt

requests==2.28.0

参考文献
https://qiita.com/neruneruo/items/feca4ea15e2230c188b4

【2】S3バケットのイベントトリガー

0)使用するAPI

aws_lambda_permission

* S3 などの外部ソースが Lambda 関数を呼び出す

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

1)フォルダ構成

├── lambda
│   └── main.py
└── main.tf

2)サンプル

* main.py は、以下の関連記事を参照のこと

Lambda ~ Python / S3トリガー ~
https://dk521123.hatenablog.com/entry/2024/05/23/162229

main.tf

# Lambdaをアップロードする際にzipにする必要があるので
# ファイルをZIP圧縮する
data "archive_file" "demo_zip" {
  type        = "zip"
  source_dir  = "${path.module}/lambda"
  output_path = "${path.module}/demo_lambda.zip"
}

# Lambda関数の作成
resource "aws_lambda_function" "demo_lambda" {
  function_name    = "demo-lambda"
  handler          = "main.handler"
  runtime          = "python3.12"
  filename         = data.archive_file.demo_zip.output_path
  source_code_hash = filebase64sha256(data.archive_file.demo_zip.output_path)
  # ★Need to modify, otherwise error 'AccessDeniedException: Cross-account pass role is not allowed'
  role = "arn:aws:iam::123456789012:role/your-role"
  environment {
    variables = {
      TARGET_URL = "${var.bucket_name}",
      DRY_RUN = "true"
    }
  }
}

resource "aws_cloudwatch_log_group" "demo_log_group" {
  name = "/aws/lambda/${aws_lambda_function.demo_lambda.function_name}"
  retention_in_days = 30
}

data "aws_s3_bucket" "target_s3_bucket" {
  bucket = "bucket.test.com"
}

resource "aws_lambda_permission" "demo_allow_s3_bucket" {
   statement_id = "AllowExecutionFromS3Bucket"
   action = "lambda:InvokeFunction"
   function_name = "${aws_lambda_function.demo_lambda.arn}"
   principal = "s3.amazonaws.com"
   source_arn = "${data.aws_s3_bucket.target_s3_bucket.arn}"
}

resource "aws_s3_bucket_notification" "demo_bucket_notification" {
   bucket = "${data.aws_s3_bucket.target_s3_bucket.id}"
   lambda_function {
       lambda_function_arn = "${aws_lambda_function.demo_lambda.arn}"
       events = ["s3:ObjectCreated:*"]
       filter_prefix = "images/"
       filter_suffix = ".png"
   }
   depends_on = [ aws_lambda_permission.demo_allow_s3_bucket ]
}

参考文献
https://hands-on.cloud/s3-trigger-lambda-terraform-example/

【3】Lambda に AWS roleを付与

1)フォルダ構成

├── lambda
│   └── main.py
├── local.tf
├── cloudwatch.tf
├── iam.tf
└── lambda.tf

2)サンプル

local.tf

locals {
  lambda_function_name = "demo-lambda"
  s3_bucket_name = "your-s3-bucket"
}

cloudwatch.tf

resource "aws_cloudwatch_log_group" "demo_log_group" {
  name = "/aws/lambda/${local.lambda_function_name}"
  retention_in_days = 30
}

iam.tf

resource "aws_iam_role" "demo_lambda_role" {
  name = "demo-lambda-role"
  assume_role_policy = jsonencode({
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "sts:AssumeRole",
        "Principal": {
          "Service": "lambda.amazonaws.com"
        },
        "Effect": "Allow",
        "Sid": "ForLambda"
      }
    ]
  })
}

# For S3 write/read access
# See also https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html
resource "aws_iam_policy" "demo_lambda_policy" {
  name        = "demo-lambda-policy"
  description = "IAM policy for the Lambda function"

  policy = jsonencode({
    Version   = "2012-10-17"
    Statement = [
      {
        "Sid": "ListObjectsInBucket",
        "Action": [
          "s3:ListBucket"
        ],
        "Effect": "Allow",
        "Resource": ["arn:aws:s3:::${local.s3_bucket_name}"]
      },
      {
        "Sid": "AllObjectActions",
        "Action": [
          "s3:*Object"
        ],
        "Effect": "Allow",
        "Resource": ["arn:aws:s3:::${local.s3_bucket_name}/*"]
      },
      {
        Effect   = "Allow"
        Action   = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = [
          aws_cloudwatch_log_group.demo_log_group.arn,
          "${aws_cloudwatch_log_group.demo_log_group.arn}:*"
        ]
      }
    ]
  })
}

resource "aws_cloudwatch_log_group" "example_log_group" {
  name = "/aws/lambda/${aws_lambda_function.example_lambda.function_name}"
  retention_in_days = 30
}

lambda.tf

# Lambdaをアップロードする際にzipにする必要があるので
# ファイルをZIP圧縮する
data "archive_file" "demo_zip" {
  type        = "zip"
  source_dir  = "${path.module}/lambda"
  output_path = "${path.module}/demo_lambda.zip"
}

# Lambda関数の作成
resource "aws_lambda_function" "demo_lambda" {
  function_name    = "demo-lambda"
  handler          = "main.handler"
  runtime          = "python3.12"
  filename         = data.archive_file.demo_zip.output_path
  source_code_hash = filebase64sha256(data.archive_file.demo_zip.output_path)
  role = aws_iam_role.demo_lambda_role.arn
}

main.py

import json

def handler(event, context):
    response = {
        'statusCode': 200,
        'body': 'Hello, World!'
    }
    return response

参考文献
https://zenn.dev/not75743/articles/7a7d3a2fc7e788

関連記事

Terraform ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/04/05/000224
Terraform ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2019/12/09/222057
Terraform ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2023/05/03/000000
Terraform ~ local / variable ~
https://dk521123.hatenablog.com/entry/2023/12/24/173633
Terraform ~ tfstate / Backend ~
https://dk521123.hatenablog.com/entry/2023/05/05/004939
Terraform ~ Terraformあれこれ ~
https://dk521123.hatenablog.com/entry/2023/05/15/205352
Terraform ~ AWS Lambda / 入門編 ~
https://dk521123.hatenablog.com/entry/2024/05/30/010920
Terraform ~ AWS ECR ~
https://dk521123.hatenablog.com/entry/2023/05/23/002314
Terraform ~ 複数環境へデプロイすることを考える ~
https://dk521123.hatenablog.com/entry/2023/05/06/003645
Lambda ~ Python / 入門編 ~
https://dk521123.hatenablog.com/entry/2021/10/07/103317
Lambda ~ Python / 外部モジュール追加 ~
https://dk521123.hatenablog.com/entry/2024/05/25/005456
Lambda ~ Python / S3トリガー ~
https://dk521123.hatenablog.com/entry/2024/05/23/162229