2026-05-19 학습 노트 — CloudFront, OAC, CDN
정적 파일의 관리
정적 mp3 하나 호스팅하는데 왜 이렇게 길게 고민했나
ghworld 에 환경음 mp3 를 붙이는 단계에서, “이 파일 어디에 두지” 하나로 한참을 고민했다
처음에는 “그냥 S3 에 올리고 public 으로 풀면 끝 아닌가” 싶었다
근데 막상 결정을 하려고 하니 찝찝함이 남았다
S3 URL 을 그대로 프론트엔드에 박는 게 정말 괜찮은가?
누가 막 호출하면 비용은 어떻게 되나?
한국 밖에서 접속하면 느리지 않나?
CloudFront 를 끼면 뭐가 바뀌나? OAC 는 또 뭐고?
질문이 끝없이 나왔다
이번에는 그 질문에 답을 찾아가며 정리한 기록이다
출발점 — 운영 환경 환경음이 무음이었다
처음 발견한 건 사소한 증상이었다
로컬에서는 환경음이 잘 들리는데 운영 환경에서는 무음이었다
원인은 단순했다
mp3 파일이 .gitignore에 걸려 git 에서 추적되지 않았고, Docker 이미지에도 포함되지 않았던 것
해결을 위해서는 두 가지 방법이 있었다
- mp3 를 git 에 포함시켜 이미지 빌드에 포함시킨다
- 정적 파일 호스팅을 별도 인프라(S3 / CDN) 로 분리한다
정적 파일이 적다면 1 번도 나쁘지 않다
근데 정적 파일은 환경음뿐 아니라 캐릭터, 아이템 등의 에셋 파일도 필요해질 예정이다
”정적 파일은 정적 파일 인프라에 둔다” 라는 선을 지금 그어두는 게 맞다고 판단했다
그래서 자연스럽게 다음 질문으로 넘어갔다
”정적 파일을 어떻게 관리해야할까?“
1 차 후보 — S3 만 public 으로 노출
가장 빠른 방법이다
S3 버킷을 public read 로 풀고, 프론트엔드에서 https://***.amazonaws.com/sounds/village-night.mp3 같은 URL 을 직접 호출한다
장점은 명확하다
- 설정이 단순하다
- 추가 비용 없음
근데 단점이 있었다
단점 1. 한국 밖 사용자에게 느리다
S3 버킷은 하나의 리전에 존재한다 (ap-northeast-2 서울)
일본, 미국에서 접속하면 매번 서울까지 패킷이 다녀와야 한다
정적 파일이 로드하는데 오래 걸리면 사용자 사용성이 많이 나빠진다
단점 2. origin 부하 직격
매 요청이 S3 에 그대로 닿는다
유저 1 만 명이 마을에 들어와서 환경음을 동시에 받으면, S3 가 1 만 번 동일한 mp3 를 내보낸다
캐싱이 되지 않는 구조다
단점 3. 비용 폭주 위험 — 제일 심각한 문제이다
S3 egress 가 한국 리전 기준 대략 $0.126/GB다
누가 악의적으로 mp3 URL 을 curl 루프로 박으면 (또는 그냥 봇이 긁어가면) 비용이 걷잡을 수 없이 커진다
“public 정적 파일이라 어차피 공개인데?”라고 넘기기엔 문제가 있었다
S3 는 “이 IP 가 좀 많이 가져가니까 잠깐 막을게” 같은 게 없다
오는 요청은 다 받고, 다 청구한다
2 차 후보 — CloudFront 를 앞단에 끼우기
여기서 CDN 이 왜 정적 파일 배포의 자연스러운 선택지처럼 여겨지는지 어느 정도 와닿았다
CDN — 정적 파일을 전 세계 edge 에 흩뿌린다
CloudFront 는 전 세계 edge location 에 정적 파일 사본을 둔다
사용자가 정적 파일을 요청하면, 가장 가까운 edge 가 응답한다
흐름은 이렇다
[ 사용자 ] → [ 가까운 edge ] ──(캐시 HIT)──> 200 응답 (origin 안 거침)
│
└─(캐시 MISS)──> [ S3 origin ] → edge 캐시에 저장 → 200 응답
첫 호출만 origin (S3) 까지 다녀오고, 이후로는 edge 에서 끝난다
캐시 TTL 을 24 시간으로 잡으면, 그 24 시간 동안 같은 edge 에서 발생하는 요청은 S3 를 거치지 않는다
결과적으로 origin 부하가 99.99% 가까이 줄어든다
edge 캐시 hit 비율이 높을수록 egress 비용이 낮아진다
CloudFront 응답 헤더에 X-Cache: Hit from cloudfront / Miss from cloudfront가 박힌다
캐시는 무조건 좋은가 — TTL 트레이드오프
CloudFront 의 CachingOptimized managed policy 를 쓰면 기본 TTL 이 하루다
mp3 같이 잘 안 바뀌는 정적 파일은 적절하다고 판단했다
근데 정적 파일명을 그대로 두고 내용만 갈아끼우면 24 시간 동안 edge 에는 옛날 mp3 가 남아있다
정적 파일을 수정할 때는 파일명을 바꾸거나 (village-night-v2.mp3), CloudFront invalidation 을 호출해야 한다
이걸 모르면 “왜 새 파일이 안 보이지?” 로 한참 헤맬 수 있다
3 차 — OAC 까지 더하면 뭐가 달라지나
여기까지만 봐도 CloudFront 의 가치는 충분해 보였는데
GPT 와 Gemini 한테 같이 검토를 시켜보니 Gemini 가 이런 말을 했다
S3 를 public 으로 두면 무단 데이터 접근 위험이 있습니다
OAC(Origin Access Control) 로 막아야 합니다
처음엔 “음 그렇겠지” 했는데, 다시 보니까 우리 컨텍스트에서는 좀 어긋난 말이었다
우리 정적 파일은 어차피 public 으로 모두에게 노출하려고 하는 파일이다
”무단 접근” 이라는 표현이 우리 케이스에는 안 맞는다
그럼 OAC 는 왜 끼우나?
OAC 가 실제로 하는 일
OAC 는 CloudFront 가 S3 origin 을 호출할 때 AWS SigV4 서명을 붙여준다
S3 Bucket Policy 는 “이 서명이 우리 CloudFront 에서 온 게 맞는지” 확인되면 통과시킨다
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::***/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::***:distribution/E***"
}
}
}]
}
결과적으로
https://***.cloudfront.net/sounds/village-night.mp3← 됨https://***.amazonaws.com/sounds/village-night.mp3← 403
같은 mp3 가 같은 S3 에 있는데, 직접 S3 URL 로는 못 가져온다
그래서?
“URL 이 노출된다 = 접근할 수 있다” 가 아니다
S3 URL 이 어딘가에 새어나가도, 그 URL 로는 401/403 만 응답한다
누가 강제로 origin 부하를 일으키려고 해도 진입점이 CloudFront 하나로 좁혀진다
public_assets이라도 진입점을 좁혀두면 정책 적용이 쉽다
- WAF 룰을 CloudFront 에만 붙이면 끝
- 캐시 정책도 CloudFront 만 보면 끝
- 비용 alert 도 CloudFront 메트릭 하나만 보면 끝
“우회로가 없다” 는 게 별 거 아닌 것 같아도 운영에 들어가면 차이가 크다
그래서 DDoS 도 막아주나?
여기서 한 가지 헷갈리기 쉬운 부분이 있다
CloudFront 는 DDoS 를 완전히 차단해주지 않는다
AWS Shield Standard 가 막는 건 Layer 3-4 공격이다
SYN flood, UDP reflection 같은 류
- Layer 3-4 (네트워크 / 전송 계층) — Shield Standard 무료로 어느 정도 커버
- Layer 7 (HTTP flood, 봇이 정상 형태로 무한 요청) — Shield Standard 로는 못 막는다
- WAF rate-limit 룰을 별도로 붙이거나
- Shield Advanced ($3,000/월~) 를 결합해야 한다
다만 Layer 7 공격이 와도 CloudFront 의 트래픽 분산 자체가 origin 보호에 효과가 있다
edge 가 1 차 흡수해주는 구조라 origin 까지 다 도달하지는 않는다
CloudFront 만으로는 “원천 차단” 까진 아니지만, 무방비 S3 보다는 압도적으로 낫다 정도이다
| 위협 | S3 만 | CloudFront + OAC |
|---|---|---|
| 일반 트래픽 (정상 사용자) | 매번 S3 부하 | edge 캐시 흡수 |
| Layer 3-4 DDoS (SYN flood 등) | 무방비 | Shield Standard 1 차 차단 |
| Layer 7 DDoS (HTTP flood) | 무방비 + 비용 폭주 | edge 분산 |
| 비용 통제 | 어렵다 (egress 직접) | Budget alert + WAF rate-limit 으로 가드 |
| URL 노출 시 직접 origin 접근 | 가능 | 차단 (403) |
한국 사용자 latency 는 정말 빨라지나?
“CDN 을 중간에 끼우면 다 빨라진다” 라고 단순하게 외우고 있었는데, 실제로는 한 가지 의외의 포인트가 있다
| 케이스 | 대략 latency |
|---|---|
| S3 직접 호출 | 30~50ms |
| CloudFront 첫 호출 (cache MISS) | 40~60ms |
| CloudFront 2 번째 이후 (cache HIT) | 5~15ms |
cache MISS 일 때는 S3 직접 호출보다 살짝 더 느릴 수 있다
edge → S3 한 단계가 더 끼기 때문이다
근데 한 번 캐시 처리가 되면 그 다음부터는 빠르다
Gemini 의 일반론을 그대로 받지 않은 이유
이번에 한 번 더 느꼈는데, AI 가 주는 보안 권고는 “일반적으로 맞는 말” 이긴 한데 내 컨텍스트가 그 일반론에 해당하는지 는 내가 판단해야 한다
Gemini 의 “무단 데이터 접근 위험” 은
- 사내 문서, 개인정보, 결제 정보 같은 비공개 정적 파일을 다룰 때는 맞는 말이다
- 근데 우리는 모두에게 들려주려고 만든 환경음 mp3 다
- 우리 컨텍스트의 진짜 위협은 “무단 접근” 이 아니라 “비용 폭주” 다
같은 “S3 를 CloudFront 뒤로 숨기자” 라는 결론이라도, 왜 숨기는가 의 답이 다르다
- Gemini - 파일이 새는 걸 막기 위해
- 우리 케이스 - 정적 파일은 어차피 공개지만, 진입점을 좁혀 비용·운영 통제권을 확보하기 위해
AI 의 권고를 그대로 박아넣기보다 “우리한테 그 권고가 왜 유효한가” 를 한 번 더 따져보는 습관이 필요하다
마이그레이션 절차
S3 만 쓰던 상태에서 CloudFront + OAC 로 옮길 때, 순서가 꼬이면 갑자기 정적 파일이 안 보이는 사고가 난다
순서는 이렇다
- CloudFront distribution 을 먼저 생성한다
- origin = S3 버킷
- 이 시점에는 Bucket Policy 를 public 그대로 둔다
- 프론트엔드 코드의 정적 파일 base URL 을 CloudFront 도메인으로 교체한다
- 예:
NEXT_PUBLIC_ASSETS_BASE_URL환경변수
- 예:
- 운영 배포 후, 모든 정적 파일이 CloudFront 경유로 잘 뜨는지 확인한다
X-Cache: Hit from cloudfront헤더 확인
- 마지막에 Bucket Policy 를 OAC 전용으로 교체한다
- 이 시점부터 S3 URL 직접 호출은 403 이 된다
3 번을 건너뛰고 4 번을 먼저 하면, 만약 코드가 어딘가에서 아직 S3 URL 을 직접 부르고 있을 때 그 부분이 깨진다
이번 선택
- 정적 파일은 S3 에 둔다
- 앞단에 CloudFront 를 끼운다 (캐시 + 진입점 단일화)
- OAC 로 S3 직접 접근을 막는다
mp3 파일을 어떻게 관리할 지 생각하다가, “정적 파일 인프라” 자체를 어떻게 관리할 지 고민할 수 있었다
앞으로 에셋들은 S3 에서 관리할 것이다
그런데 CloudFront 가 늘 정답은 아니다
이번 프로젝트는
- 이유는 모르겠지만 CloudFlare 집계에서 해외 접속이 집계되었고(봇일 가능성이 높긴 하다)
- 정적 파일 수가 점점 늘어날 예정이며
- 비용 통제권을 일찍 확보하고 싶었다
위 조건이라 CloudFront 가 답이 되었다
만약
- 정적 파일이 2~3 개로 끝나고 추가될 일이 없고
- 모든 사용자가 한 리전 안에서만 쓰고
- 트래픽 자체가 무시할 만한 규모면
S3 만 public 으로 노출하는 게 더 단순한 선택일 수 있다
운영 비용 (CloudFront 도메인, 인증서, invalidation 관리) 이 정적 파일 규모를 넘어선다
“CDN 은 무조건 좋다” 가 아니라 “CDN 을 활용할 때” 를 보는 시야가 필요하다
다음에 더 볼 것
- CloudFront signed URL / signed cookie
- Cache-Control 헤더와 CloudFront 캐시 정책의 우선순위
- Lambda@Edge / CloudFront Functions
- AWS WAF rate-limit 룰
- HTTP/3 (QUIC)
- Cloudflare 와의 비교
종합 회고
처음에는 “mp3 하나 어디 둘지” 의 고민이었는데, 한 번 파보니 CDN 의 본질 — 캐시, edge 분산, 진입점 단일화, 비용 통제 — 까지 줄줄이 연결됐다
그리고 한 가지 큰 교훈
AI 가 주는 보안 권고를 그대로 받지 말고
”우리 컨텍스트에서 그 권고가 왜 유효한가” 를 한 번 더 묻자
“S3 를 CloudFront 뒤로 숨기자” 라는 같은 결론이지만, 우리한테 진짜 위협은 무단 접근이 아니라 비용 폭주였다
이 차이를 알고 결정하는 것과 모르고 결정하는 것은 다르다고 생각한다