★★ 中級

07. Lambda / API Gateway

サーバを管理せずに HTTP API を作る最小構成。Lambda(実行)API Gateway HTTP API(HTTP 受付)IAM ロール(権限)の 3 点セット。

リクエスト処理の流れ

[クライアント] ───HTTPS──→ [API Gateway HTTP API]
                                  │ AWS_PROXY 統合
                                  ↓
                            [Lambda 関数]
                                  │ IAM Role の権限で
                                  ↓
                            [DynamoDB / S3 / RDS など]

料金は 呼ばれた回数 × 実行時間 のみ。リクエストが来ない夜中は $0。

関数コードの zip 化

関数のコードは zip にして Lambda に渡します。Terraform の archive_file で生成可。

data "archive_file" "hello" {
  type        = "zip"
  source_dir  = "${path.module}/lambda/hello"
  output_path = "${path.module}/build/hello.zip"
}

例: lambda/hello/index.js

exports.handler = async (event) => {
  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      message: "Hello from Lambda!",
      path: event.rawPath,
    }),
  };
};

Lambda 用 IAM ロール

data "aws_iam_policy_document" "lambda_assume" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

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

resource "aws_iam_role" "lambda" {
  name               = "lambda-hello"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

# CloudWatch Logs に書く最小権限
resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

aws_lambda_function

resource "aws_lambda_function" "hello" {
  function_name = "hello"
  role          = aws_iam_role.lambda.arn

  filename         = data.archive_file.hello.output_path
  source_code_hash = data.archive_file.hello.output_base64sha256

  runtime = "nodejs20.x"
  handler = "index.handler"

  memory_size = 256       # MB
  timeout     = 10        # 秒
  architectures = ["arm64"]   # Graviton(同性能で 20% 安い)

  environment {
    variables = {
      LOG_LEVEL = "info"
    }
  }
}

Lambda の主要属性

属性デフォルト用途
memory_size128大きいほど CPU 性能も上がる
timeout3 秒最大 15 分
architecturesx86_64arm64 推奨(Graviton で割安)
vpc_config無しVPC 内のリソースにアクセスする時のみ
reserved_concurrent_executions無し同時実行数の上限
layers無し共通ライブラリの分離(ARN 指定)

aws_apigatewayv2_api(HTTP API)

API Gateway には REST API(v1)と HTTP API(v2)がありますが、新規は HTTP API(v2)が標準。料金が約 70% 安く、構成もシンプル。

resource "aws_apigatewayv2_api" "main" {
  name          = "hello-api"
  protocol_type = "HTTP"

  cors_configuration {
    allow_origins = ["*"]
    allow_methods = ["GET", "POST"]
    allow_headers = ["Content-Type", "Authorization"]
    max_age       = 86400
  }
}

integration / route / stage

# Integration: 「API → Lambda」の接続
resource "aws_apigatewayv2_integration" "hello" {
  api_id           = aws_apigatewayv2_api.main.id
  integration_type = "AWS_PROXY"
  integration_uri  = aws_lambda_function.hello.invoke_arn

  payload_format_version = "2.0"
  timeout_milliseconds   = 10000
}

# Route: 「GET /hello → 上の integration」
resource "aws_apigatewayv2_route" "hello" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "GET /hello"
  target    = "integrations/${aws_apigatewayv2_integration.hello.id}"
}

# Stage: 公開環境(HTTP API は $default を使うのが便利)
resource "aws_apigatewayv2_stage" "default" {
  api_id      = aws_apigatewayv2_api.main.id
  name        = "$default"
  auto_deploy = true

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api.arn
    format = jsonencode({
      requestId      = "$context.requestId"
      sourceIp       = "$context.identity.sourceIp"
      requestTime    = "$context.requestTime"
      httpMethod     = "$context.httpMethod"
      routeKey       = "$context.routeKey"
      status         = "$context.status"
      responseLength = "$context.responseLength"
    })
  }
}

resource "aws_cloudwatch_log_group" "api" {
  name              = "/aws/apigw/hello-api"
  retention_in_days = 14
}

aws_lambda_permission

API Gateway が Lambda を呼ぶ権限を 明示 する必要があります。これを忘れると 500 エラーで詰まります。

resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.hello.function_name
  principal     = "apigateway.amazonaws.com"

  # この API & 任意のステージ/メソッド/リソース から
  source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}

完成形と動作確認

output "api_url" {
  value = aws_apigatewayv2_stage.default.invoke_url
}
$ terraform apply
$ curl "$(terraform output -raw api_url)/hello"
{"message":"Hello from Lambda!","path":"/hello"}