[AWS] AWS 서버리스 솔루션 아키텍처

silver's avatar
Nov 24, 2025
[AWS] AWS 서버리스 솔루션 아키텍처

프로젝트 1: MyTodoList - 모바일 애플리케이션

요구사항

기능: - 사용자 인증 (로그인/회원가입) - Todo 항목 CRUD - 실시간 동기화 - 오프라인 지원 제약: - 서버 관리 없이 - 자동 확장 - 저렴한 비용

아키텍처

[모바일 앱]REST API [Amazon Cognito] ← 인증 ↓ 인증 토큰 [API Gateway][Lambda Functions] ├─ Create Todo ├─ Read Todos ├─ Update Todo └─ Delete Todo ↓ [DynamoDB] ├─ Users Table └─ Todos Table

1단계: Cognito 사용자 풀 생성

# 사용자 풀 생성 aws cognito-idp create-user-pool \ --pool-name MyTodoList \ --policies '{ "PasswordPolicy": { "MinimumLength": 8, "RequireUppercase": true, "RequireLowercase": true, "RequireNumbers": true } }' \ --auto-verified-attributes email # 앱 클라이언트 생성 aws cognito-idp create-user-pool-client \ --user-pool-id us-east-1_xxxxxxxxx \ --client-name TodoApp \ --no-generate-secret

2단계: DynamoDB 테이블 설계

# Todos 테이블 생성 aws dynamodb create-table \ --table-name Todos \ --attribute-definitions \ AttributeName=userId,AttributeType=S \ AttributeName=todoId,AttributeType=S \ --key-schema \ AttributeName=userId,KeyType=HASH \ AttributeName=todoId,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST
테이블 구조:
Todos 테이블: - userId (Partition Key): user123 - todoId (Sort Key): todo_20241119_001 - title: "Buy groceries" - completed: false - createdAt: 1700000000

3단계: Lambda 함수 구현

import json import boto3 import uuid from datetime import datetime dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('Todos') def lambda_handler(event, context): # Cognito에서 userId 가져오기 user_id = event['requestContext']['authorizer']['claims']['sub'] http_method = event['httpMethod'] # Create Todo if http_method == 'POST': body = json.loads(event['body']) todo_id = f"todo_{int(datetime.now().timestamp())}" table.put_item(Item={ 'userId': user_id, 'todoId': todo_id, 'title': body['title'], 'completed': False, 'createdAt': int(datetime.now().timestamp()) }) return { 'statusCode': 200, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps({'todoId': todo_id}) } # Get Todos elif http_method == 'GET': response = table.query( KeyConditionExpression='userId = :uid', ExpressionAttributeValues={':uid': user_id} ) return { 'statusCode': 200, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps(response['Items']) } # Update Todo elif http_method == 'PUT': todo_id = event['pathParameters']['id'] body = json.loads(event['body']) table.update_item( Key={'userId': user_id, 'todoId': todo_id}, UpdateExpression='SET completed = :c', ExpressionAttributeValues={':c': body['completed']} ) return { 'statusCode': 200, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps({'message': 'Updated'}) } # Delete Todo elif http_method == 'DELETE': todo_id = event['pathParameters']['id'] table.delete_item( Key={'userId': user_id, 'todoId': todo_id} ) return { 'statusCode': 200, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps({'message': 'Deleted'}) }

4단계: API Gateway 설정

# REST API 생성 aws apigateway create-rest-api \ --name TodoAPI \ --endpoint-configuration types=REGIONAL # Cognito Authorizer 추가 aws apigateway create-authorizer \ --rest-api-id api-id \ --name CognitoAuth \ --type COGNITO_USER_POOLS \ --provider-arns arn:aws:cognito-idp:region:account:userpool/pool-id \ --identity-source method.request.header.Authorization # 리소스 및 메서드 생성 # GET /todos # POST /todos # PUT /todos/{id} # DELETE /todos/{id}

5단계: 모바일 앱 통합

// React Native 예시 import { Auth, API } from 'aws-amplify'; // 로그인 async function signIn(email, password) { const user = await Auth.signIn(email, password); return user; } // Todo 가져오기 async function getTodos() { const todos = await API.get('TodoAPI', '/todos', { headers: { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` } }); return todos; } // Todo 추가 async function createTodo(title) { await API.post('TodoAPI', '/todos', { body: { title }, headers: { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` } }); }

향상: 실시간 동기화

[DynamoDB Streams] ↓ 변경 캡처 [Lambda: Broadcast][API Gateway WebSocket][모바일 앱들] ← 실시간 업데이트

프로젝트 2: MyBlog.com - 서버리스 웹사이트

