
Project 1: Static Website Hosting with Terraform (S3 + CloudFront + WAF)
Terraform으로 구축한 정적 웹사이트 호스팅 인프라입니다. S3에 비공개로 저장된 정적 파일을 CloudFront(CDN)를 통해 전 세계에 배포하고, WAF로 악성 트래픽을 차단하며, CloudWatch + SNS로 실시간 모니터링 및 이메일 알림을 제공합니다.
아키텍처
[User]
│
▼
[AWS WAF] ──── 악성 IP / 과도한 요청 차단
│ (Rate Limit, AWS Managed Rule Sets)
▼
[CloudFront (CDN)] ──── 글로벌 캐싱, HTTPS 강제 리디렉션
│
▼ (캐시 미스 시)
[S3 Bucket] ──── 정적 파일 저장 (Private, OAC로만 접근 허용)
[모니터링 / 알림 흐름]
CloudWatch (4xx/5xx 에러율, WAF 차단 건수)
│
▼
SNS Topic ──── 이메일 알림
주요 구성 요소
| 리소스 | 설명 |
|---|---|
| S3 Bucket | 정적 파일 저장소. 퍼블릭 액세스 전체 차단, 버저닝 활성화, AES256 서버 측 암호화 |
| CloudFront Distribution | OAC(Origin Access Control)로 S3에 안전하게 접근. PriceClass_All, HTTPS 강제(redirect-to-https), SPA 대응 커스텀 에러 응답(403/404 → index.html) |
| WAF Web ACL (Scope: CLOUDFRONT) | AWS 관리형 규칙(AWSManagedRulesCommonRuleSet, AWSManagedRulesAmazonIpReputationList) + Rate-based Rule(5분간 IP당 2,000건 초과 시 차단) |
| CloudWatch Alarms | CloudFront 4xx 에러율 5% 초과(2회 연속), 5xx 에러율 1% 초과, WAF 차단 5분간 100건 초과 시 알람 |
| CloudWatch Dashboard | 요청 수, 에러율, 캐시 히트율 등을 한눈에 확인 |
| SNS Topic | 알람 발생 시 등록된 이메일로 알림 발송 |
| AWS Budget | 월 $10 초과 시 이메일 알림 (테스트 중 과금 사고 방지) |
사전 준비
- AWS 계정
- IAM 사용자 (
terraform-admin,AdministratorAccess정책 부여, Access Key 발급 — CLI 전용, 콘솔 로그인 권한 불필요)- 1단계: IAM 사용자 생성
- 2단계: AdministratorAccess 권한 부여
- 3단계: 액세스 키(Access Key) 발급 및 저장
※ 화면에 비밀 액세스 키는 따로 적어두거나 csv 파일을 다운로드 해 둘 것 재확인 불가

-
AWS CLI v2
linux의 경우
brew install awscli aws --versionWindow의 경우
winget install Amazon.AWSCLI aws --version -
Terraform >= 1.x
linux의 경우
# 1. HashiCorp 저장소 추가 brew tap hashicorp/tap # 2. 테라폼 설치 brew install hashicorp/tap/terraform terraform --versionWindow의 경우
winget install Hashicorp.Terraform terraform --version
-
설치 진행 시 주의사항
- Windows의 winget 명령어 안내: winget 명령어를 처음 쓰시면 중간에 *‘소스 약관에 동의하십니까?’*라는 질문(Y/N)이 뜰 수 있습니다. 이때 Y를 입력하고 엔터를 누르시면 진행됩니다.
- 버전 확인 에러 발생 시: 설치 명령어가 끝난 후 바로 aws –version이나 terraform –version을 입력했을 때 *‘명령어를 찾을 수 없다’*고 나온다면, 프로그램이 실행 중인 터미널(또는 PowerShell) 창을 완전히 닫고 새로 열어서 다시 입력해 보세요. 환경 변수가 새로고침되어 정상 작동합니다.
※ Vscode의 경우 아예 창을 전부 껐다가 켜야함.

에러 발생 시 해결 방법
※ 아래 사진의 에러 메시지 마지막 줄을 보면 winget 소스에는 패키지가 잘 살아있다고 나옵니다. 문제가 터진 msstore를 거치지 않고, winget 저장소를 직접 지정해서 설치하면 허무할 정도로 잘 해결됩니다. 파워쉘에 아래 명령어를 복사해서 실행해 보세요.
# 원본(--source) 지정하기
winget install Hashicorp.Terraform --source winget

위의 방법으로도 해결이 되지 않는 경우
# winget 소스 리셋하기
# 그래도 되지 않을 경우 네트워크 점검 필요
winget source reset
winget install Hashicorp.Terraform
예산 설정 (실수로 인한 과금 방지)
클라우드를 처음 쓸 때 가장 두려운 게 바로 ’요금 폭탄’이죠. 테라폼 코드를 잘못 작성해서 리소스가 무한 생성되더라도, 이 예산 알람을 걸어두면 $10가 넘는 순간 바로 이메일이 날아와서 피해를 최소화할 수 있습니다.
1단계: AWS 예산(Budgets) 메뉴 들어가기

2단계: 예산 유형 및 금액 설정
- 예산 유형 선택:
- 예산 세부 정보 입력:
- 예산 이름: My-First-Budget
- 기간: 월별(Monthly)
- 예산 적용 시작 월: 이번 달로 두고, 갱신 유형은 되풀이 예산(Recurring budget)
- 예산 방법: 고정(Fixed)
- 금액 설정 -> 예산 금액: 10 입력 (단위는 USD)

3단계: 임계값 및 이메일 알람 설정

4단계: 최종 검토 및 생성

