概要
個人的に AWS APIGateway, Lambda を利用した discord BOT を AWS マネージコンソールでぽちぽち作成していたのですが、これを IaC で管理したいと思い、terraform で import 作業をしていました。
この際、terraform の AWS モジュールのうち API Gateway V2 の aws_apigatewayv2_api
でないと import できなかった事象に遭遇しました。
この v2 の terraform 実装があまりネット上に見つからなかったので、本記事では AWS が提供する terraform モジュールのリソースの関係を確認しながら、実装例を紹介します。
前提
- AWS APIGateway や Lambda 自体の知識
- terraform の構文
APIGateway のリソース関係
色んなリソースがありすぎて、普通に意味が分からなくなりました。なのでここで簡単に整理しておきます。
aws_apigatewayv2_api
このモジュールが APIGateway の本体。基本的にこのモジュールに他のリソースを紐づけていく。
aws_apigatewayv2_stage
APIGateway には、検証環境・ステージング環境・本番環境のような、単一の APIGateway で Lambda の向き先を変えるようなことができる仕組みがあるみたいです。
このモジュールでは、そのような環境設定を記述します。
また、このモジュールでロギング設定も行います。
aws_apigatewayv2_deployment
stage に紐づく、デプロイ状態のリソース。本記事では aws_apigatewayv2_stage
に auto deploy を指定するので、設定しなくてよいかも。
aws_apigatewayv2_integration
と aws_lambda_permission
aws_apigatewayv2_integration
は APIGateway と Lambda を紐づける、いわゆる "Lambda 統合" です。
aws_lambda_permission
は、Lambda 側で APIGateway 側から呼ばれる許可を記述するリソースです。
両方がなければ、うまく APIGateway から Lambda を呼ぶことができません。
aws_apigatewayv2_route
など
route 定義です。今回 APIGateway の route 定義は、body
属性に OpenAPI の json を渡して処理をするので、別途リソースで定義はしないので、省略します。
terraform で記述する
本記事で利用する provider 設定は下記です。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.7"
}
http = {
source = "hashicorp/http"
version = "~> 3.0"
}
}
required_version = ">= 1.5.0"
backend "local" {
path = ".cache/terraform.tfstate"
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
"Project" = "Discord-Bot"
"Managed" = "Terraform"
}
}
}
aws_apigatewayv2_api
の定義
APIGateway の route 定義では、OpenAPI の json, yaml で設定ができます。terraform のモジュールでも、同様に設定が可能です。
本記事では、json と terraform の template_file
機能を利用しています。${hoge}
の形で、テンプレート変数を埋められるので、それも利用しています。
{
"openapi" : "3.0.1",
"info" : {
"title" : "BOT-API",
"description" : "Created by AWS Lambda",
"version" : "2024-07-11 12:37:22UTC"
},
"paths" : {
"${interaction_path}" : {
"x-amazon-apigateway-any-method" : {
"responses" : {
"default" : {
"description" : "Default response for ANY ${interaction_path}"
}
},
"x-amazon-apigateway-integration" : {
"payloadFormatVersion" : "1.0",
"type" : "aws_proxy",
"httpMethod" : "POST",
"uri" : "${lambda_invoke_arn}",
"connectionType" : "INTERNET"
}
}
}
},
"x-amazon-apigateway-cors" : {
"maxAge" : -42,
"allowCredentials" : false
},
"x-amazon-apigateway-importexport-version" : "1.0"
}
resource "aws_apigatewayv2_api" "api" {
name = "Discord-BOT-API"
description = "API for Discord Bot interaction"
protocol_type = "HTTP"
body = data.template_file.gateway_body.rendered
}
data "template_file" "gateway_body" {
template = file("${path.module}/api_gateway_body.json")
vars = {
lambda_invoke_arn = aws_lambda_function.aws_lambda_bot.invoke_arn // 実際の Lambda に差し替えて下さい
interaction_path = "/Discord-BOT"
}
}
stage 定義
本記事では、環境は一つしか用意しない ( = default のみ用意する) ので、stage は一つだけ定義します。
throttling_rate_limit
と throttling_burst_limit
の設定は、最小にしています。
throttling_rate_limit
の設定値がデフォルトだと 0 になっているため、default_route_settings
を設定する場合は明示的に指定しておいた方が良いです。
参考: https://github.com/hashicorp/terraform-provider-aws/issues/30373
resource "aws_apigatewayv2_stage" "default" {
name = "default"
description = "Created by AWS Lambda"
api_id = aws_apigatewayv2_api.api.id
auto_deploy = true // teraform apply で紐づく更新があれば、自動で deploy も作成される
// ログを利用しない場合はこの block 自体削除する
access_log_settings {
destination_arn = aws_cloudwatch_log_group.apigateway_log_group.arn
format = replace(file("${path.module}/api_gateway_log_format.json"), "\r\n", "")
}
default_route_settings {
logging_level = "ERROR"
detailed_metrics_enabled = true
throttling_rate_limit = 1
throttling_burst_limit = 2
}
}
// 定義不要かも...
resource "aws_apigatewayv2_deployment" "default" {
api_id = aws_apigatewayv2_api.api.id
description = "Automatic deployment triggered by changes to the Api configuration"
}
ログの設定は、今回別ファイルで設定しました。
{
"requestId": "$context.requestId",
"ip": "$context.identity.sourceIp",
"caller": "$context.identity.caller",
"user": "$context.identity.user",
"requestTime": "$context.requestTime",
"responseLatency": "$context.responseLatency",
"protocol": "$context.protocol",
"httpMethod": "$context.httpMethod",
"resourcePath": "$context.resourcePath",
"status": "$context.status",
"error_message": "$context.error.message",
"error_response_type": "$context.error.responseType",
"integrationError": "$context.integration.error",
"integrationErrorMessage": "$context.integrationErrorMessage",
"integrationStatus": "$context.integration.integrationStatus",
"integrationLatency": "$context.integration.latency"
}
更に、cloudwatch の設定例も記しておきます。
APIGateway のログ設定は少しだけ特殊で、aws_api_gateway_account
モジュールでリソース定義します。
resource "aws_cloudwatch_log_group" "apigateway_log_group" {
name = "/aws/apigateway/Discord-BOT"
retention_in_days = 1
}
resource "aws_iam_role" "apigateway_putlog" {
name = "Discord-BOT-APIGatewayPutLogRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "apigateway.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "apigateway_putlog" {
role = aws_iam_role.apigateway_putlog.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
}
resource "aws_api_gateway_account" "api" {
cloudwatch_role_arn = aws_iam_role.apigateway_putlog.arn
}
ログ設定していても、ログが出ないケースがあることには注意が必要です。
API Gateway は次のログを生成しない場合があります。
413 Request Entity Too Large エラー。
大量の 429 Too Many Requests エラー。
API マッピングのないカスタムドメインに送信されたリクエストによる 400 シリーズのエラー。
内部の障害による 500 シリーズのエラー。
詳細については、「REST API を監視する」を参照してください。
参考: https://repost.aws/ja/knowledge-center/api-gateway-missing-cloudwatch-logs
Lambda 統合
紐づけ設定と許可設定が必要です。IAM などの追加はモジュール側に設定されていると思われるので不要です。
// APIGateway -> Lambda の紐づけ設定
resource "aws_apigatewayv2_integration" "lambda" {
api_id = aws_apigatewayv2_api.api.id
connection_type = "INTERNET"
integration_method = "POST"
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.aws_lambda_bot.invoke_arn // 実際の値を設定してください
timeout_milliseconds = 30000
}
// Lambda が APIGateway から呼ばれる許可の設定
resource "aws_lambda_permission" "apigateway_invoke" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.aws_lambda_bot.function_name // 実際の値を設定してください
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*"
}
まとめ
本記事では、APIGateway と Lambda 統合の terraform 実装例を紹介しました。
特に aws_apigatewayv2_api
ベースの実装例は情報例があまりなかったので、誰かの参考になれば幸いです。