Chapter 06 정답

프론트엔드 S3 배포 + 통합 테스트 - 정답 및 해설

Step 1 정답: S3 버킷 생성

정답

웹 콘솔 풀이

  1. AWS 콘솔에 로그인합니다.
  2. 상단 검색창에 S3를 입력하고 S3 서비스를 선택합니다.
  3. Create bucket 버튼을 클릭합니다.
  4. General configuration:
    • Bucket name: shopeasy-frontend-{ACCOUNT_ID}
    • AWS Region: Asia Pacific (Seoul) ap-northeast-2
  5. Object Ownership:
    • ACLs disabled (recommended) 선택 (기본값)
  6. 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" 체크박스를 체크합니다.
  7. 나머지 설정은 기본값으로 두고 Create bucket을 클릭합니다.

AWS CLI 풀이

bash
# 계정 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 정답: 퍼블릭 액세스 차단 해제

정답

웹 콘솔 풀이

  1. S3 → 버킷 목록에서 shopeasy-frontend-{ACCOUNT_ID}를 클릭합니다.
  2. Permissions 탭을 클릭합니다.
  3. Block public access (bucket settings) 섹션에서 Edit을 클릭합니다.
  4. Block all public access 체크박스를 해제합니다.
  5. Save changes를 클릭합니다.
  6. 확인 창에 confirm을 입력하고 Confirm을 클릭합니다.

버킷 생성 시에 이미 해제했다면 이 단계는 건너뛰어도 됩니다.

AWS CLI 풀이

bash
# 계정 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 정답: 정적 웹사이트 호스팅 활성화

정답

웹 콘솔 풀이

  1. S3shopeasy-frontend-{ACCOUNT_ID} 버킷 클릭
  2. Properties 탭을 클릭합니다.
  3. 페이지 맨 아래로 스크롤하여 Static website hosting 섹션을 찾습니다.
  4. Edit을 클릭합니다.
  5. 설정을 다음과 같이 변경합니다:
    • Static website hosting: Enable
    • Hosting type: Host a static website
    • Index document: index.html
    • Error document: index.html
  6. Save changes를 클릭합니다.
  7. 다시 Properties 탭의 Static website hosting 섹션에서 Bucket website endpoint URL을 확인합니다.
text
# 웹사이트 엔드포인트 형식
http://shopeasy-frontend-{ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com

AWS CLI 풀이

bash
# 계정 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 정답: 버킷 정책 설정 (퍼블릭 읽기 허용)

정답

