🌍 글로벌 정적 웹사이트 배포

60

왜 정적 웹사이트를 S3 + CloudFront로 배포할까?

여러분이 포트폴리오 사이트나 회사 소개 페이지를 만들었다고 합시다. 서버 없이 HTML, CSS, JavaScript 파일만으로 구성된 사이트입니다.

글로벌 정적 웹사이트 아키텍처 — S3 + CloudFront CDN 구성도

이걸 배포하려면 어떻게 해야 할까요?

방법 1: EC2 인스턴스를 띄우고 Nginx를 설치한다. → 서버 관리가 필요하고, 월 $8~15 비용이 발생합니다. → 한국에서만 빠르고, 미국이나 유럽에서는 느립니다.

방법 2: S3에 파일을 올리고 CloudFront로 전 세계에 배포한다. → 서버 관리가 필요 없고, 월 $0~1로 거의 무료입니다. → 전 세계 400개 이상의 Edge Location에서 캐시되어 어디서든 빠릅니다.

당연히 방법 2가 낫겠죠? 이것이 바로 정적 웹사이트 호스팅의 정석입니다.

실제로 Netflix, Airbnb 같은 대형 서비스도 프론트엔드를 S3 + CloudFront로 배포합니다. React, Next.js, Vue.js로 빌드한 결과물을 S3에 올리고 CloudFront로 배포하는 것은 업계 표준입니다.

이 실습을 마치면 여러분은:

  • S3 버킷을 웹사이트 오리진으로 설정할 수 있습니다
  • CloudFront CDN으로 전 세계에 배포할 수 있습니다
  • OAC로 S3 직접 접근을 차단하는 보안 설정을 할 수 있습니다
  • 캐시 무효화를 통해 배포 업데이트를 할 수 있습니다

이 조합은 실무에서 가장 많이 사용되는 정적 배포 아키텍처입니다. 비용이 거의 들지 않으면서도 글로벌 성능과 보안을 확보할 수 있습니다.

실습을 시작하기 전에 AWS 콘솔에 로그인되어 있는지 확인하세요. S3와 CloudFront는 글로벌 서비스이며, ACM 인증서는 us-east-1 (버지니아 북부) 리전에서 발급해야 합니다.

아키텍처 개요

CloudFront 적용 전후 지연 시간 비교 차트

배포 흐름

다이어그램 로딩 중...
정적 웹사이트 배포 흐름도

비용 예측

비용 계산기

4시간
0h24h
50 GB
0 GB100 GB
S3 스토리지 (1GB 이하)

GB당 $0.023

$1.1500
CloudFront 데이터 전송

GB

$4.2500
Route 53 호스팅 영역

시간

$0.0028
예상 총 비용$5.4028

* 실제 비용은 AWS 요금 정책에 따라 달라질 수 있습니다.

Step 1: S3 버킷 생성 및 파일 업로드

S3(Simple Storage Service)는 AWS의 객체 스토리지 서비스입니다. 파일을 "버킷"이라는 컨테이너에 저장합니다.

여기서 중요한 점은 퍼블릭 액세스를 차단한 상태로 두는 것입니다. "정적 웹사이트인데 왜 퍼블릭을 막아?" 라고 생각할 수 있지만, CloudFront의 OAC(Origin Access Control)를 통해 CloudFront만 S3에 접근하도록 설정합니다. 이렇게 하면 S3 URL로 직접 접근하는 것을 차단하여 보안이 강화됩니다.

먼저 간단한 HTML 파일을 준비합니다. 텍스트 편집기에서 아래 내용으로 index.html을 만드세요:

