본문 바로가기

Backend/Study

[DevOps] AWS S3 + CloudFront 이미지 업로드 & 캐싱 전략

반응형

0. 개요

  1. AWS S3 + CloudFront 이미지 업로드 & 캐싱 전략
  2. 아키텍처 개요와 선택지
    • 2-1. 정적 업로드(프론트→S3) vs 백엔드 중계
    • 2-2. OAC 기반 프라이빗 S3 + 퍼블릭 캐시
  3. 보안 기초: S3 정책·OAC·퍼블릭 차단
    • 3-1. S3 Block Public Access, 버킷 정책(OAC)
    • 3-2. Origin Access Control vs OAI
  4. 업로드 전략: 프리사인드 URL/POST & CORS
    • 4-1. 브라우저 직업로드(멀티파트) 설계
    • 4-2. 안전한 CORS·키 네이밍 규칙
  5. 캐싱 전략: 키 설계·TTL·버전닝
    • 5-1. 해시 파일명, 쿼리 파라미터 정책
    • 5-2. Cache Policy/Origin Request Policy
  6. 포맷/리사이즈: WebP/AVIF & On-the-fly
    • 6-1. 정적 선변환 vs 동적 변환
    • 6-2. Lambda@Edge/이미지 핸들러 패턴
  7. 비용·성능: 오리진 쉴드·압축·서빙 팁
    • 7-1. 오리진 쉴드·압축·범용 TTL
    • 7-2. Invalidation 최소화 운영
  8. 레퍼런스 설정 모음
    • S3 CORS, 버킷 정책(OAC), 예제 코드
  9. 트러블슈팅
    • 403/AccessDenied, CORS 오류, 304 미발생
  10. 체크리스트 & 운영 플레이북
  11. 결론 요약
  12. FAQ

1. AWS S3 + CloudFront 이미지 업로드 & 캐싱 전략

이 글은 “프론트엔드가 S3에 이미지를 올리고 CloudFront로 전 세계에 빠르게·싸게·안전하게 서빙”하는 표준 패턴을 정리합니다. 설계 핵심은 보안(OAC), 업로드 경로(프리사인드), 캐시 키 설계, 포맷/리사이즈 전략, 운영 가드레일 다섯 가지입니다.

2. 아키텍처 개요와 선택지

2-1. 정적 업로드(프론트→S3) vs 백엔드 중계

  • 프론트→S3 직업로드: 서버 부하/비용 최소. 백엔드는 프리사인드 URL/POST를 발급만 함.
  • 백엔드 중계: 엄격한 검증·워터마크 등 서버 처리 필요 시. 단점은 트래픽/레이터시 증가.
  • 권장: 기본은 프론트 직업로드 + 백엔드가 메타데이터 기록·검증. 대용량은 멀티파트 업로드.

2-2. OAC 기반 프라이빗 S3 + 퍼블릭 캐시

  • S3는 퍼블릭 완전 차단. CloudFront만 S3에 접근(OAC).
  • 클라이언트는 항상 https://dxxxxx.cloudfront.net/... 으로 이미지 조회.
  • 다운로드 권한 제어가 필요하면 서명된 URL/쿠키를 CloudFront에서 사용.

3. 보안 기초: S3 정책·OAC·퍼블릭 차단

3-1. S3 Block Public Access, 버킷 정책(OAC)

  • S3에서 Block Public Access(계정/버킷)를 모두 ON.
  • CloudFront 배포에서 오리진 유형 S3, OAC(Origin Access Control) 생성·연결.
  • 버킷 정책에서 CloudFront 서비스 주체만 허용(아래 예시 참고).
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontReadViaOAC",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-image-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E123ABC456DEF"
        }
      }
    }
  ]
}

3-2. Origin Access Control vs OAI

  • OAC(신규 권장): 서명된 요청, 보안·감사 강화.
  • OAI(레거시): 기존 배포에만 유지, 신규는 OAC로 시작하세요.

4. 업로드 전략: 프리사인드 URL/POST & CORS

4-1. 브라우저 직업로드(멀티파트) 설계

  • 백엔드가 키 네이밍(예: orgId/userId/yyyy/mm/uuid.ext)과 Content-Type 제한 포함 프리사인드 발급.
  • 대용량은 멀티파트 업로드로 네트워크 오류에 강함.
  • 업로드 완료 후 백엔드에 콜백해 DB에 메타데이터(원본, 썸네일, 해시, 사이즈)를 기록.
// Node.js (S3 v3) 프리사인드 PUT
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: "ap-northeast-2" });
const url = await getSignedUrl(s3, new PutObjectCommand({
  Bucket: "my-image-bucket",
  Key: `u/${userId}/${Date.now()}-${uuid}.jpg`,
  ContentType: "image/jpeg",
  ACL: "private"
}), { expiresIn: 60 });

4-2. 안전한 CORS·키 네이밍 규칙

  • 출처 화이트리스트, PUT, POST만 허용, 헤더는 최소화.
  • 키는 디렉터리 규칙 + UUID/해시로 충돌 방지. 확장자 기반 Content-Type 검증.
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://app.example.com"],
      "AllowedMethods": ["PUT","POST"],
      "AllowedHeaders": ["content-type","x-amz-acl"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 300
    }
  ]
}

5. 캐싱 전략: 키 설계·TTL·버전닝

5-1. 해시 파일명, 쿼리 파라미터 정책

  • 원본 불변을 기본 가정. 파일명에 컨텐츠 해시(예: abc123.jpg)를 포함하면 무효화 없이 새 버전 배포 가능.
  • 동적 리사이즈를 쿼리로 표현한다면(?w=800&fmt=webp) 캐시 정책에서 쿼리 화이트리스트를 설정해야 변형별 캐시가 분리됩니다.

