Let's Talk

Feel free to reach out. I'll get back to you as soon as possible.

Study13 min

AWS Project2 - Serverless Data Pipeline

AWS+Terraform로 구축한 서버리스 데이터 파이프라인에 대해 설명합니다.

AWS Project2 - Serverless Data Pipeline

Project 2: Serverless Data Pipeline with Terraform (S3 + Lambda + SQS + DynamoDB)

Terraform으로 구축한 서버리스 데이터 파이프라인 인프라입니다. S3 Ingestion 버킷에 파일을 업로드하면 Lambda Router가 파일 형식을 자동으로 판별하여 정형 데이터(CSV/JSON)는 DynamoDB에 적재하고, 비정형 데이터(PDF/이미지)는 텍스트를 추출해 처리 결과를 S3 Processed 버킷에 저장합니다. 지원하지 않는 파일 형식은 Quarantine 버킷으로 격리되며, CloudWatch + SNS로 실시간 모니터링 및 이메일 알림을 제공합니다.

파이프라인 전체가 Lambda와 SQS로 비동기 연결되어 있어, 대용량 파일이나 동시 다발적 업로드에도 확장 가능한 구조입니다. API Gateway를 통해 외부에서 HTTP로 파이프라인을 직접 트리거할 수도 있습니다.


아키텍처

[파일 업로드 경로]
S3 Ingestion 버킷                   API Gateway (HTTP POST)
       │                                      │
       └──────────────┬───────────────────────┘

             [Lambda Router]
             파일 확장자 감지 및 분기

          ┌───────────┼──────────────┐
          ▼           ▼              ▼
    .csv / .json   .pdf / .jpg    그 외 확장자
          │         .png / .tiff       │
          ▼              │             ▼
   [SQS 정형 큐]         │      [Quarantine 버킷]
          │              ▼           (격리)
          ▼       [SQS 비정형 큐]
  [Lambda Parser]        │
  DynamoDB 적재          ▼
          │       [Lambda Extractor]
          ▼       PDF 텍스트 추출 (pypdf)
     [DynamoDB]   이미지 레이블 감지 (Rekognition)


                 [S3 Processed 버킷]
                 results/ 경로에 JSON 저장


[모니터링 / 알림 흐름]
CloudWatch (Lambda 호출 수, 에러율, SQS 메시지 수)


SNS Topic ──── 이메일 알림 (처리 실패 시 즉시 발송)

주요 구성 요소

리소스 설명 비용
S3 Ingestion 외부에서 파일이 최초로 업로드되는 입력 버킷. S3 이벤트 트리거로 Lambda Router 호출 프리 티어: 5GB / 월
S3 Processed Extractor Lambda가 추출한 결과(JSON)를 저장하는 버킷. Lifecycle 규칙으로 90일 후 자동 삭제 프리 티어: 5GB / 월
S3 Quarantine 지원하지 않는 확장자 또는 처리 실패 파일을 격리하는 버킷. 원인 메타데이터 함께 저장 프리 티어: 5GB / 월
Lambda Router S3 이벤트 또는 API Gateway 요청 수신 → 파일 확장자 감지 → 정형/비정형/격리 분기 처리 프리 티어: 월 1,000,000 요청
Lambda Parser SQS 정형 큐 트리거 → CSV/JSON 파싱 → DynamoDB에 레코드 적재 프리 티어: 월 1,000,000 요청
Lambda Extractor SQS 비정형 큐 트리거 → PDF 텍스트 추출(pypdf) / 이미지 레이블 감지(Rekognition) → S3 Processed 저장 프리 티어: 월 1,000,000 요청
SQS 정형 큐 Router → Parser 간 메시지 버퍼. Visibility Timeout 60초, DLQ 포함 프리 티어: 월 1,000,000 요청
SQS 비정형 큐 Router → Extractor 간 메시지 버퍼. Visibility Timeout 300초(PDF 처리 대응), DLQ 포함 프리 티어: 월 1,000,000 요청
DynamoDB 정형 데이터 적재 및 비정형 처리 메타데이터 저장. record_id(PK) 기반 단일 테이블 프리 티어: 25GB / 월
API Gateway 외부 HTTP POST 요청을 받아 Lambda Router에 전달하는 HTTP API(v2). /v1/upload 엔드포인트 프리 티어: 월 1,000,000 호출
CloudWatch Lambda 실행 로그, 에러율, SQS 큐 깊이 모니터링 및 대시보드 제공 프리 티어: 5GB 로그 수집
SNS 처리 오류 발생 시 등록된 이메일로 즉시 알림 발송 프리 티어: 월 1,000건 이메일

사전 준비

IAM 사용자 생성

AWS CLI와 Terraform이 AWS 리소스를 생성할 수 있도록 프로그래밍 방식 액세스 권한을 가진 IAM 사용자가 필요합니다.

