학습 목표

시나리오

내부 도구용 조회 API를 만듭니다. 트래픽은 평소 거의 없다가 특정 시간에만 몰리고, 서버를 상시 띄워 두고 싶지 않습니다. 이런 형태가 서버리스가 가장 잘 맞는 조건입니다.

아키텍처 개요

구성 요소와 권한의 방향
구성 요소 역할 권한
Lambda 함수 요청을 처리하고 응답을 만든다 실행 역할 — 함수가 로그를 쓰고 다른 서비스를 호출할 권한
API Gateway REST API HTTP 요청을 받아 함수를 호출한다 리소스 기반 권한 — 함수 쪽에 "이 API가 나를 호출해도 된다"를 기록
프록시 통합(AWS_PROXY) 요청 전체를 함수에 그대로 전달 —
스테이지 배포 단위이자 호출 URL의 일부 —

구성 단계

1 · 실행 역할

lambda-trust.json — Lambda 서비스만 이 역할을 맡을 수 있게
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "lambda.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
create-role.sh — 역할 생성과 로그 권한 부여 (계정 ID는 예시입니다)
aws iam create-role \
  --role-name Lab06ItemsFnRole \
  --assume-role-policy-document file://lambda-trust.json

# 로그를 쓸 권한은 AWS 관리형 정책으로 최소한만 붙입니다.
aws iam attach-role-policy \
  --role-name Lab06ItemsFnRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

2 · 함수 만들기

index.mjs — 프록시 통합에 맞춘 응답 형태
export const handler = async (event) => {
  const items = [
    { id: 1, name: "alpha" },
    { id: 2, name: "beta" }
  ];

  return {
    statusCode: 200,
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ items, path: event.rawPath ?? event.path })
  };
};
create-function.sh — 압축 후 함수 생성 (런타임과 계정 ID는 예시입니다)
zip -q function.zip index.mjs

aws lambda create-function \
  --function-name lab06-items \
  --runtime nodejs22.x \
  --role arn:aws:iam::123456789012:role/Lab06ItemsFnRole \
  --handler index.handler \
  --zip-file fileb://function.zip \
  --timeout 10 \
  --memory-size 256

aws lambda wait function-active-v2 --function-name lab06-items

3 · REST API 만들기

create-api.sh — 리소스·메서드·프록시 통합 (리전과 계정 ID는 예시입니다)
REGION=ap-northeast-2
ACCOUNT=123456789012
FN_ARN="arn:aws:lambda:$REGION:$ACCOUNT:function:lab06-items"

API_ID=$(aws apigateway create-rest-api \
  --name lab06-items-api \
  --query 'id' --output text)

ROOT_ID=$(aws apigateway get-resources \
  --rest-api-id "$API_ID" \
  --query 'items[?path==`/`].id' --output text)

RES_ID=$(aws apigateway create-resource \
  --rest-api-id "$API_ID" \
  --parent-id "$ROOT_ID" \
  --path-part items \
  --query 'id' --output text)

aws apigateway put-method \
  --rest-api-id "$API_ID" \
  --resource-id "$RES_ID" \
  --http-method GET \
  --authorization-type NONE

# 프록시 통합은 항상 POST 로 Lambda 를 호출합니다. 클라이언트의 메서드와는 별개입니다.
aws apigateway put-integration \
  --rest-api-id "$API_ID" \
  --resource-id "$RES_ID" \
  --http-method GET \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri "arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/$FN_ARN/invocations"

4 · 호출 권한 부여

여기까지 만들어도 호출하면 실패합니다. 함수 쪽에 "이 API가 호출해도 된다"는 권한을 기록해야 합니다.

