テクノロジー

テスト用S3を使い続けるのは不合理だったので、SeaweedFSベースのストレージを自前で構築しました

AAnonymous
13分で読めます

はじめに

ローカルでアップロード、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はもはや推奨しません。主な理由は次のとおりです。

  1. ライセンス問題: Apache 2.0 → AGPL-3.0 に変更

    • ネットワークサービスとして提供する場合、ソースコード公開義務が発生
    • 商用利用では年間ライセンスが $96,000 から開始
  2. メンテナンスモードへの移行 (2025年12月)

    • 新機能、改善、PR受け入れを停止
    • セキュリティパッチのみケースごとに評価して適用
    • コミュニティ版バイナリ配布を停止(ソースコードのみ提供)
  3. 管理UIの削除

    • Community Editionから管理コンソール機能を削除
    • フル機能は有料版のみで提供

参考: InfoQ - MinIO in Maintenance Mode


要件

項目要件
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インストール

Bash
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インストール

Bash
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件通過)
HelmRook Operator提供
成熟度エンタープライズ級 (長年の実績)

長所

  • S3互換性は最上位 (最も多くのS3 APIをサポート)
  • ブロック、ファイル、オブジェクトストレージを統合
  • マルチテナンシー、ネームスペース分離
  • Erasure Codingの高度設定が可能
  • エクサバイト級まで拡張可能

短所

  • インストールと運用が複雑
  • リソース要件が高い (最低3ノード、高速ネットワークが必要)
  • 学習コストが高い

Helmインストール (Rook)

Bash
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 적용 필요

参考: Rook Documentation


4. Garage

項目内容
ライセンス⚠️ AGPL-3.0 (MinIOと同じ問題)
言語Rust
特徴軽量、地理的分散デプロイに特化
S3互換コアS3操作をサポート (高度機能は制限あり)
Helmコミュニティchart
成熟度小規模セルフホスティングに適する

長所

  • 軽量でリソース効率が良い
  • マルチゾーン/サイト複製を標準サポート
  • Rustベースのメモリ安全性

短所

  • AGPL-3.0ライセンス (商用利用に制約)
  • Erasure Codingをサポートしない (3x複製のみ)
  • 高度なS3機能が不足

参考: Garage


S3互換性比較

ソリューションs3-tests通過数評価
Ceph RGW576件最高
Zenko CloudServer382件優秀
MinIO321件良好
SeaweedFS56件基本

SeaweedFSはコアS3操作(PUT, GET, DELETE, LIST など)は完全サポートしていますが、 高度機能(Object Lock, Lifecycle など)は限定的です


最終推奨

🥇 SeaweedFS (強く推奨)

推奨理由:

  1. Apache 2.0ライセンス - 商用利用が完全に自由
  2. メディア保存に最適化 - 画像、音声、動画の保存に強い
  3. 本番実績あり - 長年の大規模デプロイ事例
  4. Kubernetesとの親和性 - 公式Helm chartとOperator
  5. 妥当な価格 - 25TBまで無料、以降月額 $1/TB

🥈 次善策

状況推奨
S3の完全互換が必要Ceph RGW (複雑さは受容)
最新技術を好み、実験的でもよいRustFS (ベータを受容)
小規模でAGPLが問題にならないGarage

Nginx CDN構成ガイド

SeaweedFS + Nginx の組み合わせによるCDN構成例です。

Nginxキャッシュ設定

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: バックグラウンド更新中

参考: NGINX Caching Guide


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+)                │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

次のステップ

  1. SeaweedFS Helm chartでテスト環境を構築
  2. S3 SDK連携テスト (既存コードとの互換性確認)
  3. Nginxキャッシュ層を構成
  4. パフォーマンスベンチマーク (画像、音声、動画)
  5. 本番デプロイ計画を策定

参考資料