一直拿外部 S3 做测试太不合理了,所以我自己搭了基于 SeaweedFS 的存储
开始之前
在本地反复测试上传、presigned URL、公开 bucket、CDN 缓存和缩略图时,如果还一直为了测试去接外部 S3,整个流程会比想象中更不自然。问题不只是成本,更在于每次做实验都得绕经外部账号和资源,这一点一直让我不太舒服。
所以这次我换了个方向。测试中会反复使用的存储,干脆直接搬到本地部署环境里,而应用看到的接口则尽量保持 S3 그대로。因为如果在 local、dev 环境接入本地部署的 S3 兼容存储,而在生产环境里又能用同一套接口直接切换到 AWS S3 或 Cloudflare R2,这会是更合理的结构。
最终,我用 SeaweedFS + Nginx CDN + TLS + TTL 这套组合搭了一个按单节点规模运行的 S3 兼容存储。而且这次工作本身也是一个 AI 全自动化案例:从调研初稿、基础设施配置草案、manifest 编写,到部署流程整理,基本都由 AI 完成。本文会对真实密钥、Secret、内部路径、内部 IP 等敏感信息全部做脱敏处理。
为什么要自己搭
AWS S3 本身并没有问题。只是放到测试场景里,它越来越显得别扭。
- 我需要在 local 和开发环境里反复验证文件上传流程。
- 我需要区分公开 bucket 和私有 bucket,同时一起确认 CDN、presigned URL、缩略图这一层是否正常。
- 我有很多临时文件会以很短的周期被创建又删除。
- 每次都以外部存储为前提来做这些实验,对一个测试流程来说还是太重了。
我想要的不是一个庞大的存储平台,而是 테스트와 검증에 충분한 S3 호환성 和 간단한 온프레미스 운영성。更准确地说,我需要把开发者面对的存储契约固定成 S3 호환 인터페이스,然后按环境只替换后端实现。
为什么是 SeaweedFS
一开始我先看了最熟悉的选项,但以 2025 年末为准,已经很难把 MinIO 当作默认选择。许可证变更和社区运营方向的变化,让我觉得它并不适合长期、轻量地带着走。
相反,Ceph RGW 是一个完整得多的方案,但对这次目标来说又太重了。我需要的是按单节点规模运行的测试用存储和简单的媒体承载能力,而不是去运营一个大型存储集群。
所以最终的选择是 SeaweedFS。
- 它采用 Apache 2.0 许可证,商业约束很轻。
- 它提供 S3 Gateway,不需要大幅改动现有 SDK 流程。
- 在上传、下载、按 bucket 隔离、presigned URL 这些主要使用范围内,S3 接口基本可以原样保留。
- 基于 Helm 很容易直接部署到 Kubernetes。
- 它也很适合处理图片、音频、短视频这类小型媒体文件。
与其说我找到了一个完整的 S3 替代品,不如说是在这次目标需要的范围内,选了一个最简洁的 S3와 거의 같은 계약으로 움직이는 저장소。
实际是这样搭的
整体没有做得很复杂。以 SeaweedFS 为核心搭出 S3 兼容层,前面再挂一层 Nginx 缓存层。然后在上面补上 TLS、公开 bucket 专用缩略图层,以及临时文件 TTL 策略。
运行单元大致分成这样。
- S3 endpoint:应用直接接入的基础存储
- CDN endpoint:公开资源的缓存分发
- thumbnail endpoint:仅面向公开 bucket 的图片转换层
- bucket 划分:
static、public为公开,images、videos、bgm、files为私有 - TTL:
_tmp/前缀在 2 周后自动过期
这样一来,测试里需要的关键流程就能一次性全部验证。上传、公开访问、私有 presigned URL、CDN 缓存、缩略图生成、临时文件清理,都能放在同一套存储之上完成。
Spring Boot 这一侧也没有大改原有的 S3 接入方式。在 local 和测试 profile 中,只是把 endpoint 换成本地部署的 S3 兼容地址,并统一了 path-style 访问与公开/私有 bucket 规则。也就是说,即便重新搭了一套存储,应用代码的大框架依然可以保持不变。
这一点尤其好。在 local、dev 环境里接 SeaweedFS 这类本地部署存储,而在线上环境里改成 AWS S3 或 R2 时,应用连接的接口几乎可以保持原样。也就是说,更换存储产品这件事,不会立刻演变成把应用里的存储抽象整个推翻重来。
换句话说,这次搭建的核心价值,并不只是把一套存储拉起来,而是 환경마다 저장소 구현체는 달라도, 코드가 바라보는 인터페이스는 S3 호환으로 고정했다。这样的接口对测试很友好,将来要更换线上存储时也会灵活得多。
这次 AI 做到了什么程度
这次工作里更有意思的,其实不是 SeaweedFS 本身,而是 AI 究竟能把这个过程推进到多深。
实际下面这些工作都由 AI 全自动完成,人只负责填写敏感信息和做最终复核。
- S3 兼容存储候选方案的调研与对比初稿
- 对
MinIO、SeaweedFS、Ceph、RustFS做对比后收敛选型 - Helm values、Nginx 缓存 manifest、TLS 配置草案编写
- bucket 结构、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 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 数据)
- 支持 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
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 许可证
缺点
- 仍处于 beta 阶段(生产使用需谨慎)
- 文档和社区相对不足
Helm 安装
helm repo add rustfs https://charts.rustfs.com
helm install rustfs rustfs/rustfs -n rustfs --create-namespace \
--set ingress.className="nginx"
3. Ceph RGW (via Rook)
| 项目 | 内容 |
|---|---|
| 许可证 | LGPL 2.1 |
| 语言 | C++(数据路径)、Go(Rook Operator) |
| 架构 | 基于 RADOS 的统一存储(Block + File + Object) |
| S3 兼容 | 顶级(通过 576 项 s3-tests) |
| Helm | 提供 Rook Operator |
| 成熟度 | 企业级(多年验证) |
优点
- S3 兼容性第一(支持最多的 S3 API)
- 统一提供块、文件、对象存储
- 支持多租户与命名空间隔离
- 可做高级 Erasure Coding 配置
- 可扩展到 EB 级规模
缺点
- 安装和运维复杂
- 资源要求高(至少 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(接受 beta 风险) |
| 小规模且不介意 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 缓存层
- 做性能基准测试(图片、音频、视频)
- 制定生产部署计划