🐥

Amazon API Gateway by Terraformでファイル変更時に再デプロイする

2024/03/24に公開

小ネタです。
Terraformaws_api_gateway_deploymentaws_apigatewayv2_deploymentリソースによってAPI Gatewayのデプロイを管理できます。

しかし、これらのリソースをterraform applyして一度作成すると、API Gatewayリソースを変更しても再デプロイされません。過去ではstage_descriptionにファイルのハッシュ値を埋め込むことで再デプロイを行う方法がありましたが、現在はtriggersが実装されているため、それを使う方法が推奨されています。

resource/aws_api_gateway_deployment: Add triggers argument by bflad · Pull Request #13054 · hashicorp/terraform-provider-aws
resource/aws_apigatewayv2_deployment: Add triggers argument by bflad · Pull Request #13055 · hashicorp/terraform-provider-aws

triggersの使い方についてドキュメントから引用します。

# API Gateway v1
resource "aws_api_gateway_deployment" "example" {
  rest_api_id = aws_api_gateway_rest_api.example.id

  triggers = {
    # NOTE: The configuration below will satisfy ordering considerations,
    #       but not pick up all future REST API changes. More advanced patterns
    #       are possible, such as using the filesha1() function against the
    #       Terraform configuration file(s) or removing the .id references to
    #       calculate a hash against whole resources. Be aware that using whole
    #       resources will show a difference after the initial implementation.
    #       It will stabilize to only change when resources change afterwards.
    redeployment = sha1(jsonencode([
      aws_api_gateway_resource.example.id,
      aws_api_gateway_method.example.id,
      aws_api_gateway_integration.example.id,
    ]))
  }

  lifecycle {
    create_before_destroy = true
  }
}

# API Gateway v2
resource "aws_apigatewayv2_deployment" "example" {
  api_id      = aws_apigatewayv2_api.example.id
  description = "Example deployment"

  triggers = {
    redeployment = sha1(join(",", tolist([
      jsonencode(aws_apigatewayv2_integration.example),
      jsonencode(aws_apigatewayv2_route.example),
    ])))
  }

  lifecycle {
    create_before_destroy = true
  }
}

このようにAPI Gatewayリソースの値をjsonencode関数によってエンコードしたものをsha1関数でハッシュ化してtriggersに渡すことで、リソースの変更を検知して再デプロイを行います。しかし、API Gatewayのリソースが多い場合、このように手動で記述するのは面倒です。そこで、API Gatewayリソースおよびバックエンド処理を行うAWS Lambda関数のPythonスクリプトに変更が加わった場合に、API Gatewayを再デプロイする方法を紹介します。

ディレクトリ階層

以下のようなディレクトリ階層を想定します。

apigateway
├── main.tf
├── outputs.tf
└── variables.tf
│
lambda_functions
├── foo_function
│   └── handler.py
└── bar_function
    └── handler.py

Terraform実装例

次の例のように、API GatewayのtfファイルとAWS Lambda関数内のスクリプトファイルのsha1を計算し、それをtriggersに渡すことで、ファイル変更時に再デプロイを行います。

locals {
  # API Gatewayのtfファイル
  module_file_paths = [
    "main.tf",
    "variables.tf",
  ]
  # AWS Lambda関数(lambda_functions)内のスクリプトファイル
  lambda_functions_path       = "../lambda_functions"
  lambda_functions_file_paths = fileset(local.lambda_functions_path, "**/*.py")

  combined_paths = concat(
    local.module_file_paths,
    [for file in local.lambda_functions_file_paths : "${local.lambda_functions_path}/${file}"]
  )

  # API Gatewayデプロイをトリガーとするファイル郡のsha1
  trigger_files_sha1 = sha1(join("", [for file in local.combined_paths : filesha1(file)]))
}

resource "aws_api_gateway_deployment" "example" {
  rest_api_id = aws_api_gateway_rest_api.example.id

  triggers = {
    redeployment = local.trigger_files_sha1
  }

  lifecycle {
    create_before_destroy = true
  }
}

対象となるファイルのどれかが変更されると次のようにreplacedとして検知されるようになります。

  # aws_api_gateway_deployment.example must be replaced
+/- resource "aws_api_gateway_deployment" "example" {
      ~ created_date  = "2024-02-19T15:03:05Z" -> (known after apply)
      ~ execution_arn = "arn:aws:execute-api:ap-northeast-1:138713343029:hogehoge/" -> (known after apply)
      ~ id            = "xxxxxxx" -> (known after apply)
      ~ invoke_url    = "https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/" -> (known after apply)
      ~ triggers      = { # forces replacement
          ~ "redeployment" = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -> "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
        }
        # (1 unchanged attribute hidden)
    }

まとめ

ファイルのハッシュ値によるトリガーを使うことで、API Gatewayのリソース変更時に再デプロイする方法を紹介しました。API Gatewayリソースを指定するよりも取りこぼしがなく、手間がかからないのでオススメだと思います。

参考URL

Discussion