39장. CloudFront의 Origin · 캐시 · OAC
이 장에서 말하고자 하는 것
앞 장에서 우리는 CloudFront가 무엇이고
왜 쓰는지를 봤다.
이 장은 CloudFront를 실제로 운영할 때 가장 자주 다루는 세 가지를 본다.
- Origin — 어디서 데이터를 가져올 것인가
- Cache Behavior — 어떤 경로를 어떻게 다룰 것인가
- OAC (Origin Access Control) — S3 같은 origin을 어떻게 보호할 것인가
1. 한 배포 안에서 여러 Origin 다루기
CloudFront 배포는 origin을 여러 개 가질 수 있다.
배포 example.com
├─ origin "s3-static" → S3 (정적 파일)
├─ origin "api-alb" → ALB (동적 API)
└─ origin "img-bucket" → S3 (이미지)
각 origin은 단순한 “어디서 가져올 것인가” 의 선언이다.
어떤 경로를 어떤 origin으로 보낼지는
다음에 나오는 Cache Behavior가 결정한다.
2. Cache Behavior — 경로별 동작
Cache Behavior는
어떤 path가 어떤 origin으로 가고
어떻게 캐시할지
를 결정한다.
/static/* → s3-static 캐시: 길게
/img/* → img-bucket 캐시: 길게
/api/* → api-alb 캐시: 없음
default → s3-static 캐시: 중간
규칙은 ALB처럼 우선순위 순으로 평가되며
가장 먼저 매칭된 behavior가 적용된다.
3. Cache Key — 무엇이 다르면 다른 캐시인가
CloudFront는 같은 응답이라도
“어떤 요청이 같은 요청인가”
를 정해야 캐시할 수 있다.
이 기준이 Cache Key 다.
기본적으로 다음이 키에 들어간다.
- URL 경로
- 쿼리 스트링 (선택적으로)
- 헤더 (선택적으로)
- 쿠키 (선택적으로)
GET /products?id=1 → 캐시 키 A
GET /products?id=2 → 캐시 키 B (다른 응답으로 분리)
키에 들어가는 항목이 많을수록
캐시가 잘게 쪼개져 적중률이 떨어진다.
응답을 진짜로 바꾸는 항목만 키에 포함한다
4. TTL — 얼마나 캐시할 것인가
TTL은 세 가지가 함께 작용한다.
Min TTL : 최소 캐시 시간
Default TTL: 응답에 Cache-Control 이 없을 때
Max TTL : Cache-Control 이 있어도 이 이상 안 캐시
origin이 응답에 다음을 넣으면 그게 우선이다.
Cache-Control: public, max-age=3600
이러면 1시간 캐시.
운영에서는 origin의 응답 헤더로 TTL을 통제하는 게 깔끔하다
5. 캐시 무효화 (Invalidation)
배포 후 즉시 새 파일을 보여주고 싶을 때 쓴다.
aws cloudfront create-invalidation \
--distribution-id E123ABCD \
--paths "/index.html" "/static/*"
다만 운영에서는 invalidation에 의존하는 대신
app.js → app.a3f2c.js
style.css → style.b91e8.css
처럼 파일명에 해시를 박는 방식이 표준이다.
새 배포는 새 파일이라 캐시가 무관해진다
6. OAC — S3를 안전하게 노출하기
S3를 정적 파일 origin으로 쓸 때 가장 흔한 실수는
S3 버킷을 public 으로 노출
이다.
이러면 CloudFront 없이도 S3에 직접 접근할 수 있다.
대신 쓰는 방식이
OAC (Origin Access Control)
이다.
S3는 비공개 (Public Access Block ON)
오직 "이 CloudFront 배포" 만 읽을 수 있게 권한 부여
이렇게 하면
사용자 → CloudFront → S3 (OK)
사용자 → S3 직접 접근 (차단)
운영에서는 사실상 OAC가 표준이다.
과거 OAI(Origin Access Identity)는 OAC로 대체됐다
신규 구성은 무조건 OAC
7. 동적 origin도 보호하기
ALB에 직접 누가 접근하는 걸 막고 싶다면 두 가지를 함께 쓴다.
- CloudFront가 origin에 보낼 때 비밀 헤더를 추가
x-origin-secret: <긴 무작위 문자열> - ALB는 그 헤더가 없는 요청은 403
이러면
사용자 → CloudFront → ALB (헤더 있음, OK)
사용자 → ALB 직접 접근 (헤더 없음, 차단)
CloudFront-ALB 사이도 보안을 줄 수 있는 구조
8. 우리 서비스에서
척추 그림의 CloudFront 안쪽이다.
[CloudFront]
├─ /static/* → s3-static (OAC, 24h 캐시)
├─ /img/* → s3-img (OAC, 7d 캐시)
├─ /api/* → alb (캐시 없음, 시크릿 헤더)
└─ /* → s3-static (SPA index.html)
이 단순한 구조 하나로
- 정적 자원은 CDN에서 흡수
- 동적 API는 그대로 패스
- 백엔드는 모두 비공개
- 보안과 속도가 함께 잡힌다
9. 직접 확인해보기 — CLI
배포 설정 보기
aws cloudfront get-distribution \
--id E123ABCD
Cache Hit Ratio 확인
CloudFront 콘솔의 Reports 또는 CloudWatch 지표CacheHitRate 로 확인한다.
aws cloudwatch get-metric-statistics \
--namespace AWS/CloudFront \
--metric-name CacheHitRate \
--dimensions Name=DistributionId,Value=E123ABCD Name=Region,Value=Global \
--start-time 2026-01-01T00:00:00Z \
--end-time 2026-01-02T00:00:00Z \
--period 3600 \
--statistics Average
운영에서 가장 자주 보는 지표 중 하나다.
10. 코드로는 이렇게 생겼다 — Terraform
S3 + OAC + 두 개의 behavior가 있는 배포다.
resource "aws_cloudfront_origin_access_control" "s3" {
name = "s3-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "main" {
enabled = true
aliases = ["example.com"]
origin {
domain_name = aws_s3_bucket.static.bucket_regional_domain_name
origin_id = "s3-static"
origin_access_control_id = aws_cloudfront_origin_access_control.s3.id
}
origin {
domain_name = aws_lb.main.dns_name
origin_id = "alb"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "s3-static"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
}
ordered_cache_behavior {
path_pattern = "/api/*"
target_origin_id = "alb"
viewer_protocol_policy = "https-only"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.us_east.arn
ssl_support_method = "sni-only"
}
restrictions {
geo_restriction { restriction_type = "none" }
}
}
/api/* 만 캐시 비활성 정책을 쓰고
나머지는 기본 캐시 정책을 쓰는 모양이다.
11. 이렇게 쓰면 망한다 — 안티패턴
안티패턴 1. S3 버킷을 public 으로 두고 CloudFront만 앞에 둔다
S3 버킷은 비공개로 두고 OAC로 권한을 준다.
public 버킷은 절대 운영에 두지 않는다
안티패턴 2. 모든 요청을 같은 Cache Behavior로 처리한다
정적과 동적이 같은 캐시 정책을 쓰면
한쪽이 망가진다.
/api/* 와 /static/* 은 거의 항상 다른 behavior가 필요하다
안티패턴 3. Cache Key에 Authorization 헤더를 넣지 않고 인증 API를 캐시한다
GET /api/me → 다른 사용자의 응답이 캐시된다
- 인증 API는 캐시하지 않거나
- Authorization 헤더를 Cache Key에 포함
안티패턴 4. ALB가 인터넷에 그대로 노출돼 있다
CloudFront를 앞에 두고도 ALB가 누구나 접근 가능하면
CDN을 우회한 공격이 가능하다.
CloudFront 시크릿 헤더 + ALB 규칙으로 우회를 차단한다
12. 한 줄로 정리
Origin은 어디서 가져올지, Cache Behavior는 어떻게 다룰지,
OAC는 누구만 가져갈 수 있는지를 정한다
13. 이 장의 핵심 정리
- CloudFront 배포는 여러 origin을 가질 수 있고 경로별로 다른 origin으로 보낼 수 있다.
- Cache Behavior가 어떤 경로를 어떻게 캐시할지 결정한다.
- Cache Key는 응답을 진짜로 바꾸는 항목만 포함해야 적중률이 높다.
- 배포 자원은 invalidation 대신 파일명 해싱으로 갱신하는 게 표준이다.
- S3 origin은 비공개 + OAC 조합이 기본이다.
- ALB origin은 시크릿 헤더로 CloudFront만 통과시키도록 보호할 수 있다.