前言
MinIO 说停就停,线上的文件服务还得接着跑。
2025 年 12 月,MinIO 官宣社区版进入维护模式。2026 年 2 月,仓库归档只读:不再有提交,CVE 不再修复,官方资源全面转向商业版 AIStor——订阅制,个人项目用不起。还在跑社区版的部署,等于抱着一个不再更新的黑盒。
youlai-boot(Spring Boot 权限管理后端)的文件存储一直跑在 MinIO 上,趁这次把迁移完整走了一遍:旧数据整目录复制、endpoint 和桶名不变,顺手把客户端换成 AWS SDK 标准实现。过程同样适用于任何 Docker 部署的 MinIO——包括 vue3-element-admin 配套环境在内。
本文看点:
- 选型判断:RustFS / SeaweedFS / Garage 什么场景选谁
- 客户端替换:minio SDK 换 AWS SDK v2,只改一个 Service 实现类
- 数据零丢失:旧目录整目录复制,旧数据原样保留当备份
一、环境准备
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 -v
# 复制数据要占空间,确认有约 2 倍旧数据的余量
df -h /data
# 查看旧 MinIO 的数据目录路径,2.2 节要用(本文示例为 /data/minio)
docker inspect minio --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
最后一项的输出就是旧数据目录,2.2 节从它复制。
二、迁移准备
整个迁移流程一张图:
2.1 停用 MinIO
# 容器删除,数据目录保留——迁移没验证完之前,随时可以回退
docker stop minio && docker rm minio
2.2 准备数据目录
RustFS 兼容 MinIO 磁盘格式(≥ 1.0.0-alpha.89),整目录复制即可,不走网络。旧数据在 /data/minio(1.3 节查到的路径),复制为平级的 /data/rustfs,旧目录原样保留,本身就是回退备份。
# cp 要求目标目录不存在
rm -rf /data/rustfs
# -a 保留属主和权限,直接整体复制
cp -a /data/minio /data/rustfs
# RustFS 容器以 UID 10001 运行,目录属主必须改成它,否则启动即崩
chown -R 10001:10001 /data/rustfs
复制过来后有一件事要提前知道:旧 AK/SK 不能用了。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 镜像声明了 USER 10001,以非 root 身份运行,而 bind mount 不转换权限——宿主机目录属主不是 10001 时,容器一启动就崩:
# [FATAL] Server runtime failed: Io error: Permission denied (os error 13)
2.2 已经 chown 10001 过副本,这里正常启动。10001 是镜像里声明的运行用户,可用 docker inspect rustfs/rustfs:latest --format '{{.Config.User}}' 查(官方 issue #2396)。
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 还能用,但它的维护方已经全面转向商业版。AWS SDK for Java v2 支持连接任意 S3 兼容存储,既然都动手迁移了,一起换掉:
<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 控制台,public 桶里能看到刚上传的图片文件,迁移端到端验证完成。
五、踩坑清单
| 现象 | 原因 | 解法 |
|---|---|---|
启动即崩,Permission denied (os error 13) | RustFS 容器以 UID 10001 运行,目录属主不匹配 | chown -R 10001:10001 数据目录 |
上传报 XAmzContentSHA256Mismatch | 新版 SDK 默认流式分块签名,旧版 MinIO 解析不了 | 旧版已归档不会修复,只能迁移 |
请求打到 桶名.服务器IP,连接失败 | AWS SDK 默认虚拟主机式寻址 | 设 forcePathStyle(true) |
| 上传报 403 AccessDenied | 旧 MinIO 的 Service Account 不随数据迁移 | 换成 RustFS 启动时设的 AK/SK |
上传报 NoSuchBucket | 全新空目录部署时桶不存在 | 手动建桶,或照 2.2 节从旧目录复制 |
| 外链图片 403 | 桶未开匿名读 | 桶访问策略设为「公开」(只读匿名,见 3.3 节) |
| 报校验和类错误 | AWS SDK 2025 起默认开启新校验和,个别 S3 兼容服务不支持 | AWS_REQUEST_CHECKSUM_CALCULATION=when_required |
六、总结
再主流的方案也可能停更,这次迁移值钱的是 S3 协议这层抽象:服务端换成谁都是配置级改动,客户端统一到 AWS SDK 后也不用再挑。
相关开源项目:
| 项目 | 简介 | 源码 |
|---|---|---|
| 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 兼容的替代方案切换成本不高,建议尽早迁移。