テスト用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へ切り替えたとしても、アプリケーションが向き合うインターフェースはほぼそのまま維持できるからです。ストレージ製品を変えることが、そのままアプリケーションの保存抽象化をひっくり返す話にまで広がらない、という意味でもあります。
言い換えると、今回の構築の中核的な価値は、単にストレージを1台立てたことではなく、환경마다 저장소 구현체는 달라도, 코드가 바라보는 인터페이스는 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の削除
- Community Editionから管理コンソール機能を削除
- フル機能は有料版のみで提供
要件
| 項目 | 要件 |
|---|---|
| S3互換性 | 必須 - AWS SDKをそのまま使えること |
| ライセンス | 商用利用に制約のないライセンスを優先 (Apache 2.0, MIT など) |
| 保存対象 | 画像、BGM、短いショート動画 |
| CDN拡張 | NginxリバースプロキシでキャッシュCDNを構成可能 |
| 配布環境 | Kubernetes + Helm chart |
| コスト | 自前インフラでAWSコストを削減 |
ソリューション比較
1. SeaweedFS ⭐ (推奨)
| 項目 | 内容 |
|---|---|
| ライセンス | Apache 2.0 (商用利用自由) |
| 言語 | Go |
| アーキテクチャ | Master + Volume + Filer 構造 |
| 特徴 | O(1) ディスク seek、Facebook Haystack アーキテクチャベース |
| S3互換 | S3 Gateway提供 (基本S3操作を完全サポート) |
| Helm | 公式Helm chart提供 + Kubernetes Operator |
| 成熟度 | 本番実績あり (1.5PB+ のデプロイ事例) |
| エンタープライズ | 25TB まで無料、以降 $1/TB/月 |
長所
- 数十億個のファイルを処理可能 (メディア保存に最適)
- O(1) seekで小さなファイルに高速アクセス
- Erasure Coding対応でストレージ効率が高い
- クラウドティアリング (Cold dataの自動移行)
- FUSEマウント、WebDAV、Hadoop連携をサポート
短所
- 高度なS3機能の一部は未対応 (lifecycle policy など)
- メタデータのバックアップが必須 (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 chart提供 |
| 成熟度 | ⚠️ ベータ (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互換性は最上位 (最も多くの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 | コミュニティchart |
| 成熟度 | 小規模セルフホスティングに適する |
長所
- 軽量でリソース効率が良い
- マルチゾーン/サイト複製を標準サポート
- 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 chartと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 chartでテスト環境を構築
- S3 SDK連携テスト (既存コードとの互換性確認)
- Nginxキャッシュ層を構成
- パフォーマンスベンチマーク (画像、音声、動画)
- 本番デプロイ計画を策定