前言
MinIO 说停就停,线上的文件服务还得接着跑。
2025 年 12 月,MinIO 官宣社区版进入维护模式。2026 年 2 月,仓库归档只读:不再有提交,CVE 不再修复,官方资源全面转向商业版 AIStor——订阅制,个人项目用不起。还在跑社区版的部署,等于抱着一个不再更新的黑盒。
youlai-boot(Spring Boot 权限管理后端)的文件存储一直跑在 MinIO 上,趁这次把迁移完整走了一遍:旧数据整目录复制、endpoint 和桶名不变,顺手把客户端换成 AWS SDK 标准实现。过程同样适用于任何 Docker 部署的 MinIO——包括 vue3-element-admin 配套环境在内。
本文看点:
- 选型判断:RustFS / SeaweedFS / Garage 什么场景选谁
- 标准客户端:顺手切 AWS SDK v2,跟协议不跟厂商
- 数据零丢失:旧目录整目录复制,旧数据原样保留当备份
一、环境准备
1.1 MinIO 停更时间线
先看事实,再谈迁移:
| 时间 | 事件 |
|---|---|
| 2025-12 | 官宣社区版进入维护模式:不收新功能和 PR,安全修复"视情况评估" |
| 2026-02 | GitHub 仓库归档只读,README 标注 "THIS REPOSITORY IS NO LONGER MAINTAINED" |
| 之后 | 官方资源全部转向商业版 AIStor,社区版已披露的 CVE 不再修复 |
1.2 替代品怎么选
主流开源替代有四个,先给结论表:
| 方案 | 协议 | 状态 | 适合场景 |
|---|---|---|---|
| SeaweedFS | Apache 2.0 | 生产验证 10 年 | 海量小文件,要久经考验 |
| RustFS | Apache 2.0 | 活跃迭代,较新 | 单容器部署,MinIO 迁移成本最低 |
| Garage | AGPL v3 | 生产可用 | 地理分布式集群 |
| Ceph RGW | LGPL | 企业级 | PB 级规模,单机太重 |
四个候选都宣称 S3 兼容,所以这不是区分度。真正的分水岭是部署心智模型:
- SeaweedFS 最成熟,但它是 Master / Volume / Filer 多角色架构,从 MinIO 迁过来要重新理解一套部署模型
- RustFS 单容器一条
docker run,9000 API + 9001 控制台,和 MinIO 的使用习惯完全一致 - RustFS 还直接兼容 MinIO 磁盘格式——旧数据整目录复制即可,数据不过网络(见 2.2 节),其他候选都得走网络搬运
我选 RustFS,理由是迁移路径最短:部署方式、端口约定、控制台操作全部延续,连磁盘数据格式都兼容,团队不需要学新东西,应用侧最小只换一对 AK/SK。
边界也要说清:RustFS 比 SeaweedFS 年轻,生产验证的厚度不如前者。PB 级核心存储选 SeaweedFS 或 Ceph 更稳;单机自用、中小规模,迁移成本这一票投给 RustFS。
1.3 迁移前检查
# Docker 环境
docker -v
# 磁盘空间:2.2 节整目录复制,需要约 2 倍旧数据的空间
df -h /data
# 记录旧 MinIO 数据目录路径(2.2 节复制数据用,本文示例为 /data/minio)
docker inspect minio --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
最后一项记下旧数据目录路径——它和新目录 /data/rustfs 平级对应,2.2 节从它复制数据。
二、迁移准备
整个迁移流程一张图:
流程只做一件事:数据先落位,服务后启动。不存在"先跑起来再迁数据"的折返——容器只启动一次,命令不重复执行。
2.1 停用 MinIO
# 容器删除,数据目录保留——迁移没验证完之前,随时可以回退
docker stop minio && docker rm minio
2.2 准备数据目录
旧数据在 /data/minio(1.3 节查到的路径),整目录复制为 /data/rustfs——两个目录平级独立,旧目录原样保留,天然就是回退备份。
# 清掉可能存在的空目录:复制要求目标目录不存在
rm -rf /data/rustfs
# 旧目录整体复制为新目录:-a 保留权限与时间戳
cp -a /data/minio /data/rustfs
# 副本属主还是 root(旧 MinIO 以 root 跑),对齐 RustFS 的运行身份
chown -R 10001:10001 /data/rustfs
为什么直接复制磁盘目录就行:先纠正一个直觉——MinIO 磁盘上不是"一个对象一个文件",ls /data/minio/public 看到的不是图片,而是一堆和对象同名的目录(里面是 xl.meta 元数据和分片),所以挑出"图片文件"复制行不通。但整目录复制可以:RustFS 自 1.0.0-alpha.89 起兼容 MinIO 磁盘格式(官方称 drop-in replacement,issue #2212),桶、对象、桶策略一并识别,数据不过网络。TB 级数据走网络搬运要数小时,目录复制只受磁盘速度限制(参考:2.3 TB 生产迁移实录)。
复制过来的数据,有三件事心里要有数:
- Access Key 不随数据迁移(社区实测反馈):旧密钥别指望能用,统一用启动时环境变量里的新 AK/SK——所以第四章的应用配置还是要改
- 兼容边界:桶元数据、对象(含标签/版本/对象锁)、生命周期、IAM 在迁移范围;站点复制、事件通知、LDAP/OIDC 不在
- 旧目录先别删:确认 3.2 验证通过后再清理,在那之前它就是现成的回退备份
三、启动 RustFS
3.1 启动容器
数据已就位,这一步只管把服务跑起来:
docker run -d \
--name rustfs \
--restart unless-stopped \
-p 9000:9000 \
-p 9001:9001 \
-v /data/rustfs:/data \
-e RUSTFS_ACCESS_KEY=rustfs-admin \
-e RUSTFS_SECRET_KEY='换成你的强密码' \
rustfs/rustfs:latest
端口含义和 MinIO 完全一致:9000 是 S3 API(应用连这个),9001 是 Web 控制台。云服务器安全组原来为 MinIO 放行过的话,一个端口都不用改。
数据目录属主必须是 10001。MinIO 官方镜像以 root 运行,挂哪都能写;RustFS 镜像在 Dockerfile 里声明了 USER 10001,容器进程以这个非 root 身份运行。而 bind mount 不转换权限——容器内的 UID 10001 写 /data 时,内核按宿主机目录的属主检查。属主没对齐时,容器一启动就崩:
# 启动即崩:[FATAL] Server runtime failed: Io error: Permission denied (os error 13)
2.2 已经对副本执行过 chown 10001,这里可以正常启动。
两个常见疑问。为什么不用
chmod 755?755 只给属主写权限,UID 10001 落在"其他人"位,照样不能写;用chmod 777能绕过但等于向服务器所有用户开放写权限。10001 哪来的?docker inspect rustfs/rustfs:latest --format '{{.Config.User}}'可查——镜像作者声明的普通用户区间(10000-60000)身份。官方 issue #2396 确认了这是非 root 容器的标准要求。
3.2 验证服务
docker ps | grep rustfs # 状态 Up 即成功
docker logs -f rustfs # 日志无报错
浏览器打开 http://服务器IP:9001,用启动时设置的 AK/SK 登录——控制台里应该直接看到旧 MinIO 的桶和文件。
3.3 开启匿名读
旧桶策略随数据带过来;如果是新建的桶(或旧桶没开过),博客图床、前端直链场景要开匿名下载。操作路径两步:
- 左侧菜单第一项对象浏览 → 存储桶列表 → 找到目标桶,点行末的设置按钮
- 进入桶详情后,在访问与共享 → 访问策略处点击编辑,改为公开,保存后出现“修改成功”提示即生效
不开的话,外链图片一律 403。注意“公开”是只读匿名访问——外链能拉取图片,但上传仍然要走 AK/SK 认证,安全性可控。
四、应用切换
4.1 切换 AWS SDK
io.minio:minio 是个 S3 协议客户端库,现在还能用,但命脉系于一家已全面转向商业版的公司。AWS SDK for Java v2 是 S3 生态的事实标准,官方支持连接任意 S3 兼容存储——既然都动手迁移了,一起换掉,以后不再挑 SDK:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<!-- 写作时最新版,实际以 Maven Central 为准 -->
<version>2.54.2</version>
</dependency>
完整实现源码:RustFSFileServiceImpl.java
file-storage:
type: s3 # 枚举改为 s3:跟协议走,不跟厂商走
s3:
endpoint: http://服务器IP:9000
access-key: rustfs-admin # RustFS 的新 AK
secret-key: 换成你的强密码 # RustFS 的新 SK
bucket: public # 数据已复制过来,桶还是原来那个
import software.amazon.awssdk.auth.credentials.*;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
@Bean
public S3Client s3Client(FileStorageProperties props) {
var s3 = props.getS3();
return S3Client.builder()
// 连自己的服务器,而非 AWS
.endpointOverride(URI.create(s3.getEndpoint()))
// 非 AWS 服务必填,值任意
.region(Region.US_EAST_1)
// IP 端点必须:桶名走路径不走子域名(见踩坑清单)
.forcePathStyle(true)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(s3.getAccessKey(), s3.getSecretKey())))
.build();
}
上传调用从 MinioClient 换成 S3Client.putObject,参数语义一一对应,改动集中在一个 Service 实现类(RustFSFileServiceImpl.java)里。
判断标准一句话:协议客户端跟协议走,不跟厂商走。S3 协议是 AWS 定的开放标准,比任何一家对象存储公司都活得久。
4.2 验证上传
# 重启应用
mvn spring-boot:run
方式一:打开 http://localhost:8000/doc.html,调文件上传接口,接口返回文件 URL 即成功。
方式二:打开vue3-element-admin前端「组件封装 → 图片上传」,选择图片上传;也可以直接访问有来在线环境:vue.youlai.tech/#/component…
上传成功后,复制返回的文件 URL 在浏览器打开——图片正常显示,说明后端到 RustFS 的链路通了。
最后回 RustFS 控制台,
public 桶里能看到刚上传的图片文件,迁移端到端验证完成。
五、踩坑清单
| 坑 | 现象 | 解法 |
|---|---|---|
| 目录属主不对 | 启动即崩,Permission denied (os error 13) | RustFS 以 UID 10001 非 root 运行,chown -R 10001:10001 数据目录(官方 issue #2396) |
| 旧密钥失效 | 沿用旧 AK/SK 上传报 403 AccessDenied | 旧 MinIO 的 Service Account 不会迁移,统一换 RustFS 启动时的新 AK/SK |
| SDK 没开 path-style | 请求域名解析成 桶名.服务器IP,连接失败 | AWS SDK 设 S3Configuration.pathStyleAccessEnabled(true)(旧版方法名 forcePathStyle),IP 端点必须走路径式 |
| 桶不存在 | 应用上传报 NoSuchBucket | 旧桶随数据复制带过来;只在全新空目录部署时才需手动重建 |
| 直接挑文件复制 | 复制完对象不显示:MinIO 磁盘上每个对象是目录(xl.meta+分片),不是文件 | 整目录 cp -a 复制,RustFS 兼容该格式(≥ 1.0.0-alpha.89) |
| 新版 SDK 签名报错 | XAmzContentSHA256Mismatch | 旧 MinIO 不支持流式签名,永不修复——该迁移了 |
| 外链图片 403 | 匿名拉取被拒 | 桶的访问策略设为「公开」(只读匿名访问),见 3.3 节 |
属主问题的根源是镜像安全模型的差异:MinIO 官方镜像以 root 运行,挂哪都能写;RustFS 以 UID 10001 的非 root 用户运行,宿主机目录必须先 chown 10001 再启动(官方 issue #2396 确认)。照着 MinIO 习惯迁移的人,几乎 100% 在这里崩一次。
403 的迷惑性最强:签名校验是过的,死在策略层。排查的人总以为是密钥打错了,其实是旧 Service Account 连同它绑定的桶策略,一起留在了旧系统里。
path-style 是 IP 直连的隐藏要求:AWS SDK 默认虚拟主机式寻址,把桶名拼进域名,public.服务器IP 这种地址 DNS 直接解析失败。旧 MinIO Java SDK 默认路径式所以无感,切 AWS SDK 后 pathStyleAccessEnabled(true) 就是必写项——4.1 装配代码里已带上。
最后一条是本文的引子:用新版 AWS SDK 向旧版 MinIO 上传,报 XAmzContentSHA256Mismatch。这不是配置错了——新版 SDK 默认走流式分块签名,旧版 MinIO 解析不了,而且永远不会修。遇到它别再排查配置,这是 MinIO 停更砸到你身上的第一块石头。
补一句新校验和的坑:AWS SDK 2025 年起默认开启新校验和,个别 S3 兼容服务也会报同族错误,兜底开关是环境变量 AWS_REQUEST_CHECKSUM_CALCULATION=when_required。
六、总结
对象存储的选型不是一锤子买卖,再主流的方案也可能停更。这次迁移真正值钱的不是 RustFS,而是 S3 协议这层抽象——服务端换成谁都是配置级改动;客户端统一到 AWS SDK 这个事实标准,以后连 SDK 都不用再挑。
后续可扩展:
- RustFS 分布式部署:多节点纠删码,数据可靠性再上一档
- 接入监控告警:桶容量、请求量的观测
相关开源项目:
| 项目 | 简介 | 源码 |
|---|---|---|
| youlai-boot | Spring Boot 4 权限管理后端,本文文件存储所在项目 | Gitee · GitHub · AtomGit |
| vue3-element-admin | 配套 Vue3 前端 | Gitee · GitHub · AtomGit |
在线体验:vue.youlai.tech(PC) · app.youlai.tech(移动端)
如果你的 MinIO 还在带病运行,别等下一个 CVE——S3 兼容的替代方案,切换成本比你想的低得多。