내 스토리지 + CDN 사용 (BYO)
API Velocity 및 Momentum 요금제에서 직접 보유한 오브젝트 스토리지와 CDN을 연결하는 방법: 제공업체별 설정, 최소 권한, 연결 확인, 모드를 전환할 때 일어나는 일.
사용 가능 요금제: API Velocity 및 Momentum 요금제. Ignite(CDN이 없음), 개발자 샌드박스(샌드박스 파일은 임시 파일), WordPress 요금제(관리형 smallPict CDN 사용)에서는 사용할 수 없습니다.
요약: BYO 모드에서는 smallPict가 원본과 최적화된 이미지를 고객님의 버킷에 기록하고, 고객님의 CDN이 이를 제공합니다. 스토리지와 CDN은 항상 함께 연결됩니다. 하나만 가져올 수는 없습니다.
BYO란?
Velocity와 Momentum에는 두 가지 전송 모드가 있습니다. 대시보드 → CDN 및 스토리지에서 하나를 선택합니다.
- 관리형(기본값): smallPict가 원본을 암호화된 스토리지에 보관하고(90일 후 장기 아카이브로 이동) 최적화된 이미지를
cdn.smallpict.app에서 제공합니다. 설정할 것이 없습니다. - 내 스토리지 + CDN 사용(BYO): 고객님 계정의 S3 호환 버킷과 고객님 계정의 CDN을 연결합니다. 모든 새 작업은 원본과 최적화된 파일을 고객님 버킷에 기록하며, API는 고객님 CDN 도메인의 URL을 반환합니다.
두 가지를 함께 연결해야 하는 이유는 smallPict가 전송의 두 단계, 즉 파일 기록과 캐시에서 오래된 사본 제거를 모두 수행할 수 있어야 하기 때문입니다. CDN 없는 버킷은 캐시 퍼지를 보낼 곳이 없고, 버킷 없는 CDN은 제공할 파일이 없습니다.
관리형과 BYO 비교
| 관리형(기본값) | 내 스토리지 + CDN 사용 | |
|---|---|---|
| 원본 | smallPict 암호화 스토리지 | 고객님 버킷의 originals/<job_id>/<file> |
| 최적화된 파일 | cdn.smallpict.app에서 제공 | 고객님 버킷의 optimized/<job_id>.<ext>, 고객님 CDN 도메인에서 제공 |
| 스토리지 할당량 | Velocity 50GB, Momentum 100GB | 집계하지 않습니다. 스토리지 제공업체에 비용을 지불합니다. |
| CDN 대역폭 | Velocity 월 30GB, Momentum 월 200GB | 집계하지 않습니다. CDN 제공업체에 비용을 지불합니다. |
| 변환 | 요금제 사용량에 포함 | 요금제 사용량에 포함 |
| smallPict가 보관하는 사본 | 있음(계정이 활성 상태인 동안) | 없음. 처리용 임시 업로드만 있으며 24시간 이내에 삭제됩니다. |
| 캐시 퍼지 | 자동 | 자동, 고객님 CDN의 API를 통해 |
| CDN 성능, 도메인, 비용 | smallPict | 고객님. smallPict는 고객님 CDN의 성능, 도메인, 청구 금액에 대해 책임지지 않습니다. |
지원 제공업체
스토리지(현재 사용 가능): Amazon S3, Cloudflare R2, Google Cloud Storage(S3 상호 운용성), Alibaba Cloud OSS, Tencent Cloud COS, DigitalOcean Spaces, SumoPod Storage, MinIO, 그리고 사용자 지정 엔드포인트를 통한 기타 모든 S3 호환 서비스(예: Hetzner, Vultr, Wasabi).
CDN(현재 사용 가능): Cloudflare 및 Amazon CloudFront.
예정: Azure Blob Storage 및 기타 CDN. 클라우드 제공업체 페이지에서 얼리 액세스를 요청할 수 있습니다.
시작하기 전에
- API Velocity 또는 Momentum 요금제를 사용 중이며 계정 관리자로 로그인되어 있습니다.
- 버킷과, 그 버킷의 파일을 HTTPS로 제공하는 CDN이 있습니다.
- 스토리지 엔드포인트는 인터넷에서 HTTPS로 접근할 수 있어야 합니다. 사설, 내부, 일반 HTTP 엔드포인트는 거부됩니다.
- smallPict 전용으로 범위를 좁힌 키를 만들었습니다(아래 최소 권한 참고). 계정의 루트 키나 관리자 키는 사용하지 마세요.
그런 다음 대시보드 → CDN 및 스토리지를 열고 내 스토리지 + CDN 사용을 선택한 뒤, 두 부분을 모두 입력하고 스토리지 + CDN 저장을 선택하세요. smallPict가 연결 확인(아래 설명)을 실행하고, 통과한 경우에만 BYO로 전환합니다.
설정 가이드
A. Amazon S3 + Amazon CloudFront
- 원하는 리전에 버킷을 만듭니다(예:
ap-southeast-1의my-images). Block Public Access는 켜 둡니다. - 버킷을 오리진으로 하는 CloudFront 배포를 만듭니다. 버킷을 비공개로 유지하면서 CloudFront가 읽을 수 있도록 Origin access control (OAC) 기능을 사용하고, CloudFront가 제안하는 버킷 정책을 적용하세요.
- 선택 사항: 자체 도메인. 배포에 대체 도메인 이름(예:
images.example.com)과 인증서를 추가하고, DNS 레코드가 배포를 가리키도록 설정합니다. - 다음 정책으로 스토리지용 IAM 사용자(또는 역할)를 만듭니다.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::my-images/*" } ]}- 다음 정책으로 CloudFront용 IAM 사용자를 만듭니다(같은 사용자여도 됩니다).
cloudfront:GetDistribution은 선택 사항이며, 이 권한이 있으면 CDN 도메인을 비워 두었을 때 smallPict가 배포의 도메인을 감지할 수 있습니다.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "cloudfront:CreateInvalidation", "cloudfront:GetDistribution" ], "Resource": "arn:aws:cloudfront::123456789012:distribution/E2QWRUHAPOMQZL" } ]}- 대시보드에서:
- 스토리지 제공업체 Amazon S3, 버킷
my-images, 리전ap-southeast-1, 엔드포인트는 비워 둠(리전의 표준 엔드포인트 사용), 그리고 스토리지 액세스 키 ID와 시크릿. - CDN Amazon CloudFront, Distribution ID(예:
E2QWRUHAPOMQZL), CloudFront 액세스 키 ID와 시크릿, 그리고 선택적으로 CDN 도메인(https://images.example.com). 도메인을 비워 두면 배포 자체의*.cloudfront.net도메인을 사용합니다.
- 스토리지 제공업체 Amazon S3, 버킷
B. Cloudflare R2 + Cloudflare
- R2 버킷을 만듭니다(예:
my-images). - 같은 Cloudflare 계정에 있는 존의 사용자 지정 도메인을 버킷에 연결합니다(R2 → 버킷 → Settings → Custom Domains). 예:
images.example.com. 이 도메인으로 들어오는 요청은 Cloudflare 캐시를 거칩니다. 프로덕션에서는r2.dev개발용 URL을 사용하지 마세요. - 이 버킷으로만 범위를 제한한 Object Read & Write 권한의 R2 API 토큰을 만듭니다(R2 → Manage API tokens). 표시되는 Access Key ID와 Secret Access Key를 복사하세요.
- 해당 존에 대해 Zone → Cache Purge → Purge 권한이 있는 Cloudflare API 토큰을 만듭니다(My Profile → API Tokens). Zone → Zone → Read는 선택 사항입니다.
- 대시보드에서:
- 스토리지 제공업체 Cloudflare R2, 버킷
my-images, 엔드포인트https://<account_id>.r2.cloudflarestorage.com(버킷의 S3 API 정보에 표시됨), 리전auto, 그리고 R2 Access Key ID와 Secret Access Key. - CDN Cloudflare, Zone ID(도메인의 Overview 페이지, API 섹션), API 토큰, 그리고 CDN 도메인
https://images.example.com.
- 스토리지 제공업체 Cloudflare R2, 버킷
C. Google Cloud Storage + Cloudflare
Google Cloud Storage는 HMAC 키를 사용하는 S3 호환 XML API로 연결됩니다.
- 버킷을 만듭니다(예:
my-images). - 서비스 계정을 만들고 이 버킷에 대해서만 Storage Object User(
roles/storage.objectUser) 역할을 부여합니다. - 서비스 계정의 HMAC 키를 만듭니다: Cloud Storage → Settings → Interoperability → Create a key for a service account. 액세스 ID와 시크릿을 복사하세요.
- 최적화된 파일을 CDN이 읽을 수 있게 합니다. Cloudflare는 HTTPS로 Cloud Storage에서 파일을 가져오므로
optimized/아래의 객체는 공개적으로 읽을 수 있어야 합니다. 균일한 버킷 수준 액세스를 사용하는 경우allUsers에 Storage Object Viewer 역할을 부여하면originals/를 포함한 버킷 전체를 읽을 수 있게 됩니다. 원본을 비공개로 유지해야 한다면 앞단에 인증 계층을 두세요(예: 버킷에 대한 요청에 서명하는 Cloudflare Worker). - 프록시된 Cloudflare DNS 레코드가 Cloud Storage를 가리키도록 합니다(예:
images.example.com). 버킷 이름을 호스트 이름과 같게 하고c.storage.googleapis.com으로 프록시된 CNAME을 사용하거나, 경로 앞부분에 버킷 이름을 붙여storage.googleapis.com으로 요청을 보내는 Cloudflare Origin Rule을 사용하세요. - 해당 존에 대해 Zone → Cache Purge → Purge 권한이 있는 Cloudflare API 토큰을 만듭니다(Zone → Zone → Read는 선택 사항).
- 대시보드에서: 스토리지 제공업체 Google Cloud Storage, 버킷, 엔드포인트
https://storage.googleapis.com, 리전auto, HMAC 액세스 ID와 시크릿, 그리고 CDN Cloudflare, Zone ID, API 토큰, CDN 도메인https://images.example.com.
D. MinIO 또는 모든 S3 호환 스토리지 + Cloudflare
MinIO와 Hetzner, Wasabi, Vultr, SumoPod, Alibaba Cloud OSS, Tencent Cloud COS, DigitalOcean Spaces 같은 S3 호환 서비스가 여기에 해당합니다.
| 제공업체 | 대시보드에서 선택 | 엔드포인트 | 참고 |
|---|---|---|---|
| MinIO | MinIO | 고객님 서버, 예: https://minio.example.com | 경로 방식 요청 켜기. 서버는 인터넷에서 HTTPS로 접근할 수 있어야 합니다. |
| Hetzner Object Storage | 사용자 지정 S3 호환 | https://<location>.your-objectstorage.com | |
| Wasabi | 사용자 지정 S3 호환 | https://s3.<region>.wasabisys.com | |
| Vultr Object Storage | 사용자 지정 S3 호환 | https://<region>.vultrobjects.com | |
| SumoPod Storage | SumoPod Storage | SumoPod 스토리지 대시보드에서 확인 | 경로 방식이 기본으로 켜져 있습니다. |
| Alibaba Cloud OSS | Alibaba Cloud OSS | https://oss-<region>.aliyuncs.com | 가상 호스트 방식만 지원합니다. |
| Tencent Cloud COS | Tencent Cloud COS | https://cos.<region>.myqcloud.com | 버킷 이름에 APPID가 포함됩니다. 예: my-images-1250000000. |
| DigitalOcean Spaces | DigitalOcean Spaces | https://<region>.digitaloceanspaces.com |
- 버킷을 만들고, 해당 버킷의 객체에 대해 put, get, delete만 허용하는 키를 만듭니다(아래 최소 권한 참고).
optimized/를 CDN이 읽을 수 있게 합니다. 예를 들어optimized/*에 대해서만 익명s3:GetObject를 허용하는 버킷 정책을 사용합니다. MinIO의 경우:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::my-images/optimized/*" ] } ]}- 버킷 앞에 Cloudflare를 둡니다:
images.example.com에 대한 프록시된 DNS 레코드가 버킷의 공개 호스트를 가리키도록 합니다. 제공업체가 요청에 자체 호스트 이름을 요구하면 Host 헤더를 다시 쓰는 Cloudflare Origin Rule을 추가하세요(경로 방식 호스트라면 경로에 버킷 이름도 추가). - 해당 존에 대해 Zone → Cache Purge → Purge 권한이 있는 Cloudflare API 토큰을 만듭니다(Zone → Zone → Read는 선택 사항).
- 대시보드에서: 표에서 제공업체를 선택하고 버킷, 엔드포인트, 리전(제공업체에 리전이 없으면
auto), 키를 입력합니다. 표에 안내된 경우 경로 방식 요청을 켭니다. 그다음 CDN Cloudflare, Zone ID, API 토큰, CDN 도메인을 입력합니다.
최소 권한
smallPict에는 하나의 버킷에서 필요한 작업만 할 수 있는 키를 제공하세요.
| 제공업체 | 권한 |
|---|---|
| Amazon S3 | arn:aws:s3:::<bucket>/*에 대한 s3:PutObject, s3:GetObject, s3:DeleteObject |
| Amazon CloudFront | 배포에 대한 cloudfront:CreateInvalidation. 도메인 감지를 위해 선택적으로 cloudfront:GetDistribution |
| Cloudflare R2 | 버킷으로 범위를 제한한 Object Read & Write 권한의 R2 API 토큰 |
| Cloudflare (CDN) | 존에 대해 Zone → Cache Purge → Purge 권한이 있는 API 토큰. 선택적으로 Zone → Zone → Read |
| Google Cloud Storage | 버킷에 대해 roles/storage.objectUser 역할을 가진 서비스 계정의 HMAC 키 |
| Alibaba Cloud OSS | 버킷에 대해 oss:PutObject, oss:GetObject, oss:DeleteObject 권한을 가진 RAM 사용자 |
| Tencent Cloud COS | 버킷에 대해 cos:PutObject, cos:GetObject, cos:DeleteObject 권한을 가진 CAM 하위 사용자(버킷 이름에 APPID 포함) |
| DigitalOcean Spaces | 버킷으로 제한되고 읽기, 쓰기, 삭제 권한이 있는 Spaces 액세스 키 |
| MinIO / 사용자 지정 S3 호환 | <bucket>/*에 대한 put, get, delete. 엔드포인트는 공개 HTTPS여야 합니다 |
연결 확인
저장할 때마다(그리고 다시 확인을 선택할 때) smallPict는 연결을 사용하기 전에 전체 연결을 확인합니다.
- 스토리지: 버킷의
.smallpict-probe/아래에 작은 테스트 파일을 쓰고, 다시 읽은 뒤 삭제합니다. - Cloudflare: 존에 단일 URL 테스트 퍼지를 한 번 보냅니다.
- CloudFront:
/.smallpict-probe/...경로에 대한 테스트 무효화를 한 번 생성합니다. 이는 해당 월의 CloudFront 무효화 경로 수에 포함됩니다.
어느 단계든 실패하면 아무것도 전환되지 않습니다. 대시보드는 관련 항목(예: 버킷 이름 또는 API 토큰) 옆에 실패 이유를 표시합니다. 연결 확인은 분당 5회로 제한됩니다.
CDN의 버킷 접근
optimized/경로는 CDN이 읽을 수 있어야 합니다. 해당 접두사에 공개 읽기를 허용하거나, 비공개 버킷에 대한 CDN 오리진 접근(CloudFront origin access control 또는 R2 사용자 지정 도메인)을 사용하세요.originals/경로는 비공개로 유지할 수 있습니다. CDN은 이 경로가 필요하지 않습니다.- CORS는 브라우저가 JavaScript에서 교차 출처로 이미지를 가져오는 경우(예:
fetch()또는 canvas)에만 필요합니다. 일반<img>태그에는 필요하지 않습니다. 필요하다면 사이트 출처에서 오는GET과HEAD를 허용하세요.
객체 구조와 URL
| 항목 | 버킷의 키 | URL |
|---|---|---|
| 원본 | originals/<job_id>/<file> | 제공되지 않음 |
| 최적화된 파일 | optimized/<job_id>.<ext> | <cdn_domain>/optimized/<job_id>.<ext> |
CDN 도메인에는 경로 접두사를 포함할 수 있습니다(예: https://example.com/images). 이 경우 URL은 https://example.com/images/optimized/<job_id>.<ext>가 됩니다.
캐시 헤더
최적화된 파일은 Cache-Control: public, max-age=31536000, immutable로 기록됩니다. 모든 작업은 고유한 키를 가지므로 새 결과는 항상 새 URL을 가지며, 퍼지가 필요한 경우는 드뭅니다. CDN의 캐싱 규칙이 이 헤더를 더 짧은 시간으로 덮어쓰지 않도록 하세요.
캐시 퍼지
- API를 통한 퍼지 요청(
POST /v1/purge)은 고객님의 CDN으로 일괄 전송됩니다. - Cloudflare에서는 파일이 URL 단위로 퍼지됩니다. "전체 퍼지"는 존 전체가 아니라 전송 호스트와 경로 접두사만 퍼지합니다.
- CloudFront에서는 무효화로 파일을 퍼지합니다. "전체 퍼지"는
/*무효화를 생성합니다. CloudFront의 월간 무료 한도를 초과하는 무효화 경로는 AWS가 고객님 계정에 청구합니다.
모드 전환
모드를 전환해도 기존 파일은 이동되지 않습니다. 이미 전송된 파일은 현재 URL을 유지하며, 새 작업에만 새 모드가 적용됩니다.
- 관리형 → BYO: 새 작업은 고객님 버킷에 저장되고 고객님 CDN이 제공합니다. 이미
cdn.smallpict.app에 있는 파일은 그대로 남습니다. - BYO → 관리형: 새 작업은 다시 관리형 스토리지와 CDN을 사용합니다. 연결 해제를 선택하기 전까지 BYO 설정과 키는 저장된 상태(사용 안 함)로 유지되므로, 다시 입력하지 않고도 다시 전환할 수 있습니다.
- 연결 해제: 저장된 키와 BYO 설정을 삭제하고 관리형으로 되돌립니다. 버킷의 파일은 건드리지 않습니다.
문제가 생기면
- smallPict가 버킷에 기록하지 못하면 재시도한 뒤 작업을 실패 처리하며, 그 이유는 API 응답에서 확인할 수 있습니다.
- 대시보드에 마지막 오류와 발생 시각이 다시 확인 버튼과 함께 표시됩니다.
- 실패한 작업마다가 아니라 장애 한 건당 이메일 한 통을 받습니다.
- smallPict는 관리형 스토리지로 절대 대체하지 않습니다. 파일이 고객님이 선택하지 않은 곳에 저장되는 일은 없습니다.
보안
- 키는 암호화된 시크릿 저장소에 보관됩니다. 다시 표시되지 않으며, API로 반환되지 않고, 로그에도 기록되지 않습니다.
- 키를 교체하려면 새 키를 입력하고 저장하세요. 연결 확인이 새 키로 실행됩니다. 저장된 키를 유지하려면 키 입력란을 비워 두세요.
- 스토리지와 CDN 엔드포인트는 HTTPS를 사용해야 합니다. 사설, 루프백, 링크 로컬 또는 기타 내부 주소로 확인되는 엔드포인트는 거부됩니다.
- smallPict 전용으로 범위를 좁힌 별도의 키를 사용하고, BYO 사용을 중단하면 제공업체 콘솔에서 키를 폐기하세요.