2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APIGateway の Lambda 統合を terraform で作る (aws_apigatewayv2_api 利用)

Posted at

概要

個人的に 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_integrationaws_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} の形で、テンプレート変数を埋められるので、それも利用しています。

api_gateway_body.json
{
  "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"
}
api_gateway.tf
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_limitthrottling_burst_limit の設定は、最小にしています。

throttling_rate_limit の設定値がデフォルトだと 0 になっているため、default_route_settings を設定する場合は明示的に指定しておいた方が良いです。
参考: https://github.com/hashicorp/terraform-provider-aws/issues/30373

api_gateway.tf
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"
}

ログの設定は、今回別ファイルで設定しました。

api_gateway_log_format.json
{
  "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 モジュールでリソース定義します。

api_gateway.tf
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 などの追加はモジュール側に設定されていると思われるので不要です。

api_gateway.tf
// 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 ベースの実装例は情報例があまりなかったので、誰かの参考になれば幸いです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?