07. Lambda / API Gateway
サーバを管理せずに HTTP API を作る最小構成。Lambda(実行)+API Gateway HTTP API(HTTP 受付)+IAM ロール(権限)の 3 点セット。
この章の目次
リクエスト処理の流れ
料金は 呼ばれた回数 × 実行時間 のみ。リクエストが来ない夜中は $0。
関数コードの zip 化
関数のコードは zip にして Lambda に渡します。Terraform の archive_file で生成可。
# archive_file は terraform プロバイダ標準のデータソース
# source_dir のファイル群を zip にまとめて output_path に置く
data "archive_file" "hello" {
type = "zip"
source_dir = "${path.module}/lambda/hello" # ソース(このディレクトリ配下を全部)
output_path = "${path.module}/build/hello.zip" # ビルド先(gitignore 推奨)
}
例: 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 ロール
# Trust Policy(誰がこのロールを引き受けられるか)を policy_document で組み立て
# Lambda サービス自身がこのロールを使えるようにする宣言
data "aws_iam_policy_document" "lambda_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service" # AWS のサービス
identifiers = ["lambda.amazonaws.com"] # Lambda サービス
}
}
}
# Lambda 実行ロール本体(中身の権限ポリシーは attachment で別途貼る)
resource "aws_iam_role" "lambda" {
name = "lambda-hello"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}
# CloudWatch Logs に書く最小権限
# AWS マネージドポリシー(logs:CreateLogGroup/Stream/PutLogEvents)を割り当て
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
# Lambda 関数本体
resource "aws_lambda_function" "hello" {
function_name = "hello" # AWS 上での関数名
role = aws_iam_role.lambda.arn # 実行ロール(必須)
# zip ファイルのパス + ハッシュ。ハッシュが変わると Terraform が再デプロイを検知
filename = data.archive_file.hello.output_path
source_code_hash = data.archive_file.hello.output_base64sha256
runtime = "nodejs20.x" # 実行ランタイム(python3.12, java21 等)
handler = "index.handler" # ファイル名.関数名(index.js の handler を呼ぶ)
memory_size = 256 # MB(128〜10240)。CPU 性能はメモリに比例
timeout = 10 # 秒(最大 900 = 15 分)
architectures = ["arm64"] # Graviton(同性能で 20% 安い)
# 環境変数:コード側で process.env.LOG_LEVEL のように参照
environment {
variables = {
LOG_LEVEL = "info"
}
}
}
Lambda の主要属性
| 属性 | デフォルト | 用途 |
|---|---|---|
memory_size | 128 | 大きいほど CPU 性能も上がる |
timeout | 3 秒 | 最大 15 分 |
architectures | x86_64 | arm64 推奨(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% 安く、構成もシンプル。
# HTTP API(v2)の入れ物。protocol_type で v2 種別が決まる(HTTP / WEBSOCKET)
resource "aws_apigatewayv2_api" "main" {
name = "hello-api"
protocol_type = "HTTP"
# CORS:ブラウザから別オリジンの API を叩く時に必須
cors_configuration {
allow_origins = ["*"] # 本番は具体的ドメインに絞る
allow_methods = ["GET", "POST"]
allow_headers = ["Content-Type", "Authorization"]
max_age = 86400 # プリフライト結果のキャッシュ(秒)
}
}
integration / route / stage
# Integration: 「API → Lambda」の接続定義
# AWS_PROXY は「リクエストをそのまま 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 # 呼び出し用 ARN
payload_format_version = "2.0" # v2 推奨(event のフォーマットが新)
timeout_milliseconds = 10000 # APIGW 側の最大 30000
}
# Route: 「GET /hello → 上の integration」
# route_key の形式: "メソッド パス"。"$default" で全パスをキャッチも可能
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 ステージを使うと URL に /stage/ が入らずシンプルになる
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.main.id
name = "$default"
auto_deploy = true # route 変更で自動デプロイ
# アクセスログ出力先と JSON フォーマット
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api.arn
# $context.* は APIGW が提供する変数。jsonencode で構造化ログに
format = jsonencode({
requestId = "$context.requestId"
sourceIp = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
})
}
}
# アクセスログ用の Log Group
resource "aws_cloudwatch_log_group" "api" {
name = "/aws/apigw/hello-api"
retention_in_days = 14 # コスト管理のため必ず設定(未設定だと永久保持)
}
aws_lambda_permission
API Gateway が Lambda を呼ぶ権限を 明示 する必要があります。これを忘れると 500 エラーで詰まります。
# Lambda 側のリソースベースポリシー
# 「APIGW がこの関数を呼んで OK」と Lambda 自身に許可させる(IAM ロールとは別レイヤー)
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke" # ポリシー内のステートメント識別子
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.hello.function_name
principal = "apigateway.amazonaws.com" # APIGW サービス
# この API & 任意のステージ/メソッド/リソース から
# /*/* = どのステージ/どのメソッド・パスからでも OK
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}
完成形と動作確認
# API のエンドポイント URL を出力
# apply 後に terraform output -raw api_url で取得できる
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"}