5-2. Cache Policy/Origin Request Policy

  • Cache Policy: 캐시 키에 포함할 요소(쿼리, 헤더, 쿠키)를 정의. 이미지의 경우 쿼리(w,h,fit,fmt,quality), 헤더(Accept)만 포함.
  • Origin Request Policy: 오리진으로 전달할 요소를 별도로 제어. 불필요한 쿠키/헤더 전달을 차단해 캐시 적중률↑.
  • TTL: 정적 불변 리소스는 Cache-Control: public, max-age=31536000, immutable. 동적 변형은 짧은 TTL(예: 1h) + 높은 적중률 설계.

6. 포맷/리사이즈: WebP/AVIF & On-the-fly

6-1. 정적 선변환 vs 동적 변환

  • 선변환: 업로드 시 썸네일·WebP 등 N종 생성 → 저장 비용 증가, 오리진 부하 최소.
  • 동적 변환: 최초 요청 시 변환 후 S3/CloudFront에 캐시 → 저장 비용↓, 콜드 스타트 지연 발생 가능.

6-2. Lambda@Edge/이미지 핸들러 패턴

  • Viewer Request에서 URL 정규화(예: /i/w=800/abc.jpg → /i/abc.jpg?w=800).
  • Origin Response에서 변환 미스 시 Lambda@Edge가 S3에서 원본을 가져와 변환(Sharp 등) → 변환본을 별도 버킷/프리픽스에 저장해 재사용.
  • 브라우저 Accept 헤더를 캐시 키에 포함해 AVIF/WebP를 조건부 제공.

7. 비용·성능: 오리진 쉴드·압축·서빙 팁

7-1. 오리진 쉴드·압축·범용 TTL

  • Origin Shield: 리전별 상위 캐시로 오리진 히트율 최소화, 핫 오브젝트 보호.
  • 압축: 이미지 자체는 포맷 최적화가 핵심. 텍스트 자원은 Gzip/Brotli(응답 헤더 정책)로 전송 효율↑.
  • 범용 TTL: 슬라이스(썸네일·프로필)처럼 자주 갱신되는 이미지는 짧은 TTL+버전 파라미터(?v=hash)로 일관 운영.

7-2. Invalidation 최소화 운영

  • 원칙: 파일명 버전닝(해시)으로 무효화 없는 배포.
  • 부득이하면 프리픽스 단위(예: /i/profile/*)로 묶어 최소량만 무효화.

8. 레퍼런스 설정 모음

- S3 CORS, 버킷 정책(OAC), 예제 코드

# 프론트 PUT 예시 (브라우저)
await fetch(presignedUrl, {
  method: "PUT",
  headers: { "Content-Type": file.type },
  body: file
});
# CloudFront 응답 헤더 정책(예시)
# Cache-Control은 오브젝트 메타데이터 또는 오리진에서 설정
Cache-Control: public, max-age=31536000, immutable
ETag: "abc123"
Content-Type: image/webp

9. 트러블슈팅

  • 403/AccessDenied: 버킷 정책에 OAC SourceArn이 누락되었거나 다른 배포 ARN. S3 퍼블릭 차단이 Off인지 점검(켜져 있어야 함).
  • CORS 오류: AllowedOrigins·AllowedHeaders 불일치, 프리사인드 POST 필드 누락, OPTIONS 미허용.
  • 304 미발생/재다운로드: 캐시 키에 불필요한 쿠키/헤더 포함 → 캐시 분산. Cache Policy를 슬림하게 조정.
  • WebP 제공 안 됨: Accept 헤더를 캐시 키/오리진 전달 정책에 포함했는지 확인.
  • 느린 첫 요청: 동적 변환 콜드 스타트. 자주 쓰는 사이즈는 사전 워밍 또는 선변환.

10. 체크리스트 & 운영 플레이북

  • 보안: S3 Block Public Access ON, CloudFront OAC 연결, 버킷 정책에 SourceArn 지정.
  • 업로드: 프리사인드(60초 만료), 키 규칙(조직/유저/날짜/UUID), 멀티파트.
  • 캐시: 해시 파일명/버전 파라미터, Cache Policy에 쿼리·Accept만 포함, TTL 분류.
  • 포맷: WebP/AVIF 조건부 제공, 리사이즈 파이프라인(선변환 or Lambda@Edge).
  • 운영: Origin Shield, 로그/지표(적중률·오리진 요청 수), 최소 무효화.

11. 결론 요약

프라이빗 S3 + OAC + 프리사인드 업로드 + 해시 기반 캐시가 비용·성능·보안을 동시에 잡는 정석입니다. 변환 전략(선변환/동적)을 워크로드에 맞게 고르고, Cache/Origin Request Policy로 캐시 키를 날씬하게 유지하면 운영이 쉬워집니다.

12. FAQ

Q1. 굳이 OAC까지 써야 하나요?

A1. 예. 퍼블릭 버킷은 공격 표면이 넓습니다. OAC로 CloudFront만 접근하도록 만들면 보안과 감사가 단순해집니다.

Q2. 무효화 대신 파일명 버전만 바꿔도 안전한가요?

A2. 불변 리소스라면 베스트 프랙티스입니다. 새 파일명(해시)으로 배포하고 오래된 파일은 정리 주기를 운영하세요.

Q3. 동적 변환이 느리면 어떻게 하나요?

A3. 자주 쓰는 사이즈를 선변환해 핫세트를 만들고, 나머지는 Lambda@Edge로 온디맨드 + 결과를 S3에 저장해 재사용하세요.

반응형