TerraformでEventBridge SchedulerのAtスケジュールを使って時限起動するLambdaを自動構築する

# はじめに
2022/11/10にEventBridgeにアップデートが入り、これまでのスケジュール方法以外にAtな起動がAsia/Tokyoのタイムゾーンの時間記述で作成できるようになった。これは超便利！
Terraformも2022/11/25に早々に[対応された](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/scheduler_schedule)ようだ。

ということで、さっそくTerraformでサクッと自動構築をしてみよう。

# 起動するLambda
シンプルにイベントデータを表示するだけで作っておく。

```python:example.py
import pprint

def lambda_handler(event, context):
  pprint.pprint(event)
```

上記を実行するLambdaの定義として以下のようにTerraformで作成する。
権限はこの後EvnetBridge Scheduler側のサービスロールに付与するため、Lambda Permissionの設定は不要のようだ。

```HCL
################################################################################
# Lambda                                                                       #
################################################################################
data "archive_file" "example" {
  type        = "zip"
  source_dir  = "../scripts/example"
  output_path = "../outputs/example.zip"
}

resource "aws_lambda_function" "example" {
  depends_on = [
    aws_cloudwatch_log_group.lambda,
  ]

  function_name    = local.lambda_function_name
  filename         = data.archive_file.example.output_path
  role             = aws_iam_role.lambda.arn
  handler          = "example.lambda_handler"
  source_code_hash = data.archive_file.example.output_base64sha256
  runtime          = "python3.9"

  memory_size = 128
  timeout     = 600
}

################################################################################
# IAM Role for Lambda                                                          #
################################################################################
resource "aws_iam_role" "lambda" {
  name               = local.iam_role_name_lambda
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

data "aws_iam_policy_document" "lambda_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "lambda.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "lambda_custom" {
  name   = local.iam_policy_name_lambda
  role   = aws_iam_role.lambda.name
  policy = data.aws_iam_policy_document.lambda_custom.json
}

data "aws_iam_policy_document" "lambda_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      "*",
    ]
  }
}

################################################################################
# CloudWatch Logs                                                              #
################################################################################
resource "aws_cloudwatch_log_group" "lambda" {
  name              = "/aws/lambda/${local.lambda_function_name}"
  retention_in_days = 3
}

################################################################################
# Lambda Permission                                                            #
################################################################################
resource "aws_lambda_permission" "allow_apigateway" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.example.function_name
  principal     = "scheduler.amazonaws.com"
}
```


# EventBridge Schedulerを動かすのに必要なIAMロール
IAMロールは以下のように作成する。信頼関係されたエンティティのサービスとして```scheduler.amazonaws.com```を指定しよう。
今回は単純にLambda実行するだけなので、個別の権限としては```lambda:InvokeFunction```があれば良い。

```HCL
################################################################################
# IAM Role for EventBridge Scheduler                                           #
################################################################################
resource "aws_iam_role" "eventbridge_scheduler" {
  name               = local.iam_role_name_eventbridge_scheduler
  assume_role_policy = data.aws_iam_policy_document.eventbridge_scheduler_assume.json
}

data "aws_iam_policy_document" "eventbridge_scheduler_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "scheduler.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "eventbridge_scheduler_custom" {
  name   = local.iam_policy_name_eventbridge_scheduler
  role   = aws_iam_role.eventbridge_scheduler.name
  policy = data.aws_iam_policy_document.eventbridge_scheduler_custom.json
}

data "aws_iam_policy_document" "eventbridge_scheduler_custom" {
  statement {
    effect = "Allow"

    actions = [
      "lambda:InvokeFunction",
    ]

    resources = [
      "*",
    ]
  }
}
```

# EvnetBridge Schedulerの定義
さて、↑ここまでで準備は整ったので、あとは本丸のEventBridge Schedulerの定義だ。
ポイントは、```schedule_expression```でatで指定している点だ。これは```schedule_expression_timezone```で指定したタイムゾーンで動いてくれる。```flexible_time_window```は、時間をずらして起動したい場合等に使えるが今回はOFFにする。```target```で↑で作ったLambdaとIAMロールのARNを指定すれば準備完了だ。

```HCL
################################################################################
# EventBridge Scheduler                                                        #
################################################################################
resource "aws_scheduler_schedule_group" "example" {
  name = local.eventbridge_scheduler_group_name
}

resource "aws_scheduler_schedule" "example" {
  name       = local.eventbridge_scheduler_schedule_name
  group_name = aws_scheduler_schedule_group.example.name

  state = "ENABLED"

  schedule_expression = "at(2022-12-10T22:18:00)"
  schedule_expression_timezone = "Asia/Tokyo"

  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = aws_lambda_function.example.arn
    role_arn = aws_iam_role.eventbridge_scheduler.arn
  }
}
```

# EvnetBridge SchedulerがLambdaに渡すイベントの形式
さて、上記で実行したLambdaの結果をCloudWatchで確認すると、以下のようにJSONが出力されている。
残念ながら、```time```の部分はUTCになってしまうが、いつ起動予定のタスクだったかは確認できるようだ。

```JSON
{
  "account": "XXXXXXXXXXXX",
  "detail": "{}",
  "detail-type": "Scheduled Event",
  "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:scheduler:ap-northeast-1:XXXXXXXXXXXX:schedule/eventbridge-scheduler-example-group/eventbridge-scheduler-example-schedule"
  ],
  "source": "aws.scheduler",
  "time": "2022-12-10T13:18:00Z",
  "version": "0"
}
```

これで、Atで時限起動するLambdaを簡単に作れるようになった！