배포 가이드
1. AWS CLI 자격증명 등록
aws configure
# AWS Access Key ID: <발급받은 Access Key ID>
# AWS Secret Access Key: <발급받은 Secret Access Key>
# Default region name: ap-northeast-2
# Default output format: json
연결 확인:
aws sts get-caller-identity
정상 결과:
{
"UserId": "AIDAXXXXXXXXXXXXXXXXX",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/terraform-admin"
}
2. 변수 설정 (variables.tf)

| 변수 | 설명 | 예시 |
|---|---|---|
bucket_name |
전 세계에서 고유해야 하는 S3 버킷 이름 | p1-static-web-<name>-<date> |
alert_email |
CloudWatch 알람을 받을 이메일 주소 | your-email@example.com |
3. Terraform 배포
먼저 테라폼 코드 파일(main.tf, variables.tf 등)이 모여 있는 폴더로 이동해야 합니다.
cd project1-static-web
terraform init # Provider 플러그인 다운로드 (최초 1회)
terraform plan # 생성될 리소스 미리보기 (Plan: 14 to add, 0 to change, 0 to destroy)
terraform apply # 실제 배포, 확인 프롬프트에 'yes' 입력
CloudFront 배포는 전 세계에 전파되는 데 약 5~10분 정도 소요됩니다.
4. 웹사이트 파일 업로드
# terraform apply 결과의 upload_command 출력값을 그대로 사용
aws s3 sync ./website/ s3://<bucket_name>/ --delete
5. 접속 확인
https://<cloudfront_distribution_id>.cloudfront.net
6. 콘텐츠 수정 후 캐시 무효화
웹사이트 파일을 수정하고 다시 sync했는데도 변경 사항이 반영되지 않는다면, CloudFront가 이전 버전을 캐싱하고 있기 때문입니다. 아래 명령어로 전체 캐시를 강제 갱신합니다.
aws cloudfront create-invalidation --distribution-id <DISTRIBUTION_ID> --paths '/*'
Terraform Outputs
| Output | 설명 |
|---|---|
cloudfront_domain |
사이트 접속 URL |
s3_bucket_name |
생성된 S3 버킷 이름 |
cloudfront_distribution_id |
캐시 무효화 시 필요한 배포 ID |
upload_command |
S3 업로드용 명령어 (버킷 이름 포함, 자동 생성) |
cache_invalidation_command |
캐시 무효화 명령어 |
cloudwatch_dashboard_url |
모니터링 대시보드 링크 |
waf_web_acl_arn |
WAF Web ACL ARN |
sns_topic_arn |
알림용 SNS 토픽 ARN |
모니터링 / 알림 설정
- 배포 후 알림 수신 이메일로 오는 SNS Subscription Confirmation 메일의 링크를 클릭해야 알림이 활성화됩니다.
- CloudWatch 알람 조건 요약
| 알람 | 조건 |
|---|---|
cloudfront_4xx_errors |
4xx 에러율 5% 초과 (5분 주기, 2회 연속) |
cloudfront_5xx_errors |
5xx 에러율 1% 초과 (5분 주기, 1회) |
waf_blocked_requests |
5분간 WAF 차단 요청 100건 초과 |
트러블슈팅 노트
1. CloudFront / WAF는 글로벌 리소스 → us-east-1 필수
CloudFront와 WAF(Scope: CLOUDFRONT)에 연결되는 CloudWatch 지표·알람은 반드시 us-east-1(버지니아 북부) 리전에 생성되어야 합니다. 이때 알람이 호출하는 SNS 토픽도 같은 리전(us-east-1)에 존재해야 하며, 서울 리전(ap-northeast-2)의 SNS 토픽을 참조하면 리전 불일치로 배포가 실패합니다.

2. WAF 관리형 규칙 이름 매칭
AWSManagedRulesAmazonIpReputationList 등 AWS 관리형 규칙 그룹을 사용할 때는 statement.managed_rule_group_statement의 name과 vendor_name이 AWS가 제공하는 정확한 명칭과 일치해야 합니다.

수정 전:

수정 후:

3. Git: .terraform 폴더는 절대 커밋하지 않기
.terraform/ 폴더에는 100MB를 초과하는 Provider 바이너리(예: terraform-provider-aws_*.exe, 약 685MB)가 포함될 수 있어 GitHub push가 거부됩니다. .gitignore에 다음을 반드시 추가하세요.
.terraform/
*.tfstate
*.tfstate.*
.terraform.lock.hcl
이미 커밋 기록에 포함된 경우 git filter-branch (또는 git filter-repo)로 과거 히스토리에서 제거해야 push가 가능합니다.
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .terraform/providers/registry.terraform.io/hashicorp/aws/5.100.0/windows_amd64/terraform-provider-aws_*.exe" \
--prune-empty --tag-name-filter cat -- --all
4. 다른 PC에서 terraform apply 실행 시
.terraform/ 폴더와 .tfstate 파일은 Git에 포함하지 않으므로, 새 환경에서 클론한 직후에는 반드시 terraform init을 먼저 실행해 Provider를 다시 받아야 정상 동작합니다.
인프라 삭제 (비용 정리)
terraform destroy
S3 버킷에 파일이 남아있으면 AWS 정책상 삭제가 거부될 수 있습니다. 이 경우 버킷을 먼저 비운 뒤 다시 시도합니다.
aws s3 rm s3://<bucket_name> --recursive
terraform destroy
비용 관리
- AWS Budgets로 월 $10 한도 설정, 100% 도달 시 이메일 알림
- 테스트/실습 종료 후에는
terraform destroy로 리소스를 정리해 추가 과금을 방지
기술 스택
Terraform · AWS S3 · AWS CloudFront · AWS WAF · AWS CloudWatch · AWS SNS · AWS Budgets · AWS IAM