add-permission.sh — 리소스 기반 권한 추가
aws lambda add-permission \
  --function-name lab06-items \
  --statement-id apigw-invoke-items \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API_ID/*/GET/items"

# 실제로 정책이 붙었는지 확인합니다.
aws lambda get-policy --function-name lab06-items --query 'Policy' --output text

--source-arn을 생략하면 모든 API Gateway가 이 함수를 호출할 수 있게 됩니다. 특정 API와 경로로 좁히는 것이 최소 권한 원칙에 맞습니다.

5 · 스테이지 배포

deploy-api.sh — 스테이지 배포와 호출 URL 확인
aws apigateway create-deployment \
  --rest-api-id "$API_ID" \
  --stage-name dev

echo "https://$API_ID.execute-api.$REGION.amazonaws.com/dev/items"

검증 방법

verify.sh — 함수 단독 호출과 API 경유 호출 비교
# 1) 함수만 직접 호출해 로직을 확인합니다.
aws lambda invoke \
  --function-name lab06-items \
  --payload '{"path":"/items"}' \
  --cli-binary-format raw-in-base64-out \
  response.json
cat response.json

# 2) API Gateway 의 테스트 호출로 통합 구간을 확인합니다.
aws apigateway test-invoke-method \
  --rest-api-id "$API_ID" \
  --resource-id "$RES_ID" \
  --http-method GET \
  --query '[status, body]' --output json

# 3) 함수 설정과 동시성 상태
aws lambda get-function-configuration \
  --function-name lab06-items \
  --query '[Runtime, Timeout, MemorySize, State, LastUpdateStatus]' \
  --output table
실패 지점을 좁히는 순서
증상 확인할 곳
함수 직접 호출도 실패 코드와 핸들러 이름, 실행 역할의 로그 권한
직접 호출은 되고 API가 실패 리소스 기반 권한, 통합 URI, 통합 HTTP 메서드
테스트 호출은 되고 실제 URL이 실패 스테이지 배포 여부
응답이 이상한 형태로 나옴 프록시 통합이 요구하는 statusCode·body 구조

비용 고려

요금이 발생하는 항목
항목 성격
Lambda 호출 호출 횟수와 실행 시간(메모리 크기가 곱해진다)
API Gateway 요청 요청 수와 전송량
CloudWatch Logs 수집량과 보존 기간. 보존 기간을 정하지 않으면 무기한 쌓인다
프로비저닝된 동시성 사용하지 않아도 준비된 만큼 발생한다
유휴 상태 호출이 없으면 실행 요금이 없다

메모리를 올리면 단위 시간당 요금은 오르지만 실행 시간이 짧아져 총액이 줄어드는 경우가 있습니다. CPU 바운드 작업이라면 메모리를 몇 단계 바꿔 가며 실행 시간을 재 보는 것이 가장 확실한 최적화입니다.

프로덕션 고려사항

정리 — 리소스 삭제

cleanup.sh — API, 함수, 역할, 로그 그룹 삭제
aws apigateway delete-rest-api --rest-api-id "$API_ID"

aws lambda delete-function --function-name lab06-items

aws logs delete-log-group --log-group-name /aws/lambda/lab06-items

aws iam detach-role-policy \
  --role-name Lab06ItemsFnRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name Lab06ItemsFnRole

로그 그룹은 함수를 지워도 남습니다. 보존 기간을 설정하지 않았다면 계속 쌓이므로 함께 정리합니다.

자주 하는 실수

자주 나오는 실수와 증상
실수 증상 고치는 법
리소스 기반 권한을 안 붙임 API 호출이 내부 서버 오류로 실패한다 lambda add-permission으로 API를 허용한다
통합 HTTP 메서드를 GET으로 통합 호출이 실패한다 프록시 통합은 항상 POST다
스테이지를 배포하지 않음 변경이 반영되지 않는다 create-deployment를 실행한다
응답 구조가 프록시 형식과 다름 본문이 그대로 나오지 않거나 오류가 난다 statusCode·headers·body를 반환한다
--source-arn 생략 다른 API도 이 함수를 호출할 수 있다 API ID와 경로까지 지정해 좁힌다
제한 시간을 기본 3초로 둠 느린 의존 호출에서 함수가 잘린다 실제 처리 시간을 재고 조정한다
VPC에 연결해 두고 NAT 없음 외부 API 호출이 갑자기 멈춘다 NAT 게이트웨이나 VPC 엔드포인트를 추가한다(실습 10)

이어서 볼 곳

공식 문서 출처