AWS
DynamoDB
lambda
Terraform

[Terraform] DynamoDBをトリガーとしたLambdaの設定方法

やりたいこと

記事タイトルそのままです。
DynamoDBのデータ更新をトリガーとして起動されるLambdaを、Terraformで設定したい。

tfファイルの記述例

IAM

aws.iam.tf
// Lambda用ロール
resource "aws_iam_role" "sample-lambda-role" {
    name = "sample-lambda-dynamodb-trigger"
    assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Effect": "Allow",
            "Sid": ""
        }
    ]
}
EOF
}

// Lambdaログ出力権限
resource "aws_iam_role_policy" "sample-lambda-log-output" {
    role = "${aws_iam_role.sample-lambda-role.id}"
    name = "lambda-log-output"
    policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*",
            "Effect": "Allow"
        }
    ]
}
EOF
}

// DynamoDB Stream の読み込み権限
resource "aws_iam_role_policy" "sample-dynamodb-stream" {
    role = "${aws_iam_role.sample-lambda-role.id}"
    name = "dynamodb-stream"
    policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "dynamodb:DescribeStream",
                "dynamodb:GetRecords",
                "dynamodb:GetShardIterator",
                "dynamodb:ListStreams"
            ],
            "Resource": "${aws_dynamodb_table.sample-table.stream_arn}",
            "Effect": "Allow"
        }
    ]
}
EOF
}
  • DynamoDB Stream からデータを読み取るためのポリシーが必要です。この権限が付いていないと、トリガー設定でコケます。

Lambda

aws.lambda.tf
// Lambda Function を ZIP化
data "archive_file" "lambda-zip_sample-function" {
    type        = "zip"
    output_path = "./_zip/sample-function.zip"
    source {
        filename = "lambda_function.py"
        content  = <<EOF
import json
def lambda_handler(event, context):
    result = json.dumps(event)
    print(result)
    return result
EOF
    }
}

// Lambda Function 作成
resource "aws_lambda_function" "sample-function" {
    function_name      = "sample-function-name"
    handler            = "lambda_function.lambda_handler"
    filename           = "${data.archive_file.lambda-zip_sample-function.output_path}"
    source_code_hash   = "${data.archive_file.lambda-zip_sample-function.output_base64sha256}"
    memory_size        = 128
    timeout            = 300
    runtime            = "python3.6"
    role               = "${aws_iam_role.sample-lambda-role.arn}"
    description        = "Dynamo Trigger Test"
}
  • この記述例ではイベント情報を出力するだけの Lambda Function をtfファイルに直書きしています。 zipファイルの置き場として、ディレクトリ ./_zip/ が必要。
  • functionファイルを作成しておいて archive_file でZIP化しても、あらかじめzipファイルを用意しておいても構いません。

DynamoDB

aws.dynamodb.tf
resource "aws_dynamodb_table" "sample-table" {
  name           = "sample-table-name"
  read_capacity  = 1
  write_capacity = 1
  hash_key       = "key1"
  range_key      = "key2"
  attribute {
    name = "key1"
    type = "S"
  }
  attribute {
    name = "key2"
    type = "S"
  }
  stream_enabled   = true
  stream_view_type = "NEW_AND_OLD_IMAGES"
}


//
resource "aws_lambda_event_source_mapping" "sample-table-trigger" {
    depends_on         = ["aws_dynamodb_table.sample-table", "aws_iam_role_policy.sample-dynamodb-stream"]
    batch_size         = 100
    event_source_arn   = "${aws_dynamodb_table.sample-table.stream_arn}"
    enabled            = true
    function_name      = "${aws_lambda_function.sample-function.arn}"
    starting_position  = "TRIM_HORIZON"
}
  • aws_dynamodb_table に下記のパラメータを記述して、DynamoDB Stream を有効化。
    • stream_enabled = true
    • stream_view_type = (任意)
  • aws_lambda_event_source_mapping で Lambdaのトリガーとして DynamoDB Stream を追加。
  • aws_lambda_event_source_mapping は Lambda が主語のような気はするものの、DynamoDB Stream の存在に依存する設定なので、DynamoDB用のtfファイルにまとめてます。個人的な好みです。

注意事項

一発目はこれでOK。

Terraform実行一発目から上記内容で設定する場合は、問題なく設定できるはずです。

DynamoDB Stream を後付けする場合はNG。

既にDynamoDBのTableを作成済みで、後から stream_enabled = true を追加設定する場合は、単純にtfファイルに上記の通り記述して実行しても上手く行きません。(Terraform v0.11.3 時点)
下記のようなエラーが発生するはずです。

aws_iam_role_policy.sample-dynamodb-stream: Resource 'aws_dynamodb_table.sample-table' does not have attribute 'stream_arn' for variable 'aws_dynamodb_table.sample-table.stream_arn'

aws_dynamodb_tablestream_arn が無いよ!」と怒られてます。

なぜ?

Terraform は、Terraform を利用して設定したリソースの情報をtfstateファイルに保持しています。
そして次回実行するときは、tfstateファイルに保持されている現在のリソース情報と、新たなtfファイルの内容を突き合わせて最小限のアップデートをします。
その時、リソース情報の整合性チェック等(Plan)を行ってから、設定反映(Apply)を実行する仕組みになっています。
DynamoDB Stream の後付けと同時にmappingをしようとすると、現状としては stream_arn が存在しないのにそれを利用してリソースを設定しようとしているため、Plan の段階でNGになります。
「Planで stream_arn を補完してくれればいいのに」とは思いますが、Stream名には作成日時が自動的に入るので、実際に作ってからでないと確定できないんですよね。

... というのが原因と考えられます。
Terraform のソースを見れば原因がハッキリわかるんでしょうが、私にはそこまでのモチベーションは無いです。

では、どうすれば?

段階を踏んで実行しましょう。
1. stream_enabled = true で実行しDynamoDB Stream を有効化する。これで次回から stream_arn が参照できるようになる。
2. DynamoDB Stream の ARN に依存するリソース設定を追加する。

もしかしたら Terraform の実行オプションで回避出来る等、他に方法があるかもしれませんが、私には見つけられませんでした。