테스트용 S3를 계속 쓰기 불합리해서, SeaweedFS 기반 스토리지를 직접 구축했습니다
들어가며
로컬에서 업로드, presigned URL, 공개 버킷, CDN 캐시, 썸네일까지 반복해서 테스트하다 보면, 테스트용으로만 외부 S3를 계속 붙이는 흐름이 생각보다 부자연스럽습니다. 비용 문제만이 아니라, 실험할 때마다 외부 계정과 자원을 경유해야 한다는 점이 계속 거슬렸기 때문입니다.
그래서 이번에는 방향을 바꿨습니다. 테스트에서 반복적으로 쓰는 스토리지는 아예 온프레미스로 들고 오고, 애플리케이션이 보는 인터페이스는 최대한 S3 그대로 유지하기로 했습니다. local, dev 환경에서는 온프레미스 S3 호환 스토리지를 붙이고, 운영환경에서는 같은 인터페이스로 AWS S3나 Cloudflare R2를 바로 교체할 수 있으면 훨씬 좋은 구조라고 봤기 때문입니다.
결과적으로는 SeaweedFS + Nginx CDN + TLS + TTL 조합으로 단일 노드 기준의 S3 호환 스토리지를 구축했습니다. 그리고 이번 작업은 리서치 초안, 인프라 설정 초안, 매니페스트 작성, 배포 절차 정리까지 AI가 전자동화한 사례이기도 했습니다. 이 글에서는 실제 키, Secret, 내부 경로, 내부 IP 같은 민감 정보는 모두 마스킹해서 다룹니다.
왜 굳이 직접 만들었나
AWS S3 자체가 문제였던 것은 아닙니다. 다만 테스트 목적에서는 점점 어색해졌습니다.
- 로컬과 개발 환경에서 파일 업로드 흐름을 반복 검증해야 했습니다.
- 공개 버킷과 비공개 버킷을 나눠 CDN, presigned URL, 썸네일 레이어를 같이 확인해야 했습니다.
- 임시 파일을 짧은 주기로 생성하고 지우는 흐름이 많았습니다.
- 그때마다 외부 스토리지를 기준으로 실험하는 방식은 단순한 테스트 치고는 무거웠습니다.
제가 원했던 것은 거대한 스토리지 플랫폼이 아니라, 테스트와 검증에 충분한 S3 호환성과 간단한 온프레미스 운영성이었습니다. 더 정확히 말하면, 개발자가 보는 저장소 계약 자체를 S3 호환 인터페이스로 고정해 두고, 환경에 따라 백엔드 구현체만 바꿔 끼울 수 있는 구조가 필요했습니다.
왜 SeaweedFS였나
처음에는 가장 익숙한 선택지를 먼저 봤지만, 2025년 말 기준으로는 MinIO를 기본 선택지로 두기가 어려웠습니다. 라이선스 변경과 커뮤니티 운영 방향 변화 때문에, 장기적으로 가볍게 들고 가기에는 애매하다고 판단했습니다.
반대로 Ceph RGW는 훨씬 완전한 선택지였지만, 이번 목표에는 너무 무거웠습니다. 저는 단일 노드 기준의 테스트용 스토리지와 간단한 미디어 운용이 필요했지, 대규모 스토리지 클러스터를 운영하려던 것이 아니었습니다.
그래서 최종 선택지는 SeaweedFS였습니다.
- Apache 2.0 라이선스라 상업적 제약이 가볍습니다.
- S3 Gateway를 제공해서 기존 SDK 흐름을 크게 바꾸지 않아도 됩니다.
- 업로드, 다운로드, 버킷 단위 분리, presigned URL 같은 주 사용 범위에서는 S3 인터페이스를 거의 그대로 유지할 수 있습니다.
- Helm 기반으로 Kubernetes에 바로 올리기 쉽습니다.
- 이미지, 오디오, 짧은 비디오처럼 작은 미디어 파일을 다루기에 성격이 잘 맞았습니다.
완전한 S3 대체재를 찾았다기보다, 이번 목적에 필요한 범위에서 S3와 거의 같은 계약으로 움직이는 저장소를 가장 간결하게 고른 선택에 가까웠습니다.
실제로는 이렇게 구성했습니다
구성은 복잡하게 가지 않았습니다. SeaweedFS를 중심으로 S3 호환 레이어를 만들고, 앞단에는 Nginx 캐시 계층을 붙였습니다. 그 위에 TLS, 공개 버킷용 썸네일 레이어, 임시 파일 TTL 정책까지 더했습니다.
운영 단위는 대략 이렇게 나뉘었습니다.
- S3 endpoint: 애플리케이션이 직접 붙는 기본 저장소
- CDN endpoint: 공개 리소스 캐시 전달
- thumbnail endpoint: 공개 버킷 전용 이미지 변환 레이어
- bucket 분리:
static,public은 공개,images,videos,bgm,files는 비공개 - TTL:
_tmp/prefix는 2주 후 자동 만료
이렇게 해두니 테스트에서 필요한 주요 흐름을 한 번에 검증할 수 있었습니다. 업로드, 공개 접근, 비공개 presigned URL, CDN 캐시, 썸네일 생성, 임시 파일 정리까지 모두 같은 스토리지 위에서 다룰 수 있게 된 것입니다.
Spring Boot 쪽도 기존 S3 연동 방식을 크게 바꾸지는 않았습니다. 로컬과 테스트 프로필에서는 endpoint만 온프레미스 S3 호환 주소로 바꾸고, path-style 접근과 공개/비공개 버킷 규칙을 맞추는 쪽으로 정리했습니다. 즉, 스토리지를 새로 만든 뒤에도 애플리케이션 코드는 큰 틀을 유지할 수 있었습니다.
이 점이 특히 좋았습니다. local, dev 환경에서는 SeaweedFS 같은 온프레미스 스토리지를 붙이고, 운영환경에서는 AWS S3나 R2로 바꾸더라도 애플리케이션이 붙는 인터페이스는 거의 그대로 유지할 수 있기 때문입니다. 스토리지 제품을 바꾸는 일이 곧바로 애플리케이션 저장소 추상화를 뒤엎는 일로 번지지 않는다는 뜻입니다.
다르게 말하면, 이번 구축의 핵심 가치는 단순히 스토리지를 한 대 올린 것이 아니라 환경마다 저장소 구현체는 달라도, 코드가 바라보는 인터페이스는 S3 호환으로 고정했다는 데 있었습니다. 이런 인터페이스는 테스트에도 좋고, 나중에 운영 저장소를 바꿀 때도 훨씬 유연합니다.
이번엔 AI가 어디까지 했나
이번 작업에서 흥미로웠던 부분은 SeaweedFS 자체보다, 이 과정을 AI가 어디까지 밀어줄 수 있느냐였습니다.
실제로는 아래 작업이 AI에 의해 전자동화됐고, 사람은 민감 정보 입력과 최종 검수만 맡았습니다.
- S3 호환 스토리지 후보 조사와 비교 초안 작성
MinIO,SeaweedFS,Ceph,RustFS비교 후 선택지 압축- Helm values, Nginx 캐시 매니페스트, TLS 구성 초안 작성
- 버킷 구조, TTL 정책, 테스트 절차, 운영 체크리스트 정리
- 배포 가이드와 트러블슈팅 문서 정리
이 경험에서 다시 확인한 것은, 인프라 작업도 요구사항과 보안 경계가 먼저 잡혀 있으면 AI가 꽤 깊숙이 자동화할 수 있다는 점입니다. 특히 이번처럼 초안 작성 -> 설정 파일 생성 -> 배포 절차 문서화 -> 실제 반영 흐름이 반복되는 작업에서는 체감 효율이 컸습니다.
마무리
이번 구축은 거창한 스토리지 플랫폼 도입기가 아닙니다. 테스트 때문에 반복적으로 쓰게 되는 S3를 조금 더 제 손 안으로 가져온 기록에 가깝습니다.
하지만 이 정도만으로도 충분히 의미가 있었습니다. 공개/비공개 리소스, CDN, presigned URL, 썸네일, TTL 같은 실제 운영 흐름을 외부 의존성 없이 더 자주 검증할 수 있게 됐기 때문입니다. 더 중요한 것은, local과 dev에서는 온프레미스, 운영에서는 S3나 R2를 같은 S3 호환 인터페이스 아래에서 선택할 수 있는 구조를 얻었다는 점입니다.
그리고 한 가지는 더 분명해졌습니다. AI는 코드 작성뿐 아니라, 이런 인프라 초안과 설정, 배포 문서까지도 꽤 높은 수준으로 전자동화할 수 있습니다. 사람이 해야 할 일은 여전히 남아 있지만, 적어도 이제는 어디까지 맡길 수 있는지가 훨씬 더 명확해졌습니다.
별첨
아래는 documents/workspace/infrastructure/S3_COMPATIBLE_STORAGE_RESEARCH.md 전문입니다.
S3 호환 오브젝트 스토리지 솔루션 리서치
작성일: 2025-12-30 목적: AWS S3 대체를 위한 자체 인프라 구축 솔루션 선정
배경
MinIO 현황 (2025년 12월)
MinIO는 더 이상 권장하지 않는다. 주요 이유:
-
라이선스 문제: Apache 2.0 → AGPL-3.0으로 변경
- 네트워크 서비스로 제공 시 소스코드 공개 의무
- 상업적 사용 시 연간 라이선스 $96,000부터 시작
-
메인터넌스 모드 전환 (2025년 12월)
- 새로운 기능, 개선사항, PR 수락 중단
- 보안 패치만 케이스별 평가 후 적용
- 커뮤니티 버전 바이너리 배포 중단 (소스코드만 제공)
-
관리 UI 제거
- 커뮤니티 에디션에서 관리 콘솔 기능 삭제
- 유료 버전에서만 풀 기능 제공
요구사항
| 항목 | 요구사항 |
|---|---|
| S3 호환성 | 필수 - AWS SDK 그대로 사용 가능해야 함 |
| 라이선스 | 상업적 사용 제약 없는 라이선스 선호 (Apache 2.0, MIT 등) |
| 저장 대상 | 이미지, BGM, 짧은 쇼츠 영상 |
| CDN 확장 | Nginx 리버스 프록시로 캐싱 CDN 구성 가능 |
| 배포 환경 | Kubernetes + Helm 차트 |
| 비용 | 자체 인프라로 AWS 비용 절감 |
솔루션 비교
1. SeaweedFS ⭐ (추천)
| 항목 | 내용 |
|---|---|
| 라이선스 | Apache 2.0 (상업적 사용 자유) |
| 언어 | Go |
| 아키텍처 | Master + Volume + Filer 구조 |
| 특징 | O(1) 디스크 seek, Facebook Haystack 아키텍처 기반 |
| S3 호환 | S3 Gateway 제공 (기본 S3 작업 완벽 지원) |
| Helm | 공식 Helm 차트 제공 + Kubernetes Operator |
| 성숙도 | 프로덕션 검증됨 (1.5PB+ 배포 사례) |
| 엔터프라이즈 | 25TB까지 무료, 이후 $1/TB/월 |
장점
- 수십억 개 파일 처리 가능 (미디어 저장에 최적)
- O(1) seek으로 소형 파일 빠른 접근
- Erasure Coding 지원으로 스토리지 효율성
- 클라우드 티어링 (Cold 데이터 자동 이관)
- FUSE 마운트, WebDAV, Hadoop 연동 지원
단점
- 고급 S3 기능 일부 미지원 (lifecycle 정책 등)
- 메타데이터 백업 필수 (Filer 메타데이터 손실 시 파일 고아됨)
Helm 설치
helm repo add seaweedfs https://seaweedfs.github.io/seaweedfs/helm
helm upgrade --install seaweedfs seaweedfs/seaweedfs -n storage --create-namespace
참고: SeaweedFS GitHub
2. RustFS
| 항목 | 내용 |
|---|---|
| 라이선스 | Apache 2.0 |
| 언어 | Rust |
| 성능 | MinIO 대비 4KB 오브젝트 2.3배 빠름 |
| S3 호환 | S3 API 완벽 지원 |
| Helm | 공식 Helm 차트 제공 |
| 성숙도 | ⚠️ 베타 (2025년 12월 기준 0.0.77) |
장점
- MinIO 마이그레이션 및 공존 지원
- 소형 오브젝트 성능 우수
- Apache 2.0 라이선스
단점
- 아직 베타 단계 (프로덕션 사용 주의)
- 문서화 및 커뮤니티 부족
Helm 설치
helm repo add rustfs https://charts.rustfs.com
helm install rustfs rustfs/rustfs -n rustfs --create-namespace \
--set ingress.className="nginx"
참고: RustFS GitHub
3. Ceph RGW (via Rook)
| 항목 | 내용 |
|---|---|
| 라이선스 | LGPL 2.1 |
| 언어 | C++ (데이터 경로), Go (Rook 오퍼레이터) |
| 아키텍처 | RADOS 기반 통합 스토리지 (Block + File + Object) |
| S3 호환 | 최고 수준 (s3-tests 576개 통과) |
| Helm | Rook Operator 제공 |
| 성숙도 | 엔터프라이즈급 (수년간 검증) |
장점
- S3 호환성 1위 (가장 많은 S3 API 지원)
- 블록, 파일, 오브젝트 스토리지 통합
- 멀티테넌시, 네임스페이스 격리
- Erasure Coding 고급 설정 가능
- 엑사바이트급 확장
단점
- 복잡한 설치 및 운영
- 높은 리소스 요구사항 (최소 3노드, 빠른 네트워크 필요)
- 학습 곡선 높음
Helm 설치 (Rook)
helm repo add rook-release https://charts.rook.io/release
helm install rook-ceph rook-release/rook-ceph -n rook-ceph --create-namespace
# CephCluster CRD 적용 필요
4. Garage
| 항목 | 내용 |
|---|---|
| 라이선스 | ⚠️ AGPL-3.0 (MinIO와 동일 문제) |
| 언어 | Rust |
| 특징 | 경량, 지리적 분산 배포 특화 |
| S3 호환 | 핵심 S3 작업 지원 (고급 기능 제한) |
| Helm | 커뮤니티 차트 |
| 성숙도 | 소규모 셀프호스팅에 적합 |
장점
- 가볍고 리소스 효율적
- 멀티 존/사이트 복제 기본 지원
- Rust 기반 메모리 안전성
단점
- AGPL-3.0 라이선스 (상업적 사용 제약)
- Erasure Coding 미지원 (3x 복제만)
- 고급 S3 기능 부족
참고: Garage
S3 호환성 비교
| 솔루션 | s3-tests 통과 | 평가 |
|---|---|---|
| Ceph RGW | 576개 | 최고 |
| Zenko CloudServer | 382개 | 우수 |
| MinIO | 321개 | 양호 |
| SeaweedFS | 56개 | 기본 |
SeaweedFS는 핵심 S3 작업(PUT, GET, DELETE, LIST 등)은 완벽 지원하지만, 고급 기능(Object Lock, Lifecycle 등)은 제한적
최종 추천
🥇 SeaweedFS (강력 추천)
추천 이유:
- Apache 2.0 라이선스 - 상업적 사용 완전 자유
- 미디어 저장 최적화 - 이미지, 오디오, 비디오 저장에 탁월
- 프로덕션 검증 - 수년간 대규모 배포 사례
- Kubernetes 친화적 - 공식 Helm 차트 및 Operator
- 합리적 가격 - 25TB까지 무료, 이후 월 $1/TB
🥈 차선책
| 상황 | 추천 |
|---|---|
| S3 완벽 호환 필요 | Ceph RGW (복잡성 감수) |
| 최신 기술 선호 & 실험적 | RustFS (베타 감수) |
| 소규모 & AGPL 무관 | Garage |
Nginx CDN 구성 가이드
SeaweedFS + Nginx 조합으로 CDN 구성 예시:
Nginx 캐시 설정
# /etc/nginx/nginx.conf
http {
# 캐시 저장소 설정
proxy_cache_path /var/cache/nginx/s3
levels=1:2
keys_zone=s3_cache:100m
max_size=50g
inactive=7d
use_temp_path=off;
upstream seaweedfs_s3 {
server seaweedfs-s3.storage.svc.cluster.local:8333;
keepalive 64;
}
server {
listen 80;
server_name cdn.example.com;
# 이미지 캐싱 (7일)
location ~* \.(jpg|jpeg|png|gif|webp|ico|svg)$ {
proxy_pass http://seaweedfs_s3;
proxy_cache s3_cache;
proxy_cache_valid 200 7d;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
add_header Cache-Control "public, max-age=604800";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 오디오/비디오 캐싱 (30일)
location ~* \.(mp3|wav|ogg|mp4|webm|m4a)$ {
proxy_pass http://seaweedfs_s3;
proxy_cache s3_cache;
proxy_cache_valid 200 30d;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
add_header Cache-Control "public, max-age=2592000";
# Range 요청 지원 (비디오 스트리밍)
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 기타 파일
location / {
proxy_pass http://seaweedfs_s3;
proxy_cache s3_cache;
proxy_cache_valid 200 1d;
add_header X-Cache-Status $upstream_cache_status;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
캐시 상태 헤더
HIT: 캐시에서 제공MISS: 원본에서 가져옴STALE: 만료된 캐시 제공 (원본 오류 시)UPDATING: 백그라운드 갱신 중
Kubernetes 배포 아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Ingress (Nginx) │ │
│ │ cdn.example.com │ │
│ └──────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ Nginx Cache Layer (CDN) │ │
│ │ /var/cache/nginx (PVC: 50Gi+) │ │
│ └──────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼───────────────────────────────────┐ │
│ │ SeaweedFS │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Master │ │ Volume │ │ Volume │ │ Volume │ │ │
│ │ │ (x3) │ │ #1 │ │ #2 │ │ #3 │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────────────▼────────────▼────────────▼────┐ │ │
│ │ │ Filer (S3 Gateway) │ │ │
│ │ │ seaweedfs-s3:8333 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Persistent Volumes │ │
│ │ Volume #1 Volume #2 Volume #3 │ │
│ │ (100Gi+) (100Gi+) (100Gi+) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
다음 단계
- SeaweedFS Helm 차트로 테스트 환경 구축
- S3 SDK 연동 테스트 (기존 코드 호환성 확인)
- Nginx 캐시 레이어 구성
- 성능 벤치마크 (이미지, 오디오, 비디오)
- 프로덕션 배포 계획 수립