프론트엔드 S3 배포 + 통합 테스트 - 정답 및 해설
Step 1 정답: S3 버킷 생성
정답
웹 콘솔 풀이
- AWS 콘솔에 로그인합니다.
- 상단 검색창에
S3를 입력하고 S3 서비스를 선택합니다. - Create bucket 버튼을 클릭합니다.
- General configuration:
- Bucket name:
shopeasy-frontend-{ACCOUNT_ID} - AWS Region:
Asia Pacific (Seoul) ap-northeast-2
- Bucket name:
- Object Ownership:
ACLs disabled (recommended)선택 (기본값)
- Block Public Access settings for this bucket:
Block all public access체크를 해제합니다.- "I acknowledge that the current settings might result in this bucket and the objects within becoming public" 체크박스를 체크합니다.
- 나머지 설정은 기본값으로 두고 Create bucket을 클릭합니다.
AWS CLI 풀이
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "계정 ID: $ACCOUNT_ID"
# S3 버킷 생성
aws s3api create-bucket \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--region ap-northeast-2 \
--create-bucket-configuration LocationConstraint=ap-northeast-2
# 생성 확인
aws s3api head-bucket --bucket shopeasy-frontend-${ACCOUNT_ID}
echo "버킷 생성 완료!"
S3 버킷 이름은 전 세계적으로 고유해야 합니다. 계정 ID를 포함하면 이름 충돌을 방지할 수 있습니다. ap-northeast-2 리전은 서울 리전으로, EC2 API 서버와 같은 리전에 두어야 네트워크 지연이 최소화됩니다.
CLI 참고: ap-northeast-2 리전에서 버킷을 생성할 때는 반드시 --create-bucket-configuration LocationConstraint=ap-northeast-2 옵션을 추가해야 합니다. us-east-1을 제외한 모든 리전에서 이 옵션이 필요합니다.
Step 2 정답: 퍼블릭 액세스 차단 해제
정답
웹 콘솔 풀이
- S3 → 버킷 목록에서
shopeasy-frontend-{ACCOUNT_ID}를 클릭합니다. - Permissions 탭을 클릭합니다.
- Block public access (bucket settings) 섹션에서 Edit을 클릭합니다.
- Block all public access 체크박스를 해제합니다.
- Save changes를 클릭합니다.
- 확인 창에
confirm을 입력하고 Confirm을 클릭합니다.
버킷 생성 시에 이미 해제했다면 이 단계는 건너뛰어도 됩니다.
AWS CLI 풀이
# 계정 ID 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 퍼블릭 액세스 차단 해제
aws s3api put-public-access-block \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--public-access-block-configuration \
BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false
# 설정 확인
aws s3api get-public-access-block \
--bucket shopeasy-frontend-${ACCOUNT_ID}
# 모든 값이 false로 표시되어야 합니다
S3 버킷은 기본적으로 모든 퍼블릭 액세스가 차단되어 있습니다. 정적 웹사이트 호스팅을 위해서는 이 차단을 해제해야 합니다.
4가지 퍼블릭 액세스 차단 설정:
BlockPublicAcls: 퍼블릭 ACL 추가 차단IgnorePublicAcls: 기존 퍼블릭 ACL 무시BlockPublicPolicy: 퍼블릭 버킷 정책 차단RestrictPublicBuckets: 퍼블릭 버킷 제한
프론트엔드 웹 호스팅 용도이므로 모두 해제합니다. 하지만 리뷰 이미지 버킷(Chapter 05) 등 다른 S3 버킷은 퍼블릭 접근을 허용하지 않도록 주의하세요.
Step 3 정답: 정적 웹사이트 호스팅 활성화
정답
웹 콘솔 풀이
- S3 →
shopeasy-frontend-{ACCOUNT_ID}버킷 클릭 - Properties 탭을 클릭합니다.
- 페이지 맨 아래로 스크롤하여 Static website hosting 섹션을 찾습니다.
- Edit을 클릭합니다.
- 설정을 다음과 같이 변경합니다:
- Static website hosting: Enable
- Hosting type: Host a static website
- Index document:
index.html - Error document:
index.html
- Save changes를 클릭합니다.
- 다시 Properties 탭의 Static website hosting 섹션에서 Bucket website endpoint URL을 확인합니다.
# 웹사이트 엔드포인트 형식
http://shopeasy-frontend-{ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com
AWS CLI 풀이
# 계정 ID 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 정적 웹사이트 호스팅 활성화
aws s3 website s3://shopeasy-frontend-${ACCOUNT_ID}/ \
--index-document index.html \
--error-document index.html
# 설정 확인
aws s3api get-bucket-website \
--bucket shopeasy-frontend-${ACCOUNT_ID}
# 웹사이트 URL 출력
echo "웹사이트 URL: http://shopeasy-frontend-${ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com"
Index document: 루트 URL(/)로 접속했을 때 반환할 기본 문서입니다. React 앱의 진입점인 index.html을 지정합니다.
Error document: 요청한 파일이 없을 때(404) 반환할 문서입니다. React SPA에서는 클라이언트 사이드 라우팅을 사용하므로, 모든 경로에서 index.html을 반환해야 합니다. 예를 들어 /products/3으로 접속하면 S3에 해당 파일이 없지만, index.html을 반환하면 React Router가 URL을 파싱하여 상품 상세 페이지를 렌더링합니다.
주의: S3 웹사이트 엔드포인트 URL 형식은 리전마다 다릅니다. ap-northeast-2(서울)의 경우 s3-website.ap-northeast-2 형식을 사용합니다 (하이픈 주의: s3-website. 뒤에 리전명).
Step 4 정답: 버킷 정책 설정 (퍼블릭 읽기 허용)
정답
웹 콘솔 풀이
- S3 →
shopeasy-frontend-{ACCOUNT_ID}버킷 클릭 - Permissions 탭을 클릭합니다.
- Bucket policy 섹션에서 Edit을 클릭합니다.
- 아래 JSON 정책을 붙여넣습니다 (
{ACCOUNT_ID}를 본인의 계정 ID로 변경):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-frontend-{ACCOUNT_ID}/*"
}
]
}
- Save changes를 클릭합니다.
- Permissions 탭에서 버킷이 "Publicly accessible"로 표시되는 것을 확인합니다.
AWS CLI 풀이
# 계정 ID 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 버킷 정책 JSON 생성
cat > /tmp/frontend-bucket-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-frontend-${ACCOUNT_ID}/*"
}
]
}
EOF
# 버킷 정책 적용
aws s3api put-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--policy file:///tmp/frontend-bucket-policy.json
# 정책 확인
aws s3api get-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--output text | python3 -m json.tool
echo "버킷 정책 적용 완료!"
이 버킷 정책은 다음과 같이 해석됩니다:
- Effect: Allow - 허용
- Principal: * - 모든 사용자 (인터넷의 누구나)
- Action: s3:GetObject - 파일 읽기(다운로드)만 허용
- Resource: .../* - 이 버킷의 모든 객체
읽기만 허용하므로 외부 사용자가 파일을 수정하거나 삭제할 수는 없습니다. 파일 업로드/삭제는 IAM 사용자 또는 IAM 역할을 가진 EC2만 가능합니다.
Resource에 /*를 빠뜨리면 안 됩니다! /*가 없으면 버킷 자체에 대한 권한이고, /*가 있어야 버킷 안의 개별 객체(파일)에 대한 권한입니다.
Step 5 정답: EC2에서 프론트엔드 빌드
정답
EC2 SSH 접속 후 빌드
- 먼저 EC2 퍼블릭 IP를 확인합니다:
AWS 콘솔 → EC2 → Instances → ShopEasy 인스턴스의 Public IPv4 address 확인 - SSH로 EC2에 접속합니다:
# EC2에 SSH 접속
ssh -i "ShopEasy-Key.pem" ec2-user@3.35.xxx.xxx
- 프론트엔드 디렉토리에서 빌드합니다:
# 프론트엔드 디렉토리로 이동
cd ~/ecommerce-app/frontend
# 의존성 설치 (처음이라면)
npm install
# EC2 퍼블릭 IP를 확인 (EC2 내부에서)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
EC2_IP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/public-ipv4)
echo "EC2 퍼블릭 IP: $EC2_IP"
# VITE_API_URL을 설정하여 빌드
VITE_API_URL=http://${EC2_IP}:5000 npm run build
# 빌드 결과 확인
ls -la dist/
echo ""
echo "=== 빌드된 파일 목록 ==="
find dist/ -type f
전체 빌드 명령어
# 프론트엔드 디렉토리로 이동
cd ~/ecommerce-app/frontend
# 의존성 설치
npm install
# 방법 1: 직접 IP 입력
VITE_API_URL=http://3.35.xxx.xxx:5000 npm run build
# 방법 2: .env 파일 사용
echo "VITE_API_URL=http://3.35.xxx.xxx:5000" > .env
npm run build
# 방법 3: EC2 메타데이터에서 자동으로 IP 가져오기
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
EC2_IP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/public-ipv4)
VITE_API_URL=http://${EC2_IP}:5000 npm run build
# 빌드 결과 확인
ls -la dist/
# 예상 결과:
# dist/
# index.html
# assets/
# index-xxxxx.js (번들된 JavaScript)
# index-xxxxx.css (번들된 CSS)
# ...
# VITE_API_URL이 올바르게 포함되었는지 확인
grep -o 'http://[^"]*:5000' dist/assets/index-*.js | head -1
VITE_API_URL 환경변수가 핵심입니다. 이 값은 빌드 시점에 JavaScript 코드에 하드코딩됩니다. 빌드 후에는 변경할 수 없으므로, 반드시 올바른 EC2 퍼블릭 IP를 입력해야 합니다.
3가지 설정 방법:
- 인라인 환경변수:
VITE_API_URL=http://... npm run build- 가장 간단 - .env 파일:
.env파일에 작성 - Vite가 자동으로 읽음 - 메타데이터 활용: EC2 내부에서 자동으로 IP를 가져오기 - 자동화에 유용
메타데이터 서비스(IMDS v2): EC2 인스턴스 내부에서 169.254.169.254로 요청하면 인스턴스의 메타데이터(IP, IAM 역할 등)를 조회할 수 있습니다. IMDSv2에서는 토큰을 먼저 발급받아야 합니다.
Step 6 정답: dist/ 폴더를 S3에 업로드
정답
웹 콘솔 풀이 (파일 업로드)
웹 콘솔에서 직접 파일을 업로드할 수도 있지만, 파일 수가 많고 폴더 구조를 유지해야 하므로 AWS CLI 사용을 강력히 권장합니다.
- S3 →
shopeasy-frontend-{ACCOUNT_ID}버킷 클릭 - Upload 버튼 클릭
- Add files 또는 Add folder로
dist/폴더의 내용물을 업로드 - 주의: 폴더 구조(assets/ 등)가 유지되어야 합니다
- Upload 클릭
그러나 실무에서는 항상 CLI를 사용합니다. 아래 CLI 풀이를 참고하세요.
AWS CLI 풀이
# 계정 ID 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# dist/ 폴더를 S3 버킷에 업로드 (동기화)
aws s3 sync dist/ s3://shopeasy-frontend-${ACCOUNT_ID}/
# 업로드된 파일 확인
aws s3 ls s3://shopeasy-frontend-${ACCOUNT_ID}/ --recursive --human-readable
# 예상 출력:
# 2026-03-03 10:30:00 1.2 KiB index.html
# 2026-03-03 10:30:00 156.3 KiB assets/index-a1b2c3.js
# 2026-03-03 10:30:00 23.4 KiB assets/index-d4e5f6.css
# ...
# 파일 수 확인
echo "업로드된 파일 수:"
aws s3 ls s3://shopeasy-frontend-${ACCOUNT_ID}/ --recursive | wc -l
이후 업데이트 시 (재배포)
# 코드 수정 후 다시 빌드
npm run build
# --delete 옵션으로 S3에만 있는 이전 파일 삭제 (깔끔한 동기화)
aws s3 sync dist/ s3://shopeasy-frontend-${ACCOUNT_ID}/ --delete
aws s3 sync 명령어는 로컬 폴더와 S3 버킷을 비교하여 변경된 파일만 업로드합니다. 전체 파일을 다시 업로드하는 것보다 훨씬 빠르고 효율적입니다.
--delete 옵션: 로컬에는 없지만 S3에는 있는 파일을 삭제합니다. 이전 빌드의 잔여 파일을 깔끔하게 정리할 수 있습니다. 처음 업로드할 때는 필요 없지만, 재배포 시에는 사용하는 것이 좋습니다.
EC2의 IAM 역할 권한: EC2에 연결된 IAM 역할에 이 프론트엔드 버킷에 대한 s3:PutObject, s3:ListBucket, s3:DeleteObject 권한이 필요합니다. Chapter 04에서 설정한 IAM 역할 정책을 확인하세요.
Step 7 정답: S3 웹사이트 URL로 접속 테스트
정답
브라우저에서 접속
- 웹 브라우저를 열고 S3 웹사이트 엔드포인트 URL로 접속합니다:
http://shopeasy-frontend-{ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com
- ShopEasy 메인 페이지가 정상적으로 로드되는지 확인합니다.
- 브라우저 개발자 도구(F12)를 열어 Console 탭에서 에러가 없는지 확인합니다.
- Network 탭에서 API 요청이 정상적으로 처리되는지 확인합니다.
통합 테스트 순서
- 회원가입: 새 계정 생성 → 성공 메시지 확인
- 로그인: 생성한 계정으로 로그인 → 사용자 이름 표시 확인
- 상품 목록: 메인 페이지에서 상품이 로드되는지 확인 (RDS 연동)
- 상품 상세: 상품 클릭 → 상세 정보 확인
- 리뷰 작성: 별점 + 텍스트 + 사진 → 리뷰 등록 확인 (DynamoDB + S3 연동)
- 장바구니: 상품 추가 → 수량 변경 → 삭제
- 주문: 장바구니에서 주문 진행 → 완료 확인
CLI로 기본 접속 테스트
# 계정 ID 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
WEBSITE_URL="http://shopeasy-frontend-${ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com"
# S3 웹사이트 접속 테스트
echo "=== S3 웹사이트 접속 테스트 ==="
echo "URL: ${WEBSITE_URL}"
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" ${WEBSITE_URL}
# 200이면 성공!
# index.html 내용 확인 (앞부분만)
echo ""
echo "=== index.html 내용 (앞 5줄) ==="
curl -s ${WEBSITE_URL} | head -5
# API 서버 접속 테스트
echo ""
echo "=== API 서버 접속 테스트 ==="
EC2_IP=$(curl -s -H "X-aws-ec2-metadata-token: $(curl -s -X PUT 'http://169.254.169.254/latest/api/token' -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')" http://169.254.169.254/latest/meta-data/public-ipv4)
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" http://${EC2_IP}:5000/api/products
# 200이면 API 서버 정상!
# 상품 목록 API 테스트
echo ""
echo "=== 상품 목록 API 테스트 ==="
curl -s http://${EC2_IP}:5000/api/products | python3 -m json.tool | head -20
통합 테스트는 전체 시스템의 각 구성 요소가 올바르게 연동되는지 확인하는 과정입니다.
- S3 → 브라우저: 정적 파일(HTML/CSS/JS) 서비스
- 브라우저 → EC2 API(포트 5000): API 요청 (CORS 허용 필요)
- EC2 API → RDS MySQL: 상품, 회원, 주문 데이터
- EC2 API → DynamoDB: 리뷰 데이터
- EC2 API → S3(리뷰 이미지): 리뷰 사진 업로드/조회
하나라도 연결이 끊어지면 해당 기능이 동작하지 않습니다. 각 단계를 순서대로 테스트하면 어디서 문제가 발생했는지 빠르게 파악할 수 있습니다.
트러블슈팅 가이드
자주 발생하는 문제와 해결 방법
원인: 브라우저가 S3 도메인에서 EC2 API 서버로 크로스 오리진 요청을 차단합니다. S3 웹사이트 URL(http://shopeasy-frontend-xxx.s3-website...)과 API 서버 URL(http://3.35.xxx.xxx:5000)의 도메인이 다르기 때문입니다.
해결: EC2의 API 서버에서 CORS를 허용해야 합니다.
# EC2에 SSH 접속 후
# 1. cors 패키지 확인 (이미 설치되어 있을 수 있음)
cd ~/ecommerce-app/api-server
npm list cors
# 2. 없다면 설치
npm install cors
# 3. API 서버의 .env에 CORS 설정 추가
# CORS_ORIGIN을 S3 웹사이트 URL로 설정
cat >> .env <<'EOF'
# CORS 설정 - S3 프론트엔드 도메인 허용
CORS_ORIGIN=*
EOF
# 4. API 서버 재시작
# pm2를 사용하는 경우:
pm2 restart all
# 또는 직접 실행하는 경우:
# 기존 프로세스 종료 후
node src/index.js &
학습 환경에서는 CORS_ORIGIN=*로 모든 도메인을 허용할 수 있습니다. 실무에서는 반드시 특정 도메인만 허용하세요: CORS_ORIGIN=http://shopeasy-frontend-xxx.s3-website.ap-northeast-2.amazonaws.com
원인: EC2 인스턴스(t2.micro)의 메모리(1GB)가 부족하여 Node.js 빌드 프로세스가 메모리 부족으로 실패합니다.
해결: Node.js 메모리 제한을 늘리거나 스왑 메모리를 추가합니다.
# 방법 1: Node.js 메모리 제한 늘리기
NODE_OPTIONS="--max-old-space-size=512" VITE_API_URL=http://EC2_IP:5000 npm run build
# 방법 2: 스왑 메모리 추가 (1GB)
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 스왑 추가 후 다시 빌드
VITE_API_URL=http://EC2_IP:5000 npm run build
# 빌드 완료 후 스왑 해제 (선택)
sudo swapoff /swapfile
sudo rm /swapfile
원인: 버킷 정책이 올바르게 설정되지 않았거나, 퍼블릭 액세스 차단이 해제되지 않았습니다.
해결:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 1. 퍼블릭 액세스 차단 상태 확인
echo "=== 퍼블릭 액세스 차단 설정 ==="
aws s3api get-public-access-block \
--bucket shopeasy-frontend-${ACCOUNT_ID}
# 모든 값이 false여야 합니다
# 2. 버킷 정책 확인
echo ""
echo "=== 버킷 정책 ==="
aws s3api get-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--output text | python3 -m json.tool
# Resource에 올바른 버킷 이름이 있는지 확인
# arn:aws:s3:::shopeasy-frontend-{ACCOUNT_ID}/*
# 3. 정적 웹사이트 호스팅 상태 확인
echo ""
echo "=== 정적 웹사이트 호스팅 설정 ==="
aws s3api get-bucket-website \
--bucket shopeasy-frontend-${ACCOUNT_ID}
# IndexDocument와 ErrorDocument가 모두 index.html인지 확인
# 4. index.html 파일이 존재하는지 확인
echo ""
echo "=== 파일 존재 여부 ==="
aws s3 ls s3://shopeasy-frontend-${ACCOUNT_ID}/index.html
흔한 실수들:
- 버킷 정책의 Resource에서 버킷 이름이 틀린 경우
- Resource 끝에
/*를 빠뜨린 경우 - 퍼블릭 액세스 차단 4개 항목 중 일부만 해제한 경우
- index.html 파일이 업로드되지 않은 경우
원인: S3 정적 웹사이트 호스팅에서 오류 문서(Error document)가 index.html로 설정되지 않았습니다. React SPA는 모든 라우팅을 클라이언트 사이드에서 처리하므로, /products/3 같은 URL로 직접 접속하면 S3에서 해당 파일을 찾지 못해 404 에러가 발생합니다.
해결: 정적 웹사이트 호스팅 설정에서 Error document를 index.html로 변경합니다.
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 현재 설정 확인
aws s3api get-bucket-website \
--bucket shopeasy-frontend-${ACCOUNT_ID}
# 오류 문서를 index.html로 재설정
aws s3 website s3://shopeasy-frontend-${ACCOUNT_ID}/ \
--index-document index.html \
--error-document index.html
echo "오류 문서 설정 완료!"
원인: VITE_API_URL 환경변수를 설정하지 않고 빌드했거나, 잘못된 IP/포트를 입력했습니다.
확인 방법:
# 빌드된 JS 파일에서 API URL 확인
grep -o 'http://[^"]*:5000' dist/assets/index-*.js
# 출력이 없거나 잘못된 IP가 나오면 다시 빌드해야 합니다
# 올바른 IP로 다시 빌드
VITE_API_URL=http://3.35.xxx.xxx:5000 npm run build
# 다시 S3에 업로드
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws s3 sync dist/ s3://shopeasy-frontend-${ACCOUNT_ID}/ --delete
흔한 실수들:
VITE_API_URL을 빌드 전이 아닌 빌드 후에 설정한 경우 (런타임에는 적용 안 됨!)- URL 끝에 슬래시(
/)를 넣은 경우:http://3.35.xxx.xxx:5000/(X) - 포트 번호(5000)를 빠뜨린 경우
https://로 입력한 경우 (이 실습에서는http://만 사용)- EC2 인스턴스를 중지/재시작하여 퍼블릭 IP가 변경된 경우 (Elastic IP 미사용 시)
원인: EC2 보안 그룹에서 포트 5000으로의 인바운드 트래픽이 허용되지 않았습니다.
해결: EC2 보안 그룹에 포트 5000 인바운드 규칙을 추가합니다.
# EC2 보안 그룹 확인
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=ShopEasy*" \
--query "Reservations[].Instances[].{ID:InstanceId,SG:SecurityGroups[].GroupId}" \
--output table
# 보안 그룹에 포트 5000 인바운드 규칙 추가
# (SG_ID를 실제 보안 그룹 ID로 변경)
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxxxxx \
--protocol tcp \
--port 5000 \
--cidr 0.0.0.0/0
echo "포트 5000 인바운드 규칙 추가 완료!"
또한 API 서버가 실제로 실행 중인지 확인합니다:
# EC2에 SSH 접속 후
# API 서버 프로세스 확인
pm2 list
# 또는
ps aux | grep node
# 포트 5000에서 리스닝 중인지 확인
sudo ss -tlnp | grep 5000
# API 서버가 실행 중이 아니면 시작
cd ~/ecommerce-app/api-server
node src/index.js &
# 또는
pm2 start src/index.js --name shopeasy-api
원인: EC2에 연결된 IAM 역할에 프론트엔드 S3 버킷에 대한 쓰기 권한이 없습니다.
해결: IAM 역할에 프론트엔드 버킷에 대한 S3 권한을 추가합니다.
# 현재 EC2에 연결된 IAM 역할 확인
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/info
# IAM 역할에 프론트엔드 버킷 권한 추가
# (IAM 콘솔에서 또는 CLI로)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 인라인 정책 추가 예시
cat > /tmp/frontend-deploy-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::shopeasy-frontend-${ACCOUNT_ID}",
"arn:aws:s3:::shopeasy-frontend-${ACCOUNT_ID}/*"
]
}
]
}
EOF
# 역할에 정책 추가 (역할 이름을 실제 값으로 변경)
aws iam put-role-policy \
--role-name ShopEasy-EC2-Role \
--policy-name ShopEasy-Frontend-Deploy \
--policy-document file:///tmp/frontend-deploy-policy.json
echo "IAM 정책 추가 완료! (적용까지 몇 초 소요될 수 있습니다)"
원인: 리뷰 이미지가 저장되는 S3 버킷(Chapter 05에서 설정)의 CORS 설정이 S3 프론트엔드 도메인을 허용하지 않을 수 있습니다.
해결: 리뷰 이미지 S3 버킷에 CORS 설정을 추가합니다.
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 리뷰 이미지 버킷에 CORS 설정 추가
cat > /tmp/cors-config.json <<EOF
{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": [
"http://shopeasy-frontend-${ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
EOF
# 리뷰 이미지 버킷 이름으로 변경하세요
aws s3api put-bucket-cors \
--bucket shopeasy-images-${ACCOUNT_ID} \
--cors-configuration file:///tmp/cors-config.json
echo "CORS 설정 완료!"
학생이 문제를 겪을 때 다음 순서로 디버깅하면 효율적입니다:
- 브라우저 개발자 도구(F12)를 먼저 확인
- Console 탭: JavaScript 에러, CORS 에러 확인
- Network 탭: API 요청의 상태 코드(200/403/404/500) 확인
- S3 설정 확인: 퍼블릭 액세스, 버킷 정책, 정적 웹사이트 호스팅
- API 서버 상태 확인: EC2 SSH 접속 →
pm2 list또는ps aux | grep node - EC2 보안 그룹 확인: 포트 5000 인바운드 규칙
- VITE_API_URL 확인: 빌드된 JS 파일에서 API URL 검색
- IAM 역할 권한 확인: EC2 인스턴스 프로파일의 정책
학생들에게 앞으로 프론트엔드를 업데이트할 때 필요한 3단계를 안내하세요:
# 프론트엔드 재배포 3단계
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 1단계: 코드 수정 후 빌드
cd ~/ecommerce-app/frontend
VITE_API_URL=http://EC2_PUBLIC_IP:5000 npm run build
# 2단계: S3에 업로드
aws s3 sync dist/ s3://shopeasy-frontend-${ACCOUNT_ID}/ --delete
# 3단계: 브라우저에서 확인
echo "http://shopeasy-frontend-${ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com"