AWS上級 #4 API Gateway + Lambda

読了 9分

#3 Lambda 基礎 の関数を HTTP で呼ぶには入口が必要です。AWS の標準は API Gateway — Lambda (または ECS、EC2、外部バックエンド) の前に置くマネージドゲートウェイ。

今回は API Gateway の 2 つの形 (REST / HTTP)、Lambda 統合パターン、権限オプション、ステージ / デプロイ、使用量プランまで一気に整理します。

API Gateway がすること #

API Gateway がやること
クライアント (ブラウザ、アプリ)
   │ HTTPS
API Gateway
   ├─ TLS 終端
   ├─ ルーティング (path → バックエンド)
   ├─ 認証 / 認可
   ├─ Rate limiting / Throttling
   ├─ リクエスト変換
   └─ キャッシング
バックエンド (Lambda / ECS / 外部 HTTP)

ALB (中級 #6) と機能が似ていますが強調点が違います。

ALBAPI Gateway
価格モデル時間 + LCUリクエスト + データ
0 トラフィック費用時間当たり (〜$20/月)0
Lambda 直接統合可能自然
API key / Usage Planなしあり
キャッシングなしあり (REST API)
Cognito 統合別途自然
適した場合常に動く大きな ECS変動の大きい / サーバーレス / API key 必要

REST API vs HTTP API #

API Gateway は 2 つの形で生きています。

HTTP API (v2) — 新型 #

  • 70% 安い、より速い
  • JWT トークン (Cognito / Auth0 / 自前 OIDC) の直接検証
  • CORS 設定がシンプル
  • Lambda proxy 統合がデフォルト
  • WebSocket 非対応API key / Usage Plan 非対応リクエスト変換 / キャッシングなし

新規プロジェクトのデフォルト候補。シンプルな REST API + JWT なら十分。

REST API (v1) — 旧型 + 豊富な機能 #

  • キャッシング (ステージ単位)
  • リクエスト / レスポンスマッピング変換
  • WAF 直接統合
  • API Key + Usage Plan
  • WebSocket 対応

複雑な変換 / 使用量分離 / WebSocket が必要なとき。

どう選ぶか #

決定木
WebSocket 必要? ─Yes→ REST API (または WebSocket API)
   │ No
API Key / Usage Plan 必要? ─Yes→ REST API
   │ No
複雑な変換 / キャッシング必要? ─Yes→ REST API
   │ No
HTTP API (安い + 速い)

この記事は HTTP API ベースで進め、REST API 専用機能は別途表示します。

最初の API — Hello, World #

Lambda 関数の準備 #

lambda_function.py
import json

def handler(event, context):
    name = event.get("queryStringParameters", {}).get("name", "world")
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": f"Hello, {name}!"})
    }

すでに #3 で扱った関数です。この関数を hello-fn としてデプロイしておく。

HTTP API を作る (コンソール) #

コンソール → API Gateway → “API 作成” → HTTP API の “構築”。

  1. 統合追加: Lambda → リージョン / 関数 (hello-fn)
  2. API 名: hello-api
  3. ルート設定: GET /hellohello-fn
  4. ステージ: $default (自動デプロイ)
  5. 作成

完了すると URL: https://abc123.execute-api.ap-northeast-2.amazonaws.com/hello?name=AWS

CLI で作る #

HTTP API CLI
# API 作成
API_ID=$(aws apigatewayv2 create-api \
  --name hello-api \
  --protocol-type HTTP \
  --target arn:aws:lambda:ap-northeast-2:123456789012:function:hello-fn \
  --query ApiId --output text)