코드
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Static Website</title>
  <style>
    body { font-family: 'Pretendard', sans-serif; text-align: center; padding: 50px; }
    h1 { color: #232f3e; }
    .badge { background: #ff9900; color: white; padding: 8px 16px; border-radius: 4px; }
  </style>
</head>
<body>
  <h1>CloudFront로 배포된 정적 웹사이트</h1>
  <p class="badge">S3 + CloudFront + OAC</p>
  <p>전 세계 어디서든 빠르게 로드됩니다!</p>
</body>
</html>

에러 페이지용 error.html도 만듭니다:

코드
<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>404</title></head>
<body style="text-align:center;padding:50px;">
  <h1>404 - 페이지를 찾을 수 없습니다</h1>
  <a href="/">홈으로 돌아가기</a>
</body>
</html>
  1. AWS 콘솔 상단 검색창에서 S3 검색 → 클릭
  2. 버킷 만들기 클릭
  3. 버킷 이름: lab-static-site-{고유ID} 입력
    • 예: lab-static-site-20260414 (전 세계에서 고유해야 합니다)
    • 이미 존재하는 이름이면 에러가 나므로, 날짜나 랜덤 숫자를 붙이세요
  4. AWS 리전: ap-northeast-2 (서울) 선택
  5. 모든 퍼블릭 액세스 차단: 체크된 상태 그대로 유지합니다 (이것이 핵심!)
  6. 버전 관리: 비활성화 상태 유지 (실습에서는 불필요)
  7. 버킷 만들기 클릭
  8. 생성된 버킷 클릭 → 객체 탭 → 업로드 클릭
  9. 준비한 index.htmlerror.html 파일을 드래그 앤 드롭으로 업로드
  10. 업로드 클릭 → 성공 확인
코드
# 버킷 생성
aws s3 mb s3://lab-static-site-$(date +%s) --region ap-northeast-2
 
# 파일 업로드
aws s3 cp index.html s3://lab-static-site-XXXXX/ --content-type "text/html"
aws s3 cp error.html s3://lab-static-site-XXXXX/ --content-type "text/html"
코드
resource "aws_s3_bucket" "website" {
  bucket = "lab-static-site-${random_id.suffix.hex}"
  tags   = { Name = "lab-static-site" }
}
 
resource "aws_s3_object" "index" {
  bucket       = aws_s3_bucket.website.id
  key          = "index.html"
  source       = "index.html"
  content_type = "text/html"
}
 
resource "aws_s3_object" "error" {
  bucket       = aws_s3_bucket.website.id
  key          = "error.html"
  source       = "error.html"
  content_type = "text/html"
}

S3 버킷 이름은 전 세계에서 고유해야 합니다. 자신만의 접두사나 날짜를 포함하여 충돌을 방지하세요. 예: lab-static-site-홍길동-20260414

주의: 이 시점에서 S3 URL(https://lab-static-site-xxx.s3.amazonaws.com/index.html)로 직접 접속하면 Access Denied 에러가 납니다. 이것이 정상입니다! 퍼블릭 액세스를 차단했으니까요. CloudFront를 통해서만 접근할 수 있도록 설정하겠습니다.

Step 2: CloudFront 배포 생성

CloudFront는 AWS의 CDN(Content Delivery Network) 서비스입니다. 전 세계 400개 이상의 Edge Location에 콘텐츠를 캐시하여, 사용자와 가장 가까운 위치에서 콘텐츠를 제공합니다.

한국 사용자가 접속하면 서울의 Edge Location에서, 미국 사용자가 접속하면 미국의 Edge Location에서 콘텐츠를 받습니다. 원본 S3(Origin)까지 매번 갈 필요가 없으므로 응답 속도가 매우 빨라집니다.

진행률 0/9
  1. 1AWS 콘솔에서 CloudFront 검색 → 클릭
  2. 2배포 생성 클릭
  3. 3원본(Origin) 설정: 원본 도메인: 검색창에서 위에서 만든 S3 버킷 선택 이름: 자동 입력됩니다
  4. 4원본 액세스 설정 (중요!): 원본 액세스 제어 설정(권장) 선택 제어 설정 생성 클릭 이름: lab-oac (기본값 유지해도 됩니다) 서명 동작: 항상 서명 선택 생성 클릭
  5. 5기본 캐시 동작 설정: 뷰어 프로토콜 정책: Redirect HTTP to HTTPS 선택 허용된 HTTP 메서드: GET, HEAD 선택 캐시 정책: CachingOptimized 선택 (기본값)
  6. 6설정 섹션: 기본 루트 객체: index.html 입력 (반드시 입력해야 합니다!) 가격 등급: 모든 Edge Location 사용 (기본값)
  7. 7배포 생성 클릭
  8. 8중요! 상단에 노란색 배너가 표시됩니다: "S3 버킷 정책을 업데이트해야 합니다" 정책 복사 버튼을 클릭하여 정책을 복사합니다 이 정책은 다음 단계에서 사용합니다
  9. 9배포 상태가 배포 중에서 활성화됨으로 바뀔 때까지 3~5분 기다립니다

기본 루트 객체를 빼먹으면: CloudFront 도메인으로 접속했을 때 index.html이 아닌 XML 에러 페이지가 표시됩니다. 반드시 index.html을 입력하세요.

Step 3: OAC 버킷 정책 설정

이전 단계에서 복사한 정책을 S3 버킷에 적용합니다. 이 정책은 "이 CloudFront 배포에서 오는 요청만 S3 객체 읽기를 허용한다"는 내용입니다.

  1. S3 콘솔 → 위에서 만든 버킷 클릭
  2. 권한 탭 클릭
  3. 버킷 정책 섹션 → 편집 클릭
  4. 이전 단계에서 복사한 정책을 붙여넣기합니다
  5. 변경 사항 저장 클릭
  6. 이제 CloudFront 도메인으로 접속을 테스트합니다:
    • CloudFront 콘솔 → 배포 선택 → 배포 도메인 이름 복사 (예: d111111abcdef.cloudfront.net)
    • 브라우저에서 해당 도메인으로 접속
    • "CloudFront로 배포된 정적 웹사이트" 페이지가 표시되면 성공!
  7. S3 URL로 직접 접속하면 여전히 Access Denied인지도 확인해 보세요
코드
aws s3api put-bucket-policy --bucket lab-static-site-XXXXX \
  --policy '{
    "Version":"2012-10-17","Statement":[{
      "Effect":"Allow",
      "Principal":{"Service":"cloudfront.amazonaws.com"},
      "Action":"s3:GetObject",
      "Resource":"arn:aws:s3:::lab-static-site-XXXXX/*",
      "Condition":{"StringEquals":{"AWS:SourceArn":"arn:aws:cloudfront::ACCOUNT:distribution/DIST_ID"}}
    }]
  }'
코드
resource "aws_cloudfront_origin_access_control" "oac" {
  name                              = "lab-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

Step 4: 캐시 무효화 테스트

CloudFront는 콘텐츠를 Edge Location에 캐시합니다. 따라서 S3의 파일을 업데이트해도 바로 반영되지 않습니다. 캐시가 만료될 때까지 이전 버전이 계속 표시됩니다.

이때 캐시 무효화(Invalidation)를 사용하면 강제로 캐시를 제거할 수 있습니다.

진행률 0/9
  1. 1index.html을 수정합니다. 예를 들어 제목을 "업데이트된 정적 웹사이트"로 변경
  2. 2S3 콘솔 → 버킷 → 업로드 → 수정된 index.html 업로드 (덮어쓰기됨)
  3. 3CloudFront 도메인으로 접속 → 아직 이전 콘텐츠가 표시됩니다 (캐시가 남아 있음)
  4. 4CloudFront 콘솔 → 배포 선택 → 무효화 탭 클릭
  5. 5무효화 생성 클릭
  6. 6객체 경로에 /* 입력 (모든 파일의 캐시를 제거한다는 뜻)
  7. 7무효화 생성 클릭
  8. 8상태가 완료됨이 될 때까지 1~2분 대기
  9. 9브라우저에서 CloudFront 도메인을 새로고침 → 변경된 콘텐츠가 표시되면 성공!

캐시 무효화는 월 1,000건까지 무료입니다. 배포 자동화 시 CI/CD 파이프라인에 무효화 단계를 포함하세요. /* 대신 /index.html처럼 특정 파일만 무효화하면 더 효율적입니다.

실무 팁: 매번 캐시 무효화를 하는 것보다 더 좋은 방법이 있습니다. 파일명에 해시를 포함하는 것입니다. 예: app.abc123.js 파일 내용이 바뀌면 파일명도 바뀌므로, CloudFront가 새 파일로 인식합니다. React, Next.js 등의 빌드 도구는 이 방식을 자동으로 지원합니다.

핵심 개념 확인

직접 설명해 보기

✏️

본인의 말로 설명해 보세요

CloudFront의 캐시 HIT와 MISS가 무엇인지, 그리고 각 경우에 콘텐츠가 어디서 오는지 설명해 보세요.

💡 브라우저 → Edge Location → S3(Origin)의 흐름에서, 캐시가 있을 때와 없을 때를 각각 설명해 보세요.

완성 후 테스트 가이드

배포가 완료되었다면, 다음 테스트를 통해 정상 동작을 확인하세요.

진행률 0/6
  1. 1CloudFront 접속 테스트: 브라우저에서 CloudFront 도메인(d111111abcdef.cloudfront.net)으로 접속 → 웹페이지가 정상 표시되는지 확인
  2. 2HTTPS 테스트: URL이 https://로 시작하는지 확인. http://로 접속하면 자동으로 https://로 리다이렉트되는지 확인
  3. 3S3 직접 접근 차단 테스트: S3 URL(lab-static-site-xxx.s3.amazonaws.com/index.html)로 접속 → Access Denied가 나오면 OAC 설정 성공
  4. 4캐시 확인: 브라우저 개발자 도구(F12) → Network 탭 → 페이지 새로고침 → 응답 헤더에서 X-Cache: Hit from cloudfront 확인
  5. 5에러 페이지 테스트: 존재하지 않는 경로(예: /없는페이지)로 접속 → CloudFront가 S3에서 403을 받으면 커스텀 에러 응답을 반환하는지 확인
  6. 6캐시 무효화 테스트: 파일 수정 → S3 재업로드 → 캐시 무효화 → 변경 반영 확인

트러블슈팅

CloudFront 도메인으로 접속 시 Access Denied:

  1. S3 버킷 정책에 CloudFront OAC 정책이 적용되었는지 확인
  2. CloudFront 배포에서 OAC가 제대로 설정되었는지 확인
  3. 기본 루트 객체에 index.html이 입력되어 있는지 확인
  4. 배포 상태가 활성화됨인지 확인 (배포 중이면 아직 사용 불가)

캐시 무효화 후에도 이전 콘텐츠가 표시되는 경우:

  1. 무효화 상태가 완료됨인지 확인 (진행 중이면 대기)
  2. 브라우저 캐시를 삭제하고 강력 새로고침(Ctrl+Shift+R)
  3. 무효화 경로가 올바른지 확인 (예: /* 또는 /index.html)
  4. S3에 새 파일이 정상적으로 업로드되었는지 확인

CloudFront 배포가 비활성화되지 않아 삭제할 수 없는 경우: CloudFront 배포를 삭제하려면 먼저 비활성화해야 합니다. 배포 선택 → 편집 → 비활성화 → 저장. 비활성화에 5~10분이 소요됩니다. 비활성화 완료 후에야 삭제가 가능합니다.

확장 아이디어

  1. 커스텀 도메인 연결: Route 53에 호스팅 영역을 만들고, CloudFront에 커스텀 도메인(예: www.mysite.com)을 연결해 보세요. ACM 인증서(us-east-1)가 필요합니다.
  2. 에러 페이지 커스터마이즈: CloudFront 설정에서 에러 응답을 커스텀하여, 403/404 에러 시 error.html을 보여주도록 설정해 보세요.
  3. CI/CD 파이프라인: GitHub Actions로 코드 푸시 시 자동으로 S3 업로드 + CloudFront 무효화를 실행하는 파이프라인을 구축해 보세요.
  4. Lambda@Edge: CloudFront의 Edge Location에서 실행되는 Lambda 함수로, URL 리다이렉트나 헤더 조작 등을 해 보세요.
  5. CloudFront Functions: Lambda@Edge보다 가볍고 빠른 함수로, 간단한 URL 리라이팅이나 캐시 키 조작을 해 보세요.

학습 정리

핵심 치트시트

이 프로젝트에서는 S3에 정적 파일을 호스팅하고, CloudFront CDN으로 전 세계에 배포하는 아키텍처를 구축했습니다. OAC로 S3 직접 접근을 차단하고, 캐시 무효화로 배포 업데이트를 처리합니다.

리소스 정리

실습 완료 후 반드시 아래 순서대로 리소스를 정리하여 불필요한 과금을 방지하세요. CloudFront 배포는 비활성화 후 삭제해야 하며, 비활성화에 수 분이 소요됩니다.

  1. CloudFront 배포 비활성화 (배포 편집 → 비활성화 → 저장, 5~10분 소요)
  2. 비활성화 완료 후 CloudFront 배포 삭제
  3. OAC(Origin Access Control) 삭제
  4. S3 버킷 내 객체 모두 삭제 (버킷 → 비우기)
  5. S3 버킷 삭제
  6. (Route 53 호스팅 영역, ACM 인증서는 필요 시 유지)