1단계: IAM 사용자 생성

AWS 콘솔에서 IAM → 사용자 → 사용자 생성으로 이동합니다. 사용자 이름(예: terraform-admin)을 입력하고, AWS Management Console 액세스는 체크하지 않습니다 (CLI 전용 사용자이므로 콘솔 로그인 권한 불필요).

2단계: AdministratorAccess 권한 부여

권한 설정 단계에서 직접 정책 연결을 선택하고 AdministratorAccess 정책을 검색하여 체크합니다.

⚠️ AdministratorAccess는 실습/테스트용 편의 설정입니다. 실제 운영 환경에서는 최소 권한 원칙에 따라 필요한 서비스만 허용하는 커스텀 정책을 사용하세요.

3단계: 액세스 키(Access Key) 발급 및 저장

사용자 생성 후 해당 사용자 → 보안 자격 증명 → 액세스 키 만들기로 이동합니다. 사용 사례로 **Command Line Interface(CLI)**를 선택합니다. 발급된 액세스 키 ID시크릿 액세스 키는 이 화면에서만 확인 가능하므로, CSV를 다운로드하거나 별도로 메모해 두세요. 화면을 닫으면 시크릿 키는 다시 볼 수 없습니다.


AWS CLI v2 설치

# Linux / macOS의 경우
brew install awscli
aws --version
# Windows의 경우
winget install Amazon.AWSCLI
aws --version

Terraform 설치

# Linux / macOS의 경우

# 1. HashiCorp 저장소 추가
brew tap hashicorp/tap

# 2. Terraform 설치
brew install hashicorp/tap/terraform
terraform --version
# Windows의 경우
winget install Hashicorp.Terraform
terraform --version

설치 후 주의사항

  • Windows winget 약관 동의: winget 명령어를 처음 사용하면 중간에 ‘소스 약관에 동의하십니까?’ 라는 질문(Y/N)이 뜰 수 있습니다. Y를 입력하고 엔터를 누르면 계속 진행됩니다.

  • 버전 확인 에러 발생 시: 설치가 완료된 직후 aws --version이나 terraform --version을 입력했을 때 *‘명령어를 찾을 수 없다’*고 나온다면, 터미널(또는 PowerShell) 창을 완전히 닫고 새로 열어 다시 입력해 보세요. 환경 변수가 새로고침되어 정상 동작합니다.

  • VSCode 사용 시: 터미널 탭만 닫는 것이 아니라 VSCode 창 전체를 완전히 껐다가 다시 켜야 합니다.


배포 가이드

1. AWS CLI 자격증명 등록

aws configure
# AWS Access Key ID:     <발급받은 Access Key ID>
# AWS Secret Access Key: <발급받은 Secret Access Key>
# Default region name:   ap-northeast-2
# Default output format: json

연결 확인:

aws sts get-caller-identity

정상 결과:

{
    "UserId": "AIDAXXXXXXXXXXXXXXXXX",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/terraform-admin"
}

2. 변수 설정 (variables.tf)

project2-serverless-pipeline/variables.tf 파일을 열어 아래 두 변수를 수정합니다.

변수 설명 예시
suffix 버킷 이름 뒤에 붙는 고유 식별자. 전 세계에서 유일해야 하므로 이름+날짜 조합 권장 myname-20260612
alert_email Lambda 처리 실패 시 SNS 알림을 받을 이메일 주소 your@email.com

3. Terraform 배포

프로젝트 폴더로 이동 후 순서대로 실행합니다.

cd project2-serverless-pipeline
terraform init    # Provider 플러그인 다운로드 (최초 1회)
terraform plan    # 생성될 리소스 미리보기
terraform apply   # 실제 배포, 확인 프롬프트에 'yes' 입력

배포가 완료되면 터미널에 Outputs이 출력됩니다. api_endpoint, ingestion_bucket, dynamodb_table 등의 값을 메모해 두면 이후 테스트에 편리합니다.

4. Lambda 함수 생성 확인

배포 후 Lambda 함수 3개가 정상 생성되었는지 확인합니다.

aws lambda list-functions --query "Functions[*].FunctionName" --region ap-northeast-2

p2-router, p2-parser, p2-extractor (또는 설정한 이름) 3개가 목록에 보이면 정상입니다.

AWS 콘솔에서 함수가 보이지 않는 경우: 콘솔 우측 상단의 리전이 아시아 태평양 (서울) ap-northeast-2로 선택되어 있는지 확인하세요. 리전이 다르게 선택되어 있으면 해당 리전의 함수 목록이 표시됩니다.

5. SNS 구독 이메일 확인

variables.tf에 입력한 이메일 주소로 AWS Notification - Subscription Confirmation 메일이 발송됩니다. 메일 내 Confirm subscription 링크를 클릭해야 이후 처리 실패 알림이 정상적으로 수신됩니다. 링크를 클릭하지 않으면 알림이 발송되지 않습니다.