요구사항

기능: - 정적 웹사이트 호스팅 - 동적 콘텐츠 (블로그 글) - 이미지 업로드 및 리사이징 - 검색 기능 - 댓글 시스템 제약: - 높은 트래픽 대응 - 글로벌 배포 - SEO 최적화

아키텍처

[CloudFront] ← 글로벌 CDN ├─ /static/* → [S3: Static Assets] │ └─ HTML, CSS, JS └─ /api/* → [API Gateway][Lambda Functions][DynamoDB]

상세 구성

프론트엔드 (React/Next.js)

[S3 Bucket: myblog-frontend] ├─ index.html ├─ _next/static/ ├─ images/ └─ favicon.ico
# 빌드 및 배포 npm run build aws s3 sync ./out s3://myblog-frontend/ aws cloudfront create-invalidation \ --distribution-id dist-id \ --paths "/*"

백엔드 API

# Lambda: Get Posts def get_posts(event, context): table = boto3.resource('dynamodb').Table('BlogPosts') response = table.scan( FilterExpression='published = :true', ExpressionAttributeValues={':true': True}, ScanIndexForward=False ) posts = response['Items'] return { 'statusCode': 200, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps(posts) } # Lambda: Create Post def create_post(event, context): body = json.loads(event['body']) post_id = str(uuid.uuid4()) table = boto3.resource('dynamodb').Table('BlogPosts') table.put_item(Item={ 'postId': post_id, 'title': body['title'], 'content': body['content'], 'author': body['author'], 'published': False, 'createdAt': int(datetime.now().timestamp()) }) return { 'statusCode': 201, 'headers': {'Access-Control-Allow-Origin': '*'}, 'body': json.dumps({'postId': post_id}) }

이미지 처리 파이프라인

[S3: uploads/] (원본 업로드) ↓ S3 Event [Lambda: Image Processing] ├─ 썸네일 생성 (300x300) ├─ 중간 크기 (800x600) └─ 워터마크 추가 ↓ [S3: processed/] ├─ thumbnails/ ├─ medium/ └─ full/
from PIL import Image import boto3 from io import BytesIO s3 = boto3.client('s3') def lambda_handler(event, context): # S3 이벤트 파싱 bucket = event['Records'][0]['s3']['bucket']['name'] key = event['Records'][0]['s3']['object']['key'] # 원본 이미지 다운로드 obj = s3.get_object(Bucket=bucket, Key=key) img = Image.open(BytesIO(obj['Body'].read())) # 썸네일 생성 thumb = img.copy() thumb.thumbnail((300, 300)) # S3 업로드 buffer = BytesIO() thumb.save(buffer, 'JPEG') buffer.seek(0) s3.put_object( Bucket=bucket, Key=key.replace('uploads/', 'thumbnails/'), Body=buffer, ContentType='image/jpeg' )

검색 기능 (OpenSearch)

[DynamoDB Streams][Lambda: Index to OpenSearch][OpenSearch] 검색 요청: [API Gateway][Lambda: Search][OpenSearch] → 결과 반환

프로젝트 3: 마이크로서비스 아키텍처

이커머스 플랫폼

[API Gateway] ├─ /users → [User Service] │ ├─ Lambda: Auth │ ├─ Lambda: Profile │ └─ DynamoDB: Users │ ├─ /products → [Product Service] │ ├─ Lambda: List │ ├─ Lambda: Detail │ └─ DynamoDB: Products │ ├─ /orders → [Order Service] │ ├─ Lambda: Create Order │ ├─ Lambda: Get Orders │ ├─ DynamoDB: Orders │ └─ Step Functions: Order Workflow │ └─ /payments → [Payment Service] ├─ Lambda: Process └─ DynamoDB: Transactions

주문 처리 워크플로우 (Step Functions)

{ "Comment": "Order Processing Workflow", "StartAt": "ValidateOrder", "States": { "ValidateOrder": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:validate-order", "Next": "CheckInventory" }, "CheckInventory": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:check-inventory", "Next": "IsInStock" }, "IsInStock": { "Type": "Choice", "Choices": [{ "Variable": "$.inStock", "BooleanEquals": true, "Next": "ProcessPayment" }], "Default": "OutOfStock" }, "ProcessPayment": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:process-payment", "Next": "IsPaymentSuccess" }, "IsPaymentSuccess": { "Type": "Choice", "Choices": [{ "Variable": "$.paymentStatus", "StringEquals": "SUCCESS", "Next": "CreateShipment" }], "Default": "PaymentFailed" }, "CreateShipment": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:create-shipment", "Next": "SendConfirmation" }, "SendConfirmation": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:send-email", "End": true }, "OutOfStock": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:notify-out-of-stock", "End": true }, "PaymentFailed": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:refund", "End": true } } }

이벤트 기반 통신

[Order Service] 주문 생성 [EventBridge] ├─ Rule: Order Created ├─ Target 1: [Lambda: Update Inventory] ├─ Target 2: [Lambda: Send Email] └─ Target 3: [Lambda: Analytics] └─ Rule: Order Cancelled ├─ Target 1: [Lambda: Restore Inventory] └─ Target 2: [Lambda: Refund]

소프트웨어 업데이트 배포

요구사항

상황: - 소프트웨어 회사 - 전 세계 고객에게 업데이트 배포 - 대용량 파일 (수GB) 문제: - 동시 다운로드 폭주 - 높은 대역폭 비용 - 느린 다운로드 속도

솔루션 아키텍처

[S3: Software Releases] ↓ Origin [CloudFront] ← 글로벌 캐싱 ↓ [사용자들] 추가: [Lambda@Edge: Version Check] ├─ 사용자 버전 확인 ├─ 최신 버전 리다이렉트 └─ 다운로드 통계 수집

단계별 배포 (Canary Release)

# Lambda@Edge: Canary Deployment def lambda_handler(event, context): request = event['Records'][0]['cf']['request'] # 사용자 ID 해시 user_id = request['headers'].get('user-agent', [{}])[0].get('value', '') hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16) # 10%에게만 새 버전 제공 if hash_value % 100 < 10: request['uri'] = '/v2.0/' + request['uri'] else: request['uri'] = '/v1.9/' + request['uri'] return request

모범 사례

1. 에러 처리

def lambda_handler(event, context): try: # 비즈니스 로직 result = process_data(event) return { 'statusCode': 200, 'body': json.dumps(result) } except ValueError as e: # 클라이언트 에러 return { 'statusCode': 400, 'body': json.dumps({'error': str(e)}) } except Exception as e: # 서버 에러 logger.error(f"Error: {str(e)}") # CloudWatch에 메트릭 전송 cloudwatch.put_metric_data( Namespace='MyApp', MetricData=[{ 'MetricName': 'Errors', 'Value': 1 }] ) return { 'statusCode': 500, 'body': json.dumps({'error': 'Internal Server Error'}) }

2. 보안

# 환경 변수로 시크릿 분리 DB_HOST = os.environ['DB_HOST'] # Secrets Manager 사용 def get_secret(): secret = secretsmanager.get_secret_value(SecretId='my-db-password') return json.loads(secret['SecretString']) # IAM 최소 권한 원칙 { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:PutItem" ], "Resource": "arn:aws:dynamodb:region:account:table/MyTable" }] }

3. 모니터링

# X-Ray 추적 from aws_xray_sdk.core import xray_recorder @xray_recorder.capture('process_order') def process_order(order_id): # 처리 로직 pass # 커스텀 메트릭 cloudwatch = boto3.client('cloudwatch') cloudwatch.put_metric_data( Namespace='MyApp/Orders', MetricData=[{ 'MetricName': 'OrdersProcessed', 'Value': 1, 'Unit': 'Count', 'Dimensions': [{ 'Name': 'Environment', 'Value': 'Production' }] }] )

비용 최적화 전략

1. Lambda 최적화

# 메모리 vs 실행 시간 트레이드오프 256MB: 10초 실행 = $0.00001667 512MB: 5초 실행 = $0.00001667 (동일!) → 메모리 증가 = 비용 동일하지만 성능 향상

2. DynamoDB On-Demand

초기 단계: - On-Demand (예측 불가) 안정화 후: - Provisioned + Auto Scaling - Reserved Capacity (1-3년) → 최대 70% 절감

3. API Gateway 캐싱

캐시 적용: - 요청: 100만/일 - 캐시 히트율: 80% - 비용 절감: 80% 추가 비용: - 캐시 (0.5GB): $0.02/시간

💡
서버리스 아키텍처는 빠른 개발과 자동 확장을 가능하게 한다.
핵심 패턴
  1. 모바일 백엔드:
  • Cognito (인증) + API Gateway + Lambda + DynamoDB
  1. 웹사이트:
  • S3 (정적) + CloudFront + API Gateway + Lambda
  1. 마이크로서비스:
  • API Gateway (라우팅) + Lambda (서비스) + EventBridge (통신)
  1. 배치 처리:
  • S3 (트리거) + Lambda + Step Functions (오케스트레이션)
  1. 실시간:
  • DynamoDB Streams + Lambda + WebSocket
 
Share article

silver