웹 콘솔 풀이

  1. S3shopeasy-frontend-{ACCOUNT_ID} 버킷 클릭
  2. Permissions 탭을 클릭합니다.
  3. Bucket policy 섹션에서 Edit을 클릭합니다.
  4. 아래 JSON 정책을 붙여넣습니다 ({ACCOUNT_ID}를 본인의 계정 ID로 변경):
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::shopeasy-frontend-{ACCOUNT_ID}/*"
    }
  ]
}
  1. Save changes를 클릭합니다.
  2. Permissions 탭에서 버킷이 "Publicly accessible"로 표시되는 것을 확인합니다.

AWS CLI 풀이

bash
# 계정 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 접속 후 빌드

  1. 먼저 EC2 퍼블릭 IP를 확인합니다:
    AWS 콘솔 → EC2 → Instances → ShopEasy 인스턴스의 Public IPv4 address 확인
  2. SSH로 EC2에 접속합니다:
bash
# EC2에 SSH 접속
ssh -i "ShopEasy-Key.pem" ec2-user@3.35.xxx.xxx
  1. 프론트엔드 디렉토리에서 빌드합니다:
bash
# 프론트엔드 디렉토리로 이동
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

전체 빌드 명령어

bash
# 프론트엔드 디렉토리로 이동
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 사용을 강력히 권장합니다.

  1. S3shopeasy-frontend-{ACCOUNT_ID} 버킷 클릭
  2. Upload 버튼 클릭
  3. Add files 또는 Add folderdist/ 폴더의 내용물을 업로드
  4. 주의: 폴더 구조(assets/ 등)가 유지되어야 합니다
  5. Upload 클릭

그러나 실무에서는 항상 CLI를 사용합니다. 아래 CLI 풀이를 참고하세요.

AWS CLI 풀이

bash
# 계정 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

이후 업데이트 시 (재배포)

bash
# 코드 수정 후 다시 빌드
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로 접속 테스트

정답

브라우저에서 접속

  1. 웹 브라우저를 열고 S3 웹사이트 엔드포인트 URL로 접속합니다:
text
http://shopeasy-frontend-{ACCOUNT_ID}.s3-website.ap-northeast-2.amazonaws.com
  1. ShopEasy 메인 페이지가 정상적으로 로드되는지 확인합니다.
  2. 브라우저 개발자 도구(F12)를 열어 Console 탭에서 에러가 없는지 확인합니다.
  3. Network 탭에서 API 요청이 정상적으로 처리되는지 확인합니다.

통합 테스트 순서

  1. 회원가입: 새 계정 생성 → 성공 메시지 확인
  2. 로그인: 생성한 계정으로 로그인 → 사용자 이름 표시 확인
  3. 상품 목록: 메인 페이지에서 상품이 로드되는지 확인 (RDS 연동)
  4. 상품 상세: 상품 클릭 → 상세 정보 확인
  5. 리뷰 작성: 별점 + 텍스트 + 사진 → 리뷰 등록 확인 (DynamoDB + S3 연동)
  6. 장바구니: 상품 추가 → 수량 변경 → 삭제
  7. 주문: 장바구니에서 주문 진행 → 완료 확인

CLI로 기본 접속 테스트

bash
# 계정 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(리뷰 이미지): 리뷰 사진 업로드/조회

하나라도 연결이 끊어지면 해당 기능이 동작하지 않습니다. 각 단계를 순서대로 테스트하면 어디서 문제가 발생했는지 빠르게 파악할 수 있습니다.

트러블슈팅 가이드

자주 발생하는 문제와 해결 방법

문제 1: CORS 에러 - "Access to XMLHttpRequest has been blocked by CORS policy"

원인: 브라우저가 S3 도메인에서 EC2 API 서버로 크로스 오리진 요청을 차단합니다. S3 웹사이트 URL(http://shopeasy-frontend-xxx.s3-website...)과 API 서버 URL(http://3.35.xxx.xxx:5000)의 도메인이 다르기 때문입니다.

해결: EC2의 API 서버에서 CORS를 허용해야 합니다.

bash
# 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=*로 모든 도메인을 허용할 수 있습니다. 실무에서는 반드시 특정 도메인만 허용하세요: CORS_ORIGIN=http://shopeasy-frontend-xxx.s3-website.ap-northeast-2.amazonaws.com

문제 2: npm run build 실패 - "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory"

원인: EC2 인스턴스(t2.micro)의 메모리(1GB)가 부족하여 Node.js 빌드 프로세스가 메모리 부족으로 실패합니다.

해결: Node.js 메모리 제한을 늘리거나 스왑 메모리를 추가합니다.

bash
# 방법 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
문제 3: S3 웹사이트 접속 시 403 Forbidden 에러

원인: 버킷 정책이 올바르게 설정되지 않았거나, 퍼블릭 액세스 차단이 해제되지 않았습니다.

해결:

bash
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 파일이 업로드되지 않은 경우
문제 4: 직접 URL 입력 시 404 에러 또는 빈 페이지

원인: S3 정적 웹사이트 호스팅에서 오류 문서(Error document)가 index.html로 설정되지 않았습니다. React SPA는 모든 라우팅을 클라이언트 사이드에서 처리하므로, /products/3 같은 URL로 직접 접속하면 S3에서 해당 파일을 찾지 못해 404 에러가 발생합니다.

해결: 정적 웹사이트 호스팅 설정에서 Error document를 index.html로 변경합니다.

bash
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 "오류 문서 설정 완료!"
문제 5: 페이지는 로드되지만 데이터가 표시되지 않음 (API 연결 실패)

원인: VITE_API_URL 환경변수를 설정하지 않고 빌드했거나, 잘못된 IP/포트를 입력했습니다.

확인 방법:

bash
# 빌드된 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 미사용 시)
문제 6: API 요청이 타임아웃됨 (응답 없음)

원인: EC2 보안 그룹에서 포트 5000으로의 인바운드 트래픽이 허용되지 않았습니다.

해결: EC2 보안 그룹에 포트 5000 인바운드 규칙을 추가합니다.

bash
# 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 서버가 실제로 실행 중인지 확인합니다:

bash
# 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
문제 7: aws s3 sync 명령어 실행 시 AccessDenied 에러

원인: EC2에 연결된 IAM 역할에 프론트엔드 S3 버킷에 대한 쓰기 권한이 없습니다.

해결: IAM 역할에 프론트엔드 버킷에 대한 S3 권한을 추가합니다.

bash
# 현재 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 정책 추가 완료! (적용까지 몇 초 소요될 수 있습니다)"
문제 8: 리뷰는 작성되지만 사진이 표시되지 않음

원인: 리뷰 이미지가 저장되는 S3 버킷(Chapter 05에서 설정)의 CORS 설정이 S3 프론트엔드 도메인을 허용하지 않을 수 있습니다.

해결: 리뷰 이미지 S3 버킷에 CORS 설정을 추가합니다.

bash
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 설정 완료!"
강사 참고: 디버깅 순서

학생이 문제를 겪을 때 다음 순서로 디버깅하면 효율적입니다:

  1. 브라우저 개발자 도구(F12)를 먼저 확인
    • Console 탭: JavaScript 에러, CORS 에러 확인
    • Network 탭: API 요청의 상태 코드(200/403/404/500) 확인
  2. S3 설정 확인: 퍼블릭 액세스, 버킷 정책, 정적 웹사이트 호스팅
  3. API 서버 상태 확인: EC2 SSH 접속 → pm2 list 또는 ps aux | grep node
  4. EC2 보안 그룹 확인: 포트 5000 인바운드 규칙
  5. VITE_API_URL 확인: 빌드된 JS 파일에서 API URL 검색
  6. IAM 역할 권한 확인: EC2 인스턴스 프로파일의 정책
강사 참고: 전체 배포 프로세스 요약

학생들에게 앞으로 프론트엔드를 업데이트할 때 필요한 3단계를 안내하세요:

bash
# 프론트엔드 재배포 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"