테스트 시나리오

테스트 전 아래 4개 변수를 본인 환경에 맞게 메모해 두세요.

  • [ingestion-bucket]: terraform output ingestion_bucket
  • [quarantine-bucket]: terraform output check_quarantine 값에서 버킷명
  • [processed-bucket]: terraform output processed_bucket
  • [api-endpoint]: terraform output api_endpoint

① CSV 업로드 → DynamoDB 저장 확인

정형 데이터(CSV) 파일이 파이프라인을 거쳐 DynamoDB에 적재되는지 확인합니다.

# 1. CSV 파일 업로드
aws s3 cp sample_data/test.csv s3://[ingestion-bucket]/test.csv

# 2. 약 5~10초 후 DynamoDB 스캔 (데이터 확인)
# 테이블 이름은 terraform output dynamodb_table 값 사용
aws dynamodb scan --table-name p2-pipeline-records --region ap-northeast-2

업로드 후 DynamoDB 테이블에 레코드가 추가된 것을 확인할 수 있습니다.

p2-upload-csv

p2-upload-csv-in-console


② JSON 업로드 → DynamoDB 저장 확인

JSON 형식의 정형 데이터도 동일한 경로로 처리됩니다.

# JSON 파일 업로드
aws s3 cp sample_data/test.json s3://[ingestion-bucket]/test.json

# DynamoDB 확인 (①번과 동일)
aws dynamodb scan --table-name p2-pipeline-records --region ap-northeast-2

p2-upload-json-in-console


③ 미지원 확장자 (.xyz) → Quarantine 격리 확인

지원하지 않는 확장자 파일이 에러 없이 격리 버킷으로 이동되는지 확인합니다.

# 테스트 파일 생성 및 업로드
"test" | Out-File -Encoding utf8 sample_data/test.xyz
aws s3 cp sample_data/test.xyz s3://[ingestion-bucket]/test.xyz

# 격리 버킷 확인 (약 5초 후)
aws s3 ls s3://[quarantine-bucket]/ --recursive

unknown/2026/06/12/test.xyz 형태로 Quarantine 버킷에 파일이 이동된 것을 확인할 수 있습니다.

p2-upload-wrong-extension-in-console


④ PDF 업로드 → Processed 버킷 결과 확인

PDF 파일이 업로드되면 Extractor Lambda가 텍스트를 추출하여 결과 JSON을 Processed 버킷에 저장합니다.

# PDF 파일 업로드
aws s3 cp sample_data/test.pdf s3://[ingestion-bucket]/test.pdf

# 처리 결과 확인 (약 15~30초 후)
aws s3 ls s3://[processed-bucket]/results/ --recursive

results/2026/06/12/<uuid>.json 형태로 파일이 생성된 것을 확인합니다.

p2-upload-pdf-in-log

p2-upload-pdf-in-log2


⑤ API Gateway 엔드포인트 테스트

외부 HTTP 요청으로 파이프라인을 트리거합니다. Windows PowerShell에서는 curl.exe -d 대신 Invoke-RestMethod를 사용해야 JSON 포맷이 깨지지 않습니다.

# 1. 요청 데이터를 PowerShell 오브젝트로 빌드
$bodyObj = @{
    Records = @(
        @{
            s3 = @{
                bucket = @{ name = "[ingestion-bucket]" }
                object = @{ key = "test.csv"; size = 100 }
            }
        }
    )
}

# 2. JSON 문자열로 변환
$jsonBody = $bodyObj | ConvertTo-Json -Depth 5 -Compress

