목차
18 장

API Gateway + Lambda

Lambda를 HTTP로 노출하는 표준 패턴을 정리합니다. REST API와 HTTP API의 차이, Lambda 통합(proxy / non-proxy), 라우트 / 메소드, 권한(IAM / Cognito / Lambda authorizer), CORS, 스테이지 / 배포, Throttling, 사용량 플랜, 캐싱, Custom Domain, 비용까지 다룹니다.

17장 Lambda 기초의 함수를 HTTP로 부르려면 입구가 필요합니다. AWS의 표준은 API Gateway입니다. Lambda (또는 ECS, EC2, 외부 백엔드) 앞에 두는 매니지드 게이트웨이입니다.

본 챕터는 API Gateway의 두 형태(REST / HTTP), Lambda 통합 패턴, 권한 옵션, 스테이지 / 배포, 사용량 플랜까지 한 번에 정리합니다. 직전 17장에서 잡은 동기 호출 모델이 여기서 실제 HTTP 엔드포인트가 되고, 다음 19장 EventBridge / SQS / SNS에서는 그 반대편인 비동기 통신으로 넘어갑니다.

API Gateway가 하는 일 #

API Gateway가 하는 일
클라이언트 (브라우저, 앱)
   │ HTTPS
API Gateway
   ├─ TLS 종단
   ├─ 라우팅 (path → 백엔드)
   ├─ 인증 / 권한
   ├─ Rate limiting / Throttling
   ├─ 요청 변환
   └─ 캐싱
백엔드 (Lambda / ECS / 외부 HTTP)

ALB (13장 ALB / NLB와 ACM)와 기능이 비슷하지만 강조점이 다릅니다.

ALBAPI Gateway
가격 모델시간 + LCU요청 + 데이터
0 트래픽 비용시간당 (~$20/월)0
Lambda 직접 통합가능자연스러움
API key / Usage Plan없음있음
캐싱없음있음 (REST API)
Cognito 통합별도자연스러움
적합항상 도는 큰 ECS변동 큰 / 서버리스 / API key 필요

REST API vs HTTP API #

API Gateway는 두 가지 형태로 제공됩니다.

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}!"})
    }

이미 17장 Lambda 기초에서 다룬 함수입니다. 이 함수를 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-route, create-integration을 따로 씁니다.

Lambda 통합 — Proxy vs Non-proxy #

API Gateway → Lambda 호출의 두 모드입니다.

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에 들어옵니다.

한 Lambda가 여러 라우트 #

작은 API 면 한 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+} 한 줄로 끝납니다.

라우트별 다른 Lambda #

큰 API는 라우트별로 분리합니다. 권한 / 자원 / 배포 단위가 명확해집니다.

라우트 분산
GET  /users         → list-users-fn
GET  /users/{id}    → get-user-fn
POST /users         → create-user-fn

분리하면 콜드 스타트도 분산됩니다 — 한 함수당 워밍 / 동시성을 따로 관리합니다.

권한 — 누가 호출할 수 있나 #

API Gateway에서 가장 자주 만나는 설정입니다. 네 옵션이 있습니다.

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는 콘솔 한 번으로 끝납니다.

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 한 개입니다.

다른 환경 (dev / staging / prod) 분리는 API 자체를 따로 만드는 방향입니다.

REST API #

명시적 배포와 스테이지를 씁니다. dev, staging, prod 같은 스테이지에 동시에 다른 버전을 운영할 수 있습니다.

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 (한도, 예: "월 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.5GB~237GB
  • 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) #

내부 단계별 로그입니다 — 디버깅용입니다. 운영에서는 비용 / 노이즈로 잘 안 켭니다.

사용자 도메인 — Custom Domain #

https://api.myapp.com/...으로 노출합니다. ACM 인증서 (13장 ALB / NLB와 ACM)와 12장 Route 53과 연결합니다.

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 오류가 납니다. 두 가지를 점검합니다.

  • 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 한도는 6MB (양쪽)입니다. 큰 파일은 S3 presigned URL 패턴을 씁니다.

5) 의도치 않은 자동 배포 (HTTP API) #

라우트 / 통합 변경이 즉시 운영에 반영됩니다. CI에서 IaC (Terraform / CDK)로 관리하면 의도된 변경이지만, 콘솔 직접 수정은 위험합니다. 콘솔은 dev / staging API만 씁니다.

6) JWT Authorizer의 audience / issuer 오타 #

aud / iss가 한 글자라도 다르면 401입니다. Cognito User Pool ID의 정확한 issuer URL (https://cognito-idp.<리전>.amazonaws.com/<UserPoolId>)을 확인합니다.

7) API Key가 모든 보안의 끝이 아님 #

API Key는 클라이언트에 평문으로 남아 있습니다 — 결제 정보 같은 용도에는 단독 인증으로 쓰지 않습니다. 사용량 분리와 Throttling 용도입니다. 진짜 인증은 JWT / IAM입니다.

연습문제 #

  1. 본인이 만들 API에 대해 §“REST API vs HTTP API"의 결정 트리를 따라가 HTTP API와 REST API 중 어느 것을 쓸지 결정하고, 그 결정을 가른 한 가지 기능(WebSocket / API Key / 캐싱 / 비용)을 한 줄로 적어 두세요.
  2. 작은 API를 한 Lambda에서 FastAPI + Mangum으로 처리하는 방식과 라우트별로 Lambda를 분리하는 방식의 장단점을, §“라우트와 메소드"와 17장 Lambda 기초의 콜드 스타트를 근거로 한 단락으로 비교해 보세요.
  3. §“권한"의 네 옵션 중 브라우저 SPA에서 직접 호출하는 API에 적합한 것과 백엔드끼리 호출하는 API에 적합한 것을 각각 골라 이유를 적어 보세요. JWT Authorizer를 쓴다면 §“자주 만나는 함정"에서 가장 흔한 401 원인이 무엇인지도 함께 적어 두세요.

한 줄 요약: API Gateway는 Lambda / ECS / 외부 백엔드 앞에 두는 매니지드 게이트웨이로, 0 트래픽 비용이 0이다. HTTP API는 REST API보다 70% 저렴하고 빠르며 새 프로젝트의 기본이고, WebSocket·API Key·캐싱이 필요하면 REST API를 쓴다. Lambda 통합은 Proxy가 표준이며 권한은 Open / IAM / JWT / Lambda Authorizer로 나뉘고, HTTP API의 JWT Authorizer가 Cognito·Auth0·OIDC 토큰을 직접 검증한다. 가장 흔한 함정은 Lambda invoke 권한 누락, CORS, 30초 타임아웃, 6MB payload 한도, JWT issuer 오타다.

다음 챕터 #

API Gateway와 Lambda의 동기 호출은 끝났습니다. 다음 19장 EventBridge / SQS / SNS에서는 비동기 / 이벤트 주도 통신을 다룹니다. 세 도구의 역할 비교, fan-out 패턴, DLQ, 멱등성(idempotency)까지 AWS의 메시지 인프라를 정리합니다.

X