S3 스토리지 + DynamoDB
"리뷰 사진은 S3에, 리뷰 데이터는 DynamoDB에 저장하자"
이 문서는 강사용 답안입니다. 학생에게 공유하지 마세요. 각 단계의 정확한 답과 해설, 트러블슈팅 가이드가 포함되어 있습니다.
Step 1: S3 버킷 생성
답안
웹 콘솔 방법
- AWS 콘솔에서 S3 서비스로 이동
- 버킷 만들기 클릭
- 버킷 이름:
shopeasy-images-{ACCOUNT_ID}입력- 예:
shopeasy-images-123456789012 - 버킷 이름은 전 세계에서 고유해야 하므로 계정 ID를 붙여 중복 방지
- 예:
- AWS 리전: 아시아 태평양(서울) ap-northeast-2 선택
- 객체 소유권: ACL 비활성화됨 (권장) 유지
- "모든 퍼블릭 액세스 차단" 체크박스를 해제 (체크 풀기)
- 4개 항목 모두 체크 해제
- "현재 설정으로 인해 이 버킷과 그 안에 포함된 객체가 퍼블릭 상태가 될 수 있음을 알고 있습니다" 체크
- 나머지 설정은 기본값 유지
- 버킷 만들기 클릭
AWS CLI 방법
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "Account ID: $ACCOUNT_ID"
# S3 버킷 생성 (서울 리전)
aws s3api create-bucket \
--bucket shopeasy-images-${ACCOUNT_ID} \
--region ap-northeast-2 \
--create-bucket-configuration LocationConstraint=ap-northeast-2
# 퍼블릭 액세스 차단 해제
aws s3api put-public-access-block \
--bucket shopeasy-images-${ACCOUNT_ID} \
--public-access-block-configuration \
"BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"
# 버킷 생성 확인
aws s3 ls | grep shopeasy
S3는 보안을 위해 기본적으로 모든 퍼블릭 액세스를 차단합니다. 이것은 "이중 잠금" 개념입니다:
- 1차 잠금: 퍼블릭 액세스 차단 설정 (버킷 레벨)
- 2차 잠금: 버킷 정책 (어떤 객체를 공개할지 세부 제어)
1차 잠금이 켜져 있으면 2차에서 아무리 공개해도 차단됩니다.
리뷰 이미지를 브라우저에서 표시하려면 1차 잠금을 풀고, 2차(버킷 정책)에서 uploads/* 경로만 선별적으로 공개합니다.
실무에서는 CloudFront를 앞에 두고 S3는 비공개로 유지하는 것이 더 안전하지만, 이번 실습에서는 간단히 직접 공개 방식을 사용합니다.
Step 2: S3 버킷 CORS 설정
답안
웹 콘솔 방법
- S3 →
shopeasy-images-{ACCOUNT_ID}버킷 클릭 - 권한 탭 클릭
- CORS(Cross-origin resource sharing) 섹션으로 스크롤
- 편집 클릭
- 아래 JSON을 붙여넣기:
[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
- 변경 사항 저장 클릭
AWS CLI 방법
# CORS 설정 JSON 파일 생성
cat > /tmp/cors-config.json << 'EOF'
{
"CORSRules": [
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}
EOF
# CORS 설정 적용
aws s3api put-bucket-cors \
--bucket shopeasy-images-${ACCOUNT_ID} \
--cors-configuration file:///tmp/cors-config.json
# CORS 설정 확인
aws s3api get-bucket-cors \
--bucket shopeasy-images-${ACCOUNT_ID}
| 설정 | 값 | 의미 |
|---|---|---|
AllowedOrigins |
["*"] |
모든 도메인에서의 요청 허용. 실무에서는 프론트엔드 도메인만 지정 |
AllowedMethods |
["GET","PUT","POST"] |
GET(이미지 조회), PUT(업로드), POST(멀티파트 업로드) 허용 |
AllowedHeaders |
["*"] |
모든 HTTP 헤더 허용 (Content-Type, Authorization 등) |
MaxAgeSeconds |
3000 |
CORS Preflight 응답을 3000초(50분) 캐시. 반복 요청 시 성능 향상 |
실무 팁: 프로덕션에서는 AllowedOrigins를 ["http://your-frontend-domain.com"]처럼
구체적인 도메인으로 제한해야 합니다. "*"는 개발/실습 용도입니다.
Step 3: S3 버킷 정책 설정
답안
웹 콘솔 방법
- S3 →
shopeasy-images-{ACCOUNT_ID}버킷 → 권한 탭 - 버킷 정책 섹션 → 편집
- 아래 JSON을 붙여넣기 (
{ACCOUNT_ID}를 본인의 계정 ID로 교체):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadUploads",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-images-{ACCOUNT_ID}/uploads/*"
}
]
}
- 변경 사항 저장 클릭
AWS CLI 방법
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 버킷 정책 JSON 파일 생성
cat > /tmp/bucket-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadUploads",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-images-${ACCOUNT_ID}/uploads/*"
}
]
}
EOF
# 버킷 정책 적용
aws s3api put-bucket-policy \
--bucket shopeasy-images-${ACCOUNT_ID} \
--policy file:///tmp/bucket-policy.json
# 버킷 정책 확인
aws s3api get-bucket-policy \
--bucket shopeasy-images-${ACCOUNT_ID} \
--output text | python3 -m json.tool
| 항목 | 값 | 의미 |
|---|---|---|
Effect |
Allow |
허용 정책 |
Principal |
"*" |
누구나 (모든 사용자, 비로그인 포함) |
Action |
s3:GetObject |
객체 읽기(다운로드)만 허용. 업로드/삭제는 불가 |
Resource |
.../uploads/* |
uploads/ 폴더 안의 모든 객체만 대상. 다른 경로는 비공개 |
보안 핵심: Resource를 uploads/*로 제한했기 때문에
버킷 루트에 있는 다른 파일(설정 파일, 로그 등)은 퍼블릭으로 노출되지 않습니다.
만약 "Resource": "arn:aws:s3:::shopeasy-images-xxxx/*"로 설정하면
버킷의 모든 파일이 공개되어 보안 사고가 발생할 수 있습니다.
Step 4: DynamoDB 테이블 생성
답안
웹 콘솔 방법
- AWS 콘솔에서 DynamoDB 서비스로 이동
- 테이블 만들기 클릭
- 테이블 세부 정보:
- 테이블 이름:
Reviews - 파티션 키:
productId(문자열)
- 테이블 이름:
- 정렬 키 추가 체크:
- 정렬 키:
createdAt#userId(문자열)
- 정렬 키:
- 테이블 설정: 설정 사용자 지정 선택
- 읽기/쓰기 용량 설정:
- 용량 모드: 온디맨드 선택
- 나머지 설정(암호화, 태그 등)은 기본값 유지
- 테이블 생성 클릭
- 상태가 활성(Active)이 될 때까지 대기 (보통 10~30초)
AWS CLI 방법
# DynamoDB 테이블 생성
aws dynamodb create-table \
--table-name Reviews \
--attribute-definitions \
AttributeName=productId,AttributeType=S \
AttributeName="createdAt#userId",AttributeType=S \
--key-schema \
AttributeName=productId,KeyType=HASH \
AttributeName="createdAt#userId",KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region ap-northeast-2
# 테이블 생성 완료 대기
aws dynamodb wait table-exists \
--table-name Reviews \
--region ap-northeast-2
# 테이블 상태 확인
aws dynamodb describe-table \
--table-name Reviews \
--region ap-northeast-2 \
--query "Table.{Name:TableName, Status:TableStatus, KeySchema:KeySchema, BillingMode:BillingModeSummary.BillingMode}"
| 파라미터 | 값 | 설명 |
|---|---|---|
--attribute-definitions |
productId (S), createdAt#userId (S) | 키에 사용할 속성 정의. S = String, N = Number |
--key-schema |
HASH + RANGE | HASH = 파티션 키, RANGE = 정렬 키 |
--billing-mode |
PAY_PER_REQUEST | 온디맨드 모드 (사용량 기반 과금). 프리 티어에 포함 |
주의: --attribute-definitions에는 키로 사용하는 속성만 정의합니다.
리뷰 내용(content), 별점(rating) 등 나머지 속성은 여기서 정의하지 않습니다.
DynamoDB는 스키마리스이므로 데이터를 넣을 때 자유롭게 속성을 추가할 수 있습니다.
파티션 키: productId
- "PROD-001 상품의 리뷰를 모두 보여줘" 같은 쿼리가 가장 빈번
- 같은 상품의 리뷰가 하나의 파티션에 모여있으므로 효율적으로 조회 가능
정렬 키: createdAt#userId
createdAt을 앞에 두어 최신 리뷰부터 정렬#userId를 뒤에 붙여 동일 시각에 작성된 리뷰를 구분 (유일성 보장)- 예:
2024-12-01T10:30:00Z#USER-042
Step 5: API 서버 .env 수정
답안
nano 편집기로 수정
# EC2 접속
ssh -i your-key.pem ec2-user@{EC2_PUBLIC_IP}
# API 서버 디렉토리로 이동
cd ~/shopeasy/api-server
# .env 파일 편집
nano .env
파일 끝에 아래 내용을 추가합니다 ({ACCOUNT_ID}를 본인 계정 ID로 교체):
# S3 설정
STORAGE_TYPE=s3
S3_BUCKET=shopeasy-images-{ACCOUNT_ID}
S3_REGION=ap-northeast-2
# DynamoDB 설정
REVIEW_STORE=dynamodb
DYNAMODB_TABLE=Reviews
DYNAMODB_REGION=ap-northeast-2
저장: Ctrl+O → Enter, 종료: Ctrl+X
CLI로 한 번에 추가
# EC2 접속
ssh -i your-key.pem ec2-user@{EC2_PUBLIC_IP}
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# .env 파일에 환경변수 추가
cat >> ~/shopeasy/api-server/.env << EOF
# S3 설정
STORAGE_TYPE=s3
S3_BUCKET=shopeasy-images-${ACCOUNT_ID}
S3_REGION=ap-northeast-2
# DynamoDB 설정
REVIEW_STORE=dynamodb
DYNAMODB_TABLE=Reviews
DYNAMODB_REGION=ap-northeast-2
EOF
# 추가된 내용 확인
cat ~/shopeasy/api-server/.env
| 환경변수 | 값 | 설명 |
|---|---|---|
STORAGE_TYPE |
s3 |
이미지 저장소를 S3로 설정 (기본: local 파일시스템) |
S3_BUCKET |
버킷 이름 | 이미지를 업로드할 S3 버킷 |
S3_REGION |
ap-northeast-2 |
S3 버킷이 위치한 리전 |
REVIEW_STORE |
dynamodb |
리뷰 저장소를 DynamoDB로 설정 (기본: SQLite) |
DYNAMODB_TABLE |
Reviews |
리뷰 데이터를 저장할 DynamoDB 테이블 |
DYNAMODB_REGION |
ap-northeast-2 |
DynamoDB 테이블이 위치한 리전 |
핵심: AWS_ACCESS_KEY_ID나 AWS_SECRET_ACCESS_KEY를 넣지 않습니다.
EC2에 연결된 IAM Role이 AWS SDK에 자동으로 임시 자격 증명을 제공합니다.
Access Key를 .env에 하드코딩하면 보안 사고의 원인이 됩니다.
Step 6: API 서버 재시작
답안
pm2로 재시작
# API 서버 재시작
pm2 restart all
# 상태 확인
pm2 status
# 로그 확인 (에러 없는지)
pm2 logs --lines 20
직접 실행
# 기존 Node.js 프로세스 종료
pkill -f "node server.js" 2>/dev/null
# 잠시 대기
sleep 2
# API 서버 백그라운드 실행
cd ~/shopeasy/api-server
nohup node server.js > ~/shopeasy-api.log 2>&1 &
# 프로세스 확인
ps aux | grep "node server"
# 로그 확인
tail -20 ~/shopeasy-api.log
Node.js 앱은 .env 파일을 서버 시작 시점에 한 번만 읽습니다 (dotenv 라이브러리 사용).
따라서 환경변수를 변경한 후에는 반드시 서버를 재시작해야 새로운 설정이 적용됩니다.
pm2의 restart는 프로세스를 종료하고 다시 시작하므로, .env가 다시 로드됩니다.
Step 7: 리뷰 작성 테스트
답안
기본 리뷰 작성 테스트
# 테스트용 이미지 생성 (1x1 픽셀 PNG)
echo -e '\x89PNG\r\n\x1a\n' > /tmp/test-image.png
# 리뷰 작성 API 호출
curl -s -X POST http://{EC2_PUBLIC_IP}:5000/api/reviews \
-F "productId=PROD-001" \
-F "userId=USER-042" \
-F "userName=testuser" \
-F "rating=5" \
-F "content=배송이 빠르고 품질이 좋습니다!" \
-F "image=@/tmp/test-image.png" | python3 -m json.tool
정상 응답 예시:
{
"success": true,
"review": {
"productId": "PROD-001",
"createdAt#userId": "2024-12-01T10:30:00Z#USER-042",
"rating": 5,
"content": "배송이 빠르고 품질이 좋습니다!",
"imageUrl": "https://shopeasy-images-123456789012.s3.ap-northeast-2.amazonaws.com/uploads/review-xxxx.png",
"userName": "testuser"
}
}
상세 테스트 (여러 리뷰 작성 + 조회)
# 테스트 이미지 생성
echo -e '\x89PNG\r\n\x1a\n' > /tmp/test-image.png
# 리뷰 1: 이미지 포함
curl -s -X POST http://{EC2_PUBLIC_IP}:5000/api/reviews \
-F "productId=PROD-001" \
-F "userId=USER-042" \
-F "userName=김철수" \
-F "rating=5" \
-F "content=배송이 빠르고 품질이 좋습니다!" \
-F "image=@/tmp/test-image.png"
echo ""
# 리뷰 2: 이미지 없이 텍스트만
curl -s -X POST http://{EC2_PUBLIC_IP}:5000/api/reviews \
-H "Content-Type: application/json" \
-d '{
"productId": "PROD-001",
"userId": "USER-099",
"userName": "이영희",
"rating": 4,
"content": "가격 대비 괜찮습니다. 다음에도 구매할 의향이 있습니다."
}'
echo ""
# 리뷰 3: 다른 상품
curl -s -X POST http://{EC2_PUBLIC_IP}:5000/api/reviews \
-H "Content-Type: application/json" \
-d '{
"productId": "PROD-002",
"userId": "USER-042",
"userName": "김철수",
"rating": 3,
"content": "보통입니다."
}'
echo ""
echo "=== PROD-001 리뷰 조회 ==="
# 특정 상품의 리뷰 조회
curl -s http://{EC2_PUBLIC_IP}:5000/api/reviews/PROD-001 | python3 -m json.tool
-F: Form 데이터 (multipart/form-data). 파일 업로드 시 사용-F "image=@파일경로":@는 파일을 첨부한다는 의미-H "Content-Type: application/json": JSON 형식으로 전송-d: 요청 본문 (Request Body)-s: Silent 모드 (진행률 표시 숨김)| python3 -m json.tool: JSON 응답을 보기 좋게 포맷팅
Step 8: S3에 이미지 저장 확인
답안
웹 콘솔에서 확인
- AWS 콘솔 → S3
shopeasy-images-{ACCOUNT_ID}버킷 클릭uploads/폴더 클릭- 업로드된 이미지 파일 확인
- 이미지 파일 클릭 → 객체 URL 복사
- 브라우저 새 탭에서 URL 열기 - 이미지가 표시되면 성공
AWS CLI로 확인
# S3 버킷의 uploads/ 폴더 내용 확인
aws s3 ls s3://shopeasy-images-${ACCOUNT_ID}/uploads/
# 또는 재귀적으로 전체 확인
aws s3 ls s3://shopeasy-images-${ACCOUNT_ID}/ --recursive
# 이미지 URL 확인 (브라우저에서 직접 접근 테스트)
echo "이미지 URL 테스트:"
echo "https://shopeasy-images-${ACCOUNT_ID}.s3.ap-northeast-2.amazonaws.com/uploads/"
# curl로 이미지 접근 테스트 (HTTP 200이면 성공)
FIRST_IMAGE=$(aws s3 ls s3://shopeasy-images-${ACCOUNT_ID}/uploads/ --recursive | head -1 | awk '{print $4}')
if [ -n "$FIRST_IMAGE" ]; then
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" \
"https://shopeasy-images-${ACCOUNT_ID}.s3.ap-northeast-2.amazonaws.com/${FIRST_IMAGE}"
fi
aws s3 ls결과에 파일이 보이면 S3 업로드 성공- 브라우저에서 이미지 URL이 열리면 버킷 정책(퍼블릭 읽기) 설정 성공
- 403 Forbidden 에러가 나오면 버킷 정책 또는 퍼블릭 액세스 차단 설정 확인 필요
Step 9: DynamoDB에 리뷰 데이터 확인
답안
웹 콘솔에서 확인
- AWS 콘솔 → DynamoDB
- 좌측 메뉴 → 테이블 →
Reviews클릭 - 항목 탐색 탭 클릭
- 항목 검색 또는 스캔 실행 → 스캔 선택 → 실행
- 저장된 리뷰 데이터가 표시되면 성공
- 특정 상품의 리뷰만 보려면:
- 쿼리 선택
- 파티션 키:
productId=PROD-001 - 실행 클릭
AWS CLI로 확인
# 전체 스캔 (모든 리뷰 조회)
aws dynamodb scan \
--table-name Reviews \
--region ap-northeast-2
# 결과를 보기 좋게 포맷팅
aws dynamodb scan \
--table-name Reviews \
--region ap-northeast-2 \
--output table
# 특정 상품의 리뷰만 조회 (Query - 더 효율적)
aws dynamodb query \
--table-name Reviews \
--key-condition-expression "productId = :pid" \
--expression-attribute-values '{":pid": {"S": "PROD-001"}}' \
--region ap-northeast-2
# 테이블 통계 확인 (항목 수)
aws dynamodb describe-table \
--table-name Reviews \
--region ap-northeast-2 \
--query "Table.{ItemCount:ItemCount, TableSizeBytes:TableSizeBytes, Status:TableStatus}"
| 방법 | 동작 | 비용 | 용도 |
|---|---|---|---|
| Scan | 테이블 전체를 읽음 | 높음 (전체 데이터 읽기) | 개발/디버깅, 소량 데이터 |
| Query | 파티션 키로 특정 데이터만 읽음 | 낮음 (필요한 데이터만) | 프로덕션 (권장) |
프로덕션에서는 항상 Query를 사용해야 합니다. Scan은 데이터가 많아지면 매우 느리고 비용이 많이 듭니다. 이번 실습에서는 데이터가 적으므로 확인 용도로 Scan을 사용했습니다.
트러블슈팅 가이드
자주 발생하는 문제와 해결 방법
원인: S3 버킷의 CORS 설정이 없거나 잘못 설정됨
해결 방법:
- S3 버킷 → 권한 → CORS 섹션에서 설정이 올바른지 확인
- JSON 형식 오류가 없는지 확인 (배열
[]로 감싸야 함) AllowedOrigins에 프론트엔드 URL이 포함되어 있는지 확인
# CORS 설정 확인
aws s3api get-bucket-cors --bucket shopeasy-images-${ACCOUNT_ID}
# 설정이 없으면 다시 적용
aws s3api put-bucket-cors \
--bucket shopeasy-images-${ACCOUNT_ID} \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["*"],"AllowedMethods":["GET","PUT","POST"],"AllowedHeaders":["*"],"MaxAgeSeconds":3000}]}'
원인: IAM Role에 S3 권한이 없거나, 버킷 이름이 잘못됨
해결 방법:
- EC2에 연결된 IAM Role에
AmazonS3FullAccess또는 커스텀 S3 정책이 있는지 확인 - .env의
S3_BUCKET값이 실제 버킷 이름과 정확히 일치하는지 확인 - 버킷의 리전이
ap-northeast-2인지 확인
# EC2에서 IAM Role 확인
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# S3 권한 테스트 (EC2에서 실행)
aws s3 cp /tmp/test-image.png s3://shopeasy-images-${ACCOUNT_ID}/test-upload.png
aws s3 rm s3://shopeasy-images-${ACCOUNT_ID}/test-upload.png
# 버킷 존재 확인
aws s3api head-bucket --bucket shopeasy-images-${ACCOUNT_ID}
원인: 퍼블릭 액세스 차단이 해제되지 않았거나, 버킷 정책이 잘못됨
해결 방법:
- 버킷의 "퍼블릭 액세스 차단" 설정 4개 항목이 모두 꺼짐인지 확인
- 버킷 정책의
ResourceARN이 올바른지 확인 (버킷 이름 오타 체크) - 업로드된 이미지가
uploads/경로 아래에 있는지 확인
# 퍼블릭 액세스 차단 설정 확인
aws s3api get-public-access-block --bucket shopeasy-images-${ACCOUNT_ID}
# 모두 false여야 함:
# {
# "PublicAccessBlockConfiguration": {
# "BlockPublicAcls": false,
# "IgnorePublicAcls": false,
# "BlockPublicPolicy": false,
# "RestrictPublicBuckets": false
# }
# }
# 버킷 정책 확인
aws s3api get-bucket-policy --bucket shopeasy-images-${ACCOUNT_ID} --output text | python3 -m json.tool
원인: DynamoDB 테이블이 존재하지 않거나, 테이블 이름/리전이 잘못됨
해결 방법:
- .env의
DYNAMODB_TABLE값이Reviews인지 확인 (대소문자 구분) - .env의
DYNAMODB_REGION이ap-northeast-2인지 확인 - DynamoDB 콘솔에서 테이블 상태가 Active인지 확인
# 테이블 존재 확인
aws dynamodb describe-table --table-name Reviews --region ap-northeast-2
# 테이블 목록 확인
aws dynamodb list-tables --region ap-northeast-2
# .env 확인
cat ~/shopeasy/api-server/.env | grep -i dynamo
원인: 데이터 저장 시 파티션 키(productId)나 정렬 키(createdAt#userId)가 누락됨
해결 방법:
- API 서버 코드에서 리뷰 저장 시
productId와createdAt#userId를 모두 포함하는지 확인 - 정렬 키 이름이
createdAt#userId인지 확인 (# 포함, 대소문자 정확히) - 값이 빈 문자열이 아닌지 확인 (DynamoDB는 빈 문자열 키를 허용하지 않음)
# 테이블 키 스키마 확인
aws dynamodb describe-table \
--table-name Reviews \
--region ap-northeast-2 \
--query "Table.KeySchema"
# 예상 결과:
# [
# { "AttributeName": "productId", "KeyType": "HASH" },
# { "AttributeName": "createdAt#userId", "KeyType": "RANGE" }
# ]
원인: EC2에 연결된 IAM Role에 S3 또는 DynamoDB 접근 권한이 없음
해결 방법:
- IAM 콘솔 → 역할 →
ShopEasy-EC2-Role클릭 - 권한 정책에 다음이 포함되어 있는지 확인:
AmazonS3FullAccess(또는 커스텀 S3 정책)AmazonDynamoDBFullAccess(또는 커스텀 DynamoDB 정책)
- 없으면 권한 추가 → 정책 연결에서 추가
# IAM Role에 S3 정책 추가
aws iam attach-role-policy \
--role-name ShopEasy-EC2-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
# IAM Role에 DynamoDB 정책 추가
aws iam attach-role-policy \
--role-name ShopEasy-EC2-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
# 연결된 정책 확인
aws iam list-attached-role-policies --role-name ShopEasy-EC2-Role
FullAccess 정책은 실습용입니다. 프로덕션에서는 필요한 최소 권한만 부여하는 커스텀 정책을 사용해야 합니다
(예: 특정 버킷에 대한 PutObject, GetObject만 허용).
원인: AWS SDK 패키지가 설치되지 않음
해결 방법:
# API 서버 디렉토리로 이동
cd ~/shopeasy/api-server
# AWS SDK v3 패키지 설치 (Node.js 프로젝트)
npm install @aws-sdk/client-s3 @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
# 또는 AWS SDK v2 사용 시
npm install aws-sdk
# 서버 재시작
pm2 restart all
원인: 키 스키마나 테이블 이름을 잘못 설정
해결 방법: DynamoDB 테이블의 키 스키마는 생성 후 변경이 불가합니다. 삭제 후 재생성해야 합니다.
# 기존 테이블 삭제
aws dynamodb delete-table \
--table-name Reviews \
--region ap-northeast-2
# 삭제 완료 대기
aws dynamodb wait table-not-exists \
--table-name Reviews \
--region ap-northeast-2
# 올바른 스키마로 재생성
aws dynamodb create-table \
--table-name Reviews \
--attribute-definitions \
AttributeName=productId,AttributeType=S \
AttributeName="createdAt#userId",AttributeType=S \
--key-schema \
AttributeName=productId,KeyType=HASH \
AttributeName="createdAt#userId",KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region ap-northeast-2
# 생성 완료 대기
aws dynamodb wait table-exists \
--table-name Reviews \
--region ap-northeast-2
echo "테이블 재생성 완료!"
- S3 버킷 이름에 대문자 사용 - S3 버킷 이름은 소문자, 숫자, 하이픈만 허용됩니다
- 퍼블릭 액세스 차단 해제 후 확인 체크 안 함 - "알고 있습니다" 체크를 빼먹으면 저장이 안 됩니다
- 버킷 정책에서 ACCOUNT_ID 교체를 잊음 -
{ACCOUNT_ID}를 실제 계정 ID로 바꿔야 합니다 - DynamoDB 정렬 키에 # 빼먹음 -
createdAt#userId에서#은 속성 이름의 일부입니다 - .env 수정 후 서버 재시작을 잊음 - Node.js는 환경변수를 시작 시 한 번만 읽습니다
- 리전 불일치 - S3 버킷, DynamoDB 테이블, .env 설정이 모두
ap-northeast-2여야 합니다