TerraformでAWS Lambdaのauto scaling（provisioned concurrency）を設定してみた

# はじめに
AWS Lambda + SpringNativeでWebAPIを構築したときに、
SpringNativeといえどもコールドスタートが発生するため、
provisioned concurrencyを設定していました。

ただしprovisioned concurrencyを固定値にしていたので、
auto scalingに変更しました。

# TL;DL
比較的簡単にできました。
ソースは[ここ](https://github.com/swdevsmz/lambda-autoscale)に置いてあります。


# やったこと

## Lambda関数の作成

Lambda関数を作成します。
関数は複数個作ることを想定しているため、
関数のパラメーターをリスト化しています。

パラメーターの中に予約実行を設定するかのフラグを持たせています。
また予約実行をする場合に`スケジュールされたオートスケール`か`ターゲット追跡のオートスケール`かを設定するようにしています。

これらのパラメーターを使い、lambdaのリソースを作るときにfor_eachで関数を作成するようにしています。

あと検証用なのでLambda関数URLを使っています。

```Terraform:lambda.tf
###################################################################################
# Lambda関数関連リソース
###################################################################################
locals {
  provisioned_type_tracking  = "tracking"
  provisioned_type_scheduled = "scheduled"
  # 関数リスト
  function_list = [
    { function_name = "function-1", memory_size = 128, timeout = 3, provisioned = { enable = true, target_alias = "dev", type = local.provisioned_type_tracking, min_capacity = 1, max_capacity = 2 } },
    { function_name = "function-2", memory_size = 128, timeout = 3, provisioned = { enable = true, target_alias = "dev", type = local.provisioned_type_scheduled, min_capacity = 0, max_capacity = 1 } },
  ]
  # エイリアス名
  alias_name = "dev"
}

# Lambda関数本体
resource "aws_lambda_function" "main" {
  for_each = { for i in local.function_list : i.function_name => i }

  # Lambda関数ごとの設定
  function_name = each.value.function_name
  timeout       = each.value.timeout
  memory_size   = each.value.memory_size

  # 全lambda関数の共通設定
  filename = data.archive_file.main.output_path
  role     = aws_iam_role.lambda.arn
  runtime  = "nodejs16.x"
  handler  = "index.handler"
  publish  = true
}

# Lambda関数URL
resource "aws_lambda_function_url" "main" {
  for_each = aws_lambda_function.main

  function_name      = each.value.function_name
  qualifier          = local.alias_name
  authorization_type = "NONE"
}

# Lambdaエイリアス
resource "aws_lambda_alias" "main" {
  for_each = aws_lambda_function.main

  function_name    = each.value.arn
  function_version = each.value.version
  name             = local.alias_name
  description      = "開発用エイリアス"
}
```

## スケジュールされたオートスケールの作成

関数のパラメーターリストからスケジュールされたオートスケールを設定する関数のみを抽出して、オートスケールを作成しています。

```Terraform:lambda.tf
###################################################################################
# Lambda関連オートスケールソース（スケジュール）
###################################################################################
# スケジュールされたオートスケール用のターゲットを作成
resource "aws_appautoscaling_target" "scheduled" {
  depends_on = [aws_lambda_function.main]

  for_each = { for i in local.function_list : i.function_name => i if can(tobool(i.provisioned.enable)) && strcontains(i.provisioned.type, local.provisioned_type_scheduled) }

  resource_id        = "function:${each.value.function_name}:${local.alias_name}"
  min_capacity       = each.value.provisioned.min_capacity
  max_capacity       = each.value.provisioned.max_capacity
  scalable_dimension = "lambda:function:ProvisionedConcurrency"
  service_namespace  = "lambda"
}

# スケジュールされたスケールイン
resource "aws_appautoscaling_scheduled_action" "scheduled_in" {
  for_each = aws_appautoscaling_target.scheduled

  service_namespace  = each.value.service_namespace
  resource_id        = each.value.resource_id
  scalable_dimension = each.value.scalable_dimension
  name               = "scheduled_in"
  schedule           = "cron(40 21 ? * * *)"
  timezone           = "Asia/Tokyo"

  scalable_target_action {
    min_capacity = each.value.min_capacity
    max_capacity = each.value.min_capacity
  }
}

# スケジュールされたスケールアウト
resource "aws_appautoscaling_scheduled_action" "scheduled_out" {
  for_each = aws_appautoscaling_target.scheduled

  service_namespace  = each.value.service_namespace
  resource_id        = each.value.resource_id
  scalable_dimension = each.value.scalable_dimension
  name               = "scheduled_out"
  schedule           = "cron(20 21 ? * * *)"
  timezone           = "Asia/Tokyo"

  scalable_target_action {
    min_capacity = each.value.max_capacity
    max_capacity = each.value.max_capacity
  }
}
```

## ターゲット追跡のオートスケールの作成

こちらもパラメーターからターゲット追跡の対象となる関数のみを抽出して設定しています。

```Terraform:lambda.tf
###################################################################################
# Lambda関連オートスケールソース（ターゲット追跡型）
###################################################################################
# ターゲット追跡型のオートスケール用のターゲットを作成
resource "aws_appautoscaling_target" "tracking" {
  depends_on = [aws_lambda_function.main]

  for_each = { for i in local.function_list : i.function_name => i if can(tobool(i.provisioned.enable)) && strcontains(i.provisioned.type, local.provisioned_type_tracking) }

  resource_id        = "function:${each.value.function_name}:${local.alias_name}"
  min_capacity       = each.value.provisioned.min_capacity
  max_capacity       = each.value.provisioned.max_capacity
  scalable_dimension = "lambda:function:ProvisionedConcurrency"
  service_namespace  = "lambda"
}
# ターゲット追跡（70%）
resource "aws_appautoscaling_policy" "tracking_policy" {
  for_each = aws_appautoscaling_target.tracking

  resource_id        = each.value.resource_id
  service_namespace  = each.value.service_namespace
  scalable_dimension = each.value.scalable_dimension
  name               = "tracking"
  policy_type        = "TargetTrackingScaling"

  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "LambdaProvisionedConcurrencyUtilization"
    }
    target_value = 0.7
  }
}
```