# 3. API Gateway로 POST 요청
Invoke-RestMethod -Uri "[api-endpoint]" `
  -Method Post `
  -Headers @{"Content-Type"="application/json"} `
  -Body $jsonBody

정상 응답:

{
  "message": "routing complete"
}

⚠️ 비용 관리

서비스별 프리 티어 범위

서비스 프리 티어 한도 초과 시 단가
Lambda × 3 월 1,000,000 요청 / 400,000 GB-초 $0.20 / 백만 요청
S3 × 3 (Ingestion, Processed, Quarantine) 5GB 스토리지 / 월 20,000 GET, 2,000 PUT $0.023 / GB
SQS × 2 (정형, 비정형) 월 1,000,000 요청 $0.40 / 백만 요청
DynamoDB 25GB 스토리지 / 월 200만 읽기·쓰기 $0.25 / WCU, $0.10 / RCU
API Gateway (HTTP API) 월 1,000,000 호출 (12개월) $1.00 / 백만 호출
CloudWatch 5GB 로그 수집 / 3개 대시보드 $0.50 / GB

Textract 구독 주의: 향후 이 파이프라인에 AWS Textract를 연동할 경우, Textract는 AWS 계정 레벨에서 별도 구독(활성화)이 필요합니다. 구독하지 않은 상태에서 API를 호출하면 SubscriptionRequiredException: The AWS Access Key Id needs a subscription for the service 오류가 발생하며, 계정 등록(신용카드) 완료 후 최대 1일 이상 소요될 수 있습니다. 본 프로젝트의 PDF 처리는 Textract 대신 pypdf 라이브러리로 대체되었습니다.

인프라 삭제 전 반드시 S3 버킷 비우기

S3 버킷에 오브젝트가 남아있으면 terraform destroy가 실패합니다. 아래 순서로 진행하세요.

# 3개 버킷 모두 비우기
aws s3 rm s3://[ingestion-bucket] --recursive
aws s3 rm s3://[processed-bucket] --recursive
aws s3 rm s3://[quarantine-bucket] --recursive

# 인프라 삭제
terraform destroy

트러블슈팅 노트

1. S3 Lifecycle 규칙에 filter {} 블록 필수

Terraform AWS 프로바이더는 aws_s3_bucket_lifecycle_configurationrule 블록 안에 filter 또는 prefix 중 하나를 반드시 명시해야 합니다. 버킷 전체에 규칙을 적용하고 싶더라도 빈 filter {} 블록을 생략하면 에러가 발생합니다.

# ❌ 잘못된 예 — filter 없음
rule {
  id     = "auto-delete-after-90-days"
  status = "Enabled"
  expiration { days = 90 }
}

# ✅ 올바른 예 — 빈 filter {}로 버킷 전체 적용
rule {
  id     = "auto-delete-after-90-days"
  status = "Enabled"
  filter {
    prefix = ""  # 모든 객체 대상
  }
  expiration { days = 90 }
}

2. API Gateway HTTP API v2 — event["body"] 파싱 주의

API Gateway HTTP API(v2)와 Lambda가 프록시 통합으로 연결되면, 클라이언트가 보낸 JSON 데이터는 event["Records"]에 직접 들어오지 않고 event["body"] 안에 문자열로 감싸져 전달됩니다. S3 직접 트리거와 구조가 다르기 때문에 Router Lambda에 분기 처리가 필요합니다.

# API Gateway를 통한 요청인 경우 body 파싱
if "body" in event:
    if isinstance(event["body"], str):
        event = json.loads(event["body"])
    else:
        event = event["body"]

이 분기를 추가하지 않으면 API Gateway 경로로 들어온 요청에서 KeyError: 'Records'가 발생합니다.

3. Windows PowerShell에서 JSON 전송 시 Invoke-RestMethod 사용

PowerShell에서 curl.exe -d '{"key": "value"}' 형태로 JSON을 전송하면 줄바꿈 문자가 삽입되거나 따옴표 이스케이프가 깨져 잘못된 JSON이 전달됩니다. Windows 환경에서는 반드시 Invoke-RestMethod를 사용하세요.

# ❌ PowerShell에서 문제가 생기는 방식
curl.exe -X POST https://... -d '{"test": "value"}'

# ✅ PowerShell 네이티브 방식 (JSON 포맷 보장)
$body = @{ test = "value" } | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "https://..." -Method Post `
  -Headers @{"Content-Type"="application/json"} -Body $body

4. Lambda 콘솔에서 함수 목록이 비어있는 경우

AWS 콘솔에서 Lambda 함수가 보이지 않는다면, 콘솔 우측 상단의 리전 선택을 확인하세요. Terraform 배포 시 기본 리전(ap-northeast-2, 서울)과 콘솔에서 선택된 리전이 다르면 해당 리전에는 함수가 없으므로 빈 목록이 표시됩니다. CLI로 확인하는 방법:

aws lambda list-functions --query "Functions[*].FunctionName" --region ap-northeast-2

인프라 삭제

# 1. S3 버킷 3개 비우기 (오브젝트가 남아있으면 terraform destroy 실패)
# 버킷 이름 확인 (terraform output으로 확인)
# [ingestion-bucket], [processed-bucket], [quarantine-bucket] 에 실제 값 입력

aws s3 rm s3://[ingestion-bucket] --recursive
aws s3 rm s3://[processed-bucket] --recursive
aws s3 rm s3://[quarantine-bucket] --recursive

# 2. 인프라 전체 삭제
cd project2-serverless-pipeline
terraform destroy

DynamoDB 테이블, Lambda 함수, SQS 큐, API Gateway, CloudWatch 알람, SNS 토픽 등 Terraform으로 생성된 모든 리소스가 삭제됩니다.


기술 스택

Terraform · AWS S3 · AWS Lambda · AWS SQS · AWS DynamoDB · AWS API Gateway · AWS CloudWatch · AWS SNS · AWS IAM · Python · pypdf · Amazon Rekognition