# Lambda が API Gateway から呼ばれる権限
aws lambda add-permission \
  --function-name hello-fn \
  --statement-id apigw-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:ap-northeast-2:123456789012:$API_ID/*/*"

echo "https://$API_ID.execute-api.ap-northeast-2.amazonaws.com/"

--target オプションがルート + 統合を自動でやってくれる短縮形。精密制御が必要なら create-routecreate-integration を別々に。

Lambda 統合 — Proxy vs Non-proxy #

API Gateway → Lambda 呼び出しの 2 モード。

Proxy 統合 (HTTP API のデフォルト) #

リクエストをほぼそのまま Lambda に渡す。Lambda が statusCode / headers / body を標準レスポンス形式で返す。

proxy 統合の入出力
# Event
{
  "version": "2.0",
  "routeKey": "POST /users",
  "rawPath": "/users",
  "queryStringParameters": {"limit": "10"},
  "headers": {...},
  "body": "{\"name\":\"Alice\"}",
  "isBase64Encoded": false
}

# Lambda レスポンス
{
  "statusCode": 201,
  "headers": {"Content-Type": "application/json"},
  "body": "{\"id\":42}"
}

運用の標準。コードが自身でルーティング / レスポンス形成を担当すれば変更時に API Gateway を触りません。

Non-proxy (REST API のみ) #

API Gateway がリクエストを変換して Lambda が望む形で送り、レスポンスも変換。マッピングテンプレート (Velocity Template Language) で定義。

VTL マッピング
{
  "name": "$input.path('$.name')",
  "user_id": "$context.authorizer.claims.sub"
}

既存 Lambda が raw event を受けず綺麗な入力だけ受けるように。学習曲線がありモニタリングが面倒なので 新規プロジェクトはほぼ常に Proxy

ルートとメソッド #

ルート = path + method の組み合わせ。

複数ルートの追加
# Catch-all
aws apigatewayv2 create-route \
  --api-id $API_ID --route-key 'ANY /'

# 特定メソッド
aws apigatewayv2 create-route \
  --api-id $API_ID --route-key 'GET /users'
aws apigatewayv2 create-route \
  --api-id $API_ID --route-key 'POST /users'
aws apigatewayv2 create-route \
  --api-id $API_ID --route-key 'GET /users/{userId}'

{userId} のようなパラメータは event の pathParameters に入ります。

1 つの Lambda が複数ルート #

小さい API なら 1 つの Lambda がすべてのルートを処理。Lambda の中で FastAPI / Flask のようなフレームワーク + adapter (例: Mangum) でルーティング。

FastAPI on Lambda
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/users")
def list_users():
    return [{"id": 1, "name": "Alice"}]

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"id": user_id, "name": "Alice"}

handler = Mangum(app)  # Lambda handler

API Gateway のルートは ANY /{proxy+} 1 行で完了。

ルート別に異なる Lambda #

大きな API はルート別に分離。権限 / リソース / デプロイ単位が明確に。

ルート分散
GET  /users         → list-users-fn
GET  /users/{id}    → get-user-fn
POST /users         → create-user-fn

分離するとコールドスタートも分散 — 関数毎にウォーミング / 並行性を別々に管理。

権限 — 誰が呼べるか #

API Gateway で最もよく出会う設定の 1 つです。4 つのオプション。

1) Open (認証なし) #

テスト / 公開 API。運用ではほぼ使いません。

2) IAM 認証 #

リクエストに SigV4 署名 (AWS SDK が自動) が必要。AWS アカウント内のサービス間呼び出しに適切。

IAM 認証された呼び出し (Python)
import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import requests

session = boto3.Session()
creds = session.get_credentials()
url = "https://abc.execute-api.ap-northeast-2.amazonaws.com/hello"
req = AWSRequest(method="GET", url=url)
SigV4Auth(creds, "execute-api", "ap-northeast-2").add_auth(req)

resp = requests.get(url, headers=dict(req.headers))

ブラウザから直接呼び出しは難しい (署名が露出)。バックエンド ↔ バックエンド。

3) JWT Authorizer (HTTP API) #

Cognito User Pool / Auth0 / 自前 OIDC の ID トークンを API Gateway が直接検証。HTTP API の強み

JWT Authorizer を作る
aws apigatewayv2 create-authorizer \
  --api-id $API_ID \
  --authorizer-type JWT \
  --identity-source '$request.header.Authorization' \
  --jwt-configuration '{
    "Audience": ["my-app"],
    "Issuer": "https://cognito-idp.ap-northeast-2.amazonaws.com/<UserPoolId>"
  }' \
  --name jwt-auth

ルートに付ければ Authorization: Bearer eyJ... ヘッダが自動で検証され、claims が Lambda event の requestContext.authorizer.jwt.claims に入ります。

4) Lambda Authorizer (Custom) #

検証ロジック自体を自分が書いた Lambda が担当。最大の柔軟性。

lambda authorizer
def handler(event, context):
    token = event["headers"]["authorization"].replace("Bearer ", "")

    # 自前検証ロジック (DB 照会 / 外部呼び出し等)
    user = verify_token_somehow(token)
    if not user:
        return {"isAuthorized": False}

    return {
        "isAuthorized": True,
        "context": {"user_id": user.id, "role": user.role}
    }

キャッシング可能 (TTL の間同じトークンは検証スキップ)。外部認証システム連携に自然。

5) Cognito User Pool Authorizer (REST API 専用) #

REST API で Cognito を直接統合。HTTP API の JWT Authorizer に似た機能だが REST API で。

CORS #

ブラウザから呼び出すには CORS 設定が必須。HTTP API は コンソール 1 度 で完了。

HTTP API CORS
aws apigatewayv2 update-api \
  --api-id $API_ID \
  --cors-configuration "AllowOrigins=https://myapp.com,AllowMethods=GET,POST,AllowHeaders=*"

REST API は各ルートに OPTIONS レスポンスを直接作らなければならない — コンソールに “CORS 有効化” ボタンが自動生成。

ステージとデプロイ #

HTTP API #

自動デプロイ オプションがデフォルト。ルート変更が即時反映。ステージは $default 1 つ。

別の環境 (dev / staging / prod) 分離は API 自体を別々に作る 方向。

REST API #

明示的なデプロイ + ステージ。devstagingprod のようなステージで同時に異なるバージョンを運用可能。

aws apigateway create-deployment --rest-api-id $REST_ID --stage-name prod

ステージ毎にキャッシング、ロギング、throttling、変数を別々に。

Throttling — トラフィック制限 #

デフォルト上限:

  • アカウント単位: 10,000 req/s、burst 5,000
  • ルート単位: 別途設定可能
ルート当たり 100 req/s に制限
aws apigatewayv2 update-stage \
  --api-id $API_ID --stage-name '$default' \
  --route-settings '{
    "GET /heavy": {
      "ThrottlingRateLimit": 100,
      "ThrottlingBurstLimit": 50
    }
  }'

悪意のある / 重いルートだけ強く — 他のルートには影響なし。

API Key + Usage Plan (REST API) #

外部ユーザー / パートナーに API を公開する用途。

構造
API Key (ユーザー毎の固有文字列)
Usage Plan (上限、e.g. "月 10 万 req、秒 50 req")
Stage (実際の API)

ユーザー登録時に API Key 発行 → Usage Plan に接続 → そのユーザーのリクエストに自動で上限適用。

API Key + Plan
KEY_ID=$(aws apigateway create-api-key \
  --name customer-A --enabled --query id --output text)

PLAN_ID=$(aws apigateway create-usage-plan \
  --name basic-tier \
  --throttle 'rateLimit=50,burstLimit=100' \
  --quota 'limit=100000,period=MONTH' \
  --query id --output text)

aws apigateway create-usage-plan-key \
  --usage-plan-id $PLAN_ID --key-id $KEY_ID --key-type API_KEY

aws apigateway update-usage-plan \
  --usage-plan-id $PLAN_ID \
  --patch-operations 'op=add,path=/apiStages,value=<rest-api-id>:prod'

呼び出し側はヘッダ x-api-key: <キー> を一緒に送ります。

HTTP API は API Key / Usage Plan 非対応。必要なら REST API。

キャッシング (REST API) #

ステージ単位でレスポンスキャッシング。GET リクエストに自然。

aws apigateway update-stage \
  --rest-api-id $REST_ID --stage-name prod \
  --patch-operations 'op=replace,path=/cacheClusterEnabled,value=true' \
  --patch-operations 'op=replace,path=/cacheClusterSize,value=0.5'
  • キャッシュサイズ: 0.5 GB 〜 237 GB
  • TTL: ルート別に設定
  • 無効化: ヘッダ Cache-Control: max-age=0 (必要権限のある呼び出し元のみ)

費用: 時間当たり — トラフィックが本当に多い場合でのみ価値。

ロギング — Access Log + Execution Log #

Access Log #

リクエスト単位 — 運用 / ビジネス分析に。

Access Log 有効化
aws apigatewayv2 update-stage \
  --api-id $API_ID --stage-name '$default' \
  --access-log-settings '{
    "DestinationArn": "arn:aws:logs:ap-northeast-2:123456789012:log-group:/aws/apigateway/hello-api",
    "Format": "{ \"requestId\":\"$context.requestId\",\"ip\":\"$context.identity.sourceIp\",\"requestTime\":\"$context.requestTime\",\"routeKey\":\"$context.routeKey\",\"status\":\"$context.status\" }"
  }'

JSON で受けて CloudWatch Logs Insights でクエリ。

Execution Log (REST API) #

内部段階別ログ — デバッグ用。運用では費用 / ノイズで滅多に on にしません。

カスタムドメイン — Custom Domain #

https://api.myapp.com/... で公開。ACM 証明書 (中級 #6) + Route 53 (中級 #5) と連携。

Custom Domain
aws apigatewayv2 create-domain-name \
  --domain-name api.myapp.com \
  --domain-name-configurations \
    "CertificateArn=arn:aws:acm:ap-northeast-2:...,EndpointType=REGIONAL"

# API → Domain マッピング
aws apigatewayv2 create-api-mapping \
  --api-id $API_ID \
  --domain-name api.myapp.com \
  --stage '$default'

# Route 53 — A レコード alias
aws route53 change-resource-record-sets ...

費用 #

HTTP API #

  • リクエスト当たり $1.00 / 100 万 (〜$0.000001)
  • Data Transfer 別途

REST API #

  • リクエスト当たり $3.50 / 100 万 (3.5 倍)
  • キャッシング有効化時に時間当たり追加

月 100 万リクエスト:

  • HTTP API: $1
  • REST API: $3.50 (+ キャッシュ / Usage Plan 使用時に追加)

Lambda ほどではないが非常に安い。

よく出会う落とし穴 #

1) Lambda が呼び出されない / 権限拒否 #

lambda:InvokeFunction 権限が API Gateway に付与されていません。コンソール統合は自動、CLI は aws lambda add-permission を直接。

2) CORS が解けない #

ブラウザコンソールに CORS エラー。2 箇所点検:

  • API Gateway CORS 設定 (上を参照)
  • Lambda レスポンスに Access-Control-Allow-Origin ヘッダ (proxy 統合は Lambda が責任)

3) タイムアウト #

API Gateway の統合タイムアウトは 最大 30 秒 (REST)、30 秒 (HTTP)。Lambda は 15 分だが API Gateway が切る。30 秒超の処理は非同期 (Lambda Destination、Step Functions、SQS) で回避。

4) Body サイズ上限 #

API Gateway の payload 上限 6 MB (両側)。大きなファイルは S3 presigned URL パターン。

5) 意図しない自動デプロイ (HTTP API) #

ルート / 統合変更が即時運用反映。CI で IaC (Terraform / CDK) で管理すれば意図された変更だが、コンソール直接修正は危険。コンソールは dev / staging API のみ

6) JWT Authorizer の audience / issuer タイポ #

aud / iss が 1 文字でも違えば 401。Cognito User Pool ID の正確な issuer URL (https://cognito-idp.<リージョン>.amazonaws.com/<UserPoolId>) を確認。

7) API Key がすべてのセキュリティの終わりではない #

API Key は クライアントに平文で生きている — 決済情報のような用途で単独認証 X。使用量分離 + Throttling の用途。本当の認証は JWT / IAM。

まとめ #

今回つかんだもの:

  • API Gateway の役割 — Lambda / ECS / 外部バックエンドの前のマネージドゲートウェイ。0 トラフィック費用 0
  • HTTP API vs REST API — HTTP は 70% 安い + 速い。WebSocket / API Key / キャッシングが必要なら REST
  • Lambda 統合 — Proxy (標準) vs Non-proxy (変換)。新規プロジェクトは Proxy
  • ルート / メソッドGET /users/{id} のようなパターン。1 つの Lambda 全ルーティング (Mangum 等) vs ルート別分散
  • 権限 4 オプション — Open / IAM / JWT / Lambda Authorizer / (REST の) Cognito User Pool
  • JWT Authorizer (HTTP API) — Cognito / Auth0 / OIDC ID トークンを直接検証
  • CORS — HTTP API は 1 行
  • ステージ — HTTP API は自動デプロイ / REST は明示的デプロイ + 環境分離
  • Throttling — ルート別上限
  • API Key + Usage Plan (REST のみ) — 外部ユーザー別上限
  • キャッシング (REST のみ) — ステージ単位
  • Access Log — JSON で CloudWatch Logs に
  • Custom Domain — ACM + Route 53
  • 費用 — HTTP API リクエスト当たり $0.000001、非常に安い
  • 落とし穴 — Lambda 権限、CORS、30 秒タイムアウト、6MB Payload、HTTP API 自動デプロイ、JWT issuer タイポ、API Key の役割

次回 — メッセージインフラ #

API Gateway / Lambda の同期呼び出しはここまでです。次は 非同期 / イベント駆動の通信 を整理する番です。

#5 EventBridge / SQS / SNS では 3 つの違いの比較、fan-out パターン、DLQ、idempotency まで — AWS のメッセージインフラを一気に整理します。

X