はじめに
今回は前回の続きです。Lambda部分をTerraformでIaC化していきます。
前回ではVPC回りとAPI Gateway構成部分をTerraformでIaC化しました。以下がその記録です。
↓
ちなみにIaC化する前提構成は前回と同じままです(以下図)。
今回の実施内容としては以下となります。
- CloudWatchLogs管理をTerraform管理に変更
- Lambdaで利用するIAMロールをTerraform管理に変更
- LambdaをTerraform管理に変更
IaC化
CloudWatchLogs
Lambdaに関わるCloudWatchLogsのロググループだけでなく、本構成で必要なロググループをIaC化しました。
# #----------------------------------------
# # Authorizer Lambda Log Group
# #----------------------------------------
resource "aws_cloudwatch_log_group" "AuthForPubSub" {
name = "/aws/lambda/AuthForPubSub"
retention_in_days = 90
skip_destroy = false
}
# #----------------------------------------
# # Fargate Task Run Lambda Log Group
# #----------------------------------------
resource "aws_cloudwatch_log_group" "deliverTopicToSNS_Test01" {
name = "/aws/lambda/deliverTopicToSNS_Test01"
retention_in_days = 90
skip_destroy = false
}
# #----------------------------------------
# # Subscriber Lambda Log Group
# #----------------------------------------
resource "aws_cloudwatch_log_group" "subscribeTopicFromSNS_Test01" {
name = "/aws/lambda/subscribeTopicFromSNS_Test01"
retention_in_days = 90
skip_destroy = false
}
# #----------------------------------------
# # DynamoDB Stream Triggered Lambda Log Group
# #----------------------------------------
resource "aws_cloudwatch_log_group" "getDataFromDynamodbStream" {
name = "/aws/lambda/getDataFromDynamodbStream"
retention_in_days = 90
skip_destroy = false
}
# #----------------------------------------
# # Fargate Publisher Log Group
# #----------------------------------------
resource "aws_cloudwatch_log_group" "task-pubsub-pub" {
name = "/ecs/task-pubsub-pub"
retention_in_days = 90
skip_destroy = false
}
IAM
続いてIAMのPolicyとRoleの設定部分をIaC管理できるようにしていきます。
もともとはインラインポリシーとして利用していたLambdaRunEcsTaskPolicy
,PutItemToSnsSqsLambdaTable
をカスタマーマネージドポリシーとして作成し、それらのポリシーも含めてCustom_Lambda_Basic_Execution_Role
にアタッチしています。
# #----------------------------------------
# # IAM Policy for Lambda Execution Role
# #----------------------------------------
resource "aws_iam_policy" "LambdaRunEcsTaskPolicy" {
name = "LambdaRunEcsTaskPolicy"
path = "/"
description = "Allow Lambda to run ECS Task"
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "VisualEditor0",
"Effect" : "Allow",
"Action" : [
"iam:PassRole",
"ecs:RunTask"
],
"Resource" : "*"
}
]
})
}
resource "aws_iam_policy" "PutItemToSnsSqsLambdaTable" {
name = "PutItemToSnsSqsLambdaTable"
path = "/"
description = "Allow Lambda to putItem to DynamoDB sns_sqs_lambda_table"
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "VisualEditor0",
"Effect" : "Allow",
"Action" : "dynamodb:PutItem",
"Resource" : "arn:aws:dynamodb:ap-northeast-1:<アカウントID>:table/sns_sqs_lambda_table*"
}
]
})
}
# #----------------------------------------
# # IAM Role: Lambda Execution Role
# #----------------------------------------
resource "aws_iam_role" "Custom_Lambda_Basic_Execution_Role" {
name = "Custom_Lambda_Basic_Execution_Role"
assume_role_policy = jsonencode(
{
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
},
]
Version = "2012-10-17"
}
)
description = "Lambda Role for PubSub Application"
managed_policy_arns = [
"arn:aws:iam::aws:policy/AWSLambdaExecute",
"arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess",
"arn:aws:iam::aws:policy/AmazonSNSFullAccess",
"arn:aws:iam::aws:policy/CloudWatchFullAccess",
"arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole",
aws_iam_policy.LambdaRunEcsTaskPolicy.arn,
aws_iam_policy.PutItemToSnsSqsLambdaTable.arn
]
}
Lambda
続いてLambdaです。これまではコンソールにコードを直接書いてデプロイしてましたが、今回のIaC化に合わせて、zipファイルを作成してzipファイルをデプロイする方式に変更しました。
LambdaをTerraformでIaC化する際の大まかな手順イメージは以下のようになるかと思います。
-
data "archive_file"
でzipファイルを作成 - Lambdaコードをデプロイ元の環境に転記(今回の場合はコンソール⇒Cloud9へ転記)
-
resource "aws_lambda_function"
でzipファイルをデプロイするリソースを作成 - 必要に応じて不要な部分を削除(既存のdataリソース部分など)
一つのファイルにまとめて記載しています。
# #----------------------------------------
# # Authorizer Lambda Function
# #----------------------------------------
# data "aws_lambda_function" "AuthForPubSub" {
# function_name = "AuthForPubSub"
# }
# #----------------------------------------
# # Publisher Lambda Function
# #----------------------------------------
# data "aws_lambda_function" "deliverTopicToSNS_Test01" {
# function_name = "deliverTopicToSNS_Test01"
# }
# #----------------------------------------
# # Create zip file
# #----------------------------------------
data "archive_file" "AuthForPubSub" {
type = "zip"
source_file = "lambda_functions/src/auth_for_pubsub.py"
output_path = "lambda_functions/output/auth_for_pubsub.zip"
}
data "archive_file" "deliverTopicToSNS_Test01" {
type = "zip"
source_file = "lambda_functions/src/deliver_topic_sns.py"
output_path = "lambda_functions/output/deliver_topic_sns.zip"
}
data "archive_file" "subscribeTopicFromSNS_Test01" {
type = "zip"
source_file = "lambda_functions/src/subscribe_topic_sns.py"
output_path = "lambda_functions/output/subscribe_topic_sns.zip"
}
data "archive_file" "getDataFromDynamodbStream" {
type = "zip"
source_file = "lambda_functions/src/get_data_from_dynamodb_stream.py"
output_path = "lambda_functions/output/get_data_from_dynamodb_stream.zip"
}
# #----------------------------------------
# # Deply Lambda Configuration
# #----------------------------------------
resource "aws_lambda_function" "AuthForPubSub" {
filename = data.archive_file.AuthForPubSub.output_path
function_name = "AuthForPubSub"
handler = "auth_for_pubsub.lambda_handler"
description = "Authorizer Lambda to check header token."
role = aws_iam_role.Custom_Lambda_Basic_Execution_Role.arn
runtime = "python3.8"
memory_size = 128
timeout = 5
source_code_hash = data.archive_file.AuthForPubSub.output_base64sha256
tags = {
"lambda-console:blueprint" = "api-gateway-authorizer-python"
}
}
resource "aws_lambda_function" "deliverTopicToSNS_Test01" {
filename = data.archive_file.deliverTopicToSNS_Test01.output_path
function_name = "deliverTopicToSNS_Test01"
handler = "deliver_topic_sns.lambda_handler"
description = "run ECS fargate task to deliver sns topic."
role = aws_iam_role.Custom_Lambda_Basic_Execution_Role.arn
runtime = "python3.8"
memory_size = 128
timeout = 5
source_code_hash = data.archive_file.deliverTopicToSNS_Test01.output_base64sha256
environment {
variables = {
"ECS_CLUSTER" = "pubsub-cluster-pub"
"SUBNET_ID_1" = "subnet-0a8e33a542ddb7443"
"SUBNET_ID_2" = "subnet-02ba1bf978801ab20"
"TASK_DEFINITION" = "task-pubsub-pub:2"
}
}
}
resource "aws_lambda_function" "subscribeTopicFromSNS_Test01" {
filename = data.archive_file.subscribeTopicFromSNS_Test01.output_path
function_name = "subscribeTopicFromSNS_Test01"
handler = "subscribe_topic_sns.lambda_handler"
description = "subscribe message from sqs and put new item into DynamoDB."
role = aws_iam_role.Custom_Lambda_Basic_Execution_Role.arn
runtime = "python3.8"
memory_size = 128
timeout = 5
source_code_hash = data.archive_file.subscribeTopicFromSNS_Test01.output_base64sha256
}
resource "aws_lambda_function" "getDataFromDynamodbStream" {
filename = data.archive_file.getDataFromDynamodbStream.output_path
function_name = "getDataFromDynamodbStream"
handler = "get_data_from_dynamodb_stream.lambda_handler"
description = "get new item info from dynamodb stream and publish a new sns topic for email."
role = aws_iam_role.Custom_Lambda_Basic_Execution_Role.arn
runtime = "python3.8"
memory_size = 128
timeout = 5
source_code_hash = data.archive_file.getDataFromDynamodbStream.output_base64sha256
}
まずは# Create zip file
のセクションでデプロイするzipファイルを作成する内容を記載しています。zip化される実際の関数コードについては別ファイルに記載(転記)してます(ソースコードは以下に載せてます)。
次にデプロイするLambda関数の設定などを記載する# Deploy Lambda Configuration
セクションでLambdaの実行時の設定などを記載しています。
また、前回までのIaC化ではLambda関数は参照部分のみTerraformで記載してたので、data "aws_lambda_function"
を使っていましたが、今回でLambda自身もTerraformで管理するように変更したので、該当部分(コード一番上部分)はコメントアウトしています。
以下がLambda関数のコードを記載しているファイルになります。lambda_functions/src
配下としました。
例えばauth_for_pubsub.py
の中身は以下のようになっています。
import base64
import json
from logging import getLogger, INFO
logger = getLogger(__name__)
logger.setLevel(INFO)
def lambda_handler(event, context):
print("============ output event =================")
logger.info(json.dumps(event))
token = event['headers']['Authorization']
effect = 'Deny'
if token == 'abc':
effect = 'Allow'
context = {
"overwrite_message": "this is override message from authorizer."
}
json_context = json.dumps(context)
base64_context = base64.b64encode(json_context.encode('utf8'))
return_policy = {
'principalId': '*',
'policyDocument': {
'Version': '2012-10-17',
'Statement': [
{
'Action': '*',
'Effect': effect,
'Resource': event['methodArn']
}
]
},
'context': {
'overwrite_message': base64_context.decode('utf-8')
}
}
logger.info(json.dumps(return_policy))
return return_policy
他のpythonファイルについてもgithubにアップ予定ですので、興味あるかたはそちらを参照してください。アップが完了したら記事の最下部にリンクを足しておきます。
そして、前回まではAPI GatewayのTerraform記載部分でdata.aws_lambda_function
としてLambda関数を紐づけていましたが、今回Lambda関数もTerraform管理としたので、該当部分をaws_lambda_function
としTerraformリソースを参照するように修正しています。
# (一部抜粋)
resource "aws_api_gateway_authorizer" "pubsubApi" {
name = "PubSubLambdaAuthorizer"
rest_api_id = aws_api_gateway_rest_api.pubsubApi.id
# authorizer_uri = data.aws_lambda_function.AuthForPubSub.invoke_arn
authorizer_uri = aws_lambda_function.AuthForPubSub.invoke_arn
authorizer_result_ttl_in_seconds = 0
type = "REQUEST"
}
...
resource "aws_api_gateway_integration" "pubsubApi" {
rest_api_id = aws_api_gateway_rest_api.pubsubApi.id
resource_id = aws_api_gateway_resource.pubsubApi.id
http_method = "POST"
type = "AWS_PROXY"
# uri = data.aws_lambda_function.deliverTopicToSNS_Test01.invoke_arn
uri = aws_lambda_function.deliverTopicToSNS_Test01.invoke_arn
content_handling = "CONVERT_TO_TEXT"
integration_http_method = "POST"
}
以上で今回やろうとしていたIaC化部分は完了しました。
おわりに
Lambda関数をTerraformで管理する場合は環境内にzipを作成する手法(今回はこちらを採用)とS3へzipをアップロードしてそこからデプロイする手法が主に考えられると思います。実際に複数人でより多くのLambda関数を開発・管理していく場合はS3を経由する方法が現実的かもしれません。
また、Lambda関数Terraform化する際に、Lambdaで間接的に利用するAWSリソース(CWログとかIAMとか)を先にTerraformで記載しておいた方が、Lambda自身をTerraformで記載する際にスムーズになるとも思いました。
次回以降、まだIaC化できていない他の部分を実施していきたいと思います。
参考サイト