Terraform 으로 aws lambda & function url 만들기 (단일 람다 함수에 API GW 없이 URL 붙이기)

Terraform 으로 aws lambda & function url 만들기 (단일 람다 함수에 API GW 없이 URL 붙이기)
Photo by Sigmund / Unsplash
Announcing AWS Lambda Function URLs: Built-in HTTPS Endpoints for Single-Function Microservices | Amazon Web Services
Organizations are adopting microservices architectures to build resilient and scalable applications using AWS Lambda. These applications are composed of multiple serverless functions that implement the business logic. Each function is mapped to API endpoints, methods, and resources using services su…
이걸 왜 지금에서야 해줬을까..

지금까지는 aws lambda를 URL로 서빙하려면 API Gateway 없이는 불가능했다. 하지만, 이제는 하나의 lambda에 하나의 url을 붙일 수 있다. 그리고 이 프로비저닝을 코드로 관리할 수도 있게 되었다.

GitHub - byunjuneseok/aws-lambda-function-urls-with-terraform: Built-in HTTPS Endpoints for Single-Function Microservices. Example with terraform!
Built-in HTTPS Endpoints for Single-Function Microservices. Example with terraform! - GitHub - byunjuneseok/aws-lambda-function-urls-with-terraform: Built-in HTTPS Endpoints for Single-Function Mic...
golang aws lambda with function urls

terraform aws provider version

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "4.11.0"
    }
  }
}

provider "aws" {
  profile = var.aws_profile
  region  = var.aws_region
}
provider.tf

terraform의 aws provider 버전은 4.9.0이상이어야 지원한다.

IAM

data "aws_caller_identity" "current" {}
sts.tf

이 data를 통해 data.aws_caller_identity.current.account_id 12자리 aws 계정번호를 얻을 수 있으니 선언해두도록 한다.

resource "aws_iam_role" "lambda" {
  name = "iam_${var.function_name}"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}


resource "aws_iam_policy" "lambda" {
  name = "policy_${var.function_name}"
  policy = templatefile("policy.json.tftpl", {
    aws_account_id = data.aws_caller_identity.current.account_id
    aws_region = var.aws_region,
    function_name = var.function_name
  })
}

resource "aws_iam_role_policy_attachment" "iam_for_lambda" {
  role = aws_iam_role.lambda.name
  policy_arn = aws_iam_policy.lambda.arn
}
iam.tf

{
  "Version": "2012-10-17",
  "Statement": [
    {
    "Effect": "Allow",
    "Action": "logs:CreateLogGroup",
    "Resource": "arn:aws:logs:${aws_region}:${aws_account_id}:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:${aws_region}:${aws_account_id}:log-group:/aws/lambda/${function_name}:*"
      ]
    }
  ]
}
policy.json.tftpl

IAM권한은 위와 같이 최소 권한을 주어준다. DynamoDB 같은 리소스를 사용하길 원한다면 policy.json.tftpl 에 정책을 추가해주면 된다.

lambda 선언

resource "aws_lambda_function" "lambda" {
  function_name = var.function_name
  filename      = "lambda.zip"
  handler       = "handler"
  role          = aws_iam_role.lambda.arn

  source_code_hash = filebase64sha256("lambda.zip")
  runtime = "go1.x"

  environment {
    variables = {
      foo = "bar"
    }
  }
}
lambda.tf

람다는 위와같이 선언한다. 기존과 다를 것이 없다.

Function URL 붙이기

resource "aws_lambda_function_url" "lambda" {
  function_name = aws_lambda_function.lambda.function_name
  authorization_type = "NONE"

}
function_url.tf

람다 이름으로 Function url을 할당해주면 끝이다. 인증 없는 url 엔드포인트를 바로 확인할 수 있다.

output "lambda_url" {
  value = aws_lambda_function_url.lambda.function_url

}
output.tf

아웃풋까지 지정해주면 terraform apply 를 입력했을 때 다음과 같은 url을 얻을 수 있을 것이다.

curl을 통해 구현 확인이 가능하다.

인증이 붙은 Function URL 붙이기

resource "aws_lambda_function_url" "test_live" {
  function_name      = aws_lambda_function.test.function_name
  qualifier          = "my_alias"
  authorization_type = "AWS_IAM"

}

authorization_typeAWS_IAM 으로 지정하면 AWS IAM Sigv4 시그니처 인증을 거치게 된다.

CORS 사용

resource "aws_lambda_function_url" "test_live" {
  function_name      = aws_lambda_function.test.function_name
  qualifier          = "my_alias"
  authorization_type = "AWS_IAM"
  
  cors {
    allow_credentials = true
    allow_origins     = ["*"]
    allow_methods     = ["*"]
    allow_headers     = ["date", "keep-alive"]
    expose_headers    = ["keep-alive", "date"]
    max_age           = 86400
  }

}

마이크로서비스를 위한 기능이다보니 CORS 기능도 당연히 포함되어 있다.

요약

  • 커스텀 도메인 불가능
  • 인증은 직접 구현하는게 아니라면 IAM 인증 가능
  • 비용은 없음, 오직 람다 비용만 청구
  • Cloudwatch Access Logs 안쌓임
  • Cloudwatch Metrics는 남음
  • HTTP Response timeout 최대 15분! (API GW는 29초)

간단한 웹훅이나, 백오피스, 29초의 응답시간을 넘겨야하는 케이스에서 생산성을 높여줄 것으로 보인다.