MinIO 停止维护怎么办?Docker 迁移 RustFS 实战:数据零丢失

29 阅读11分钟

前言

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-02GitHub 仓库归档只读,README 标注 "THIS REPOSITORY IS NO LONGER MAINTAINED"
之后官方资源全部转向商业版 AIStor,社区版已披露的 CVE 不再修复

1.2 替代品怎么选

主流开源替代有四个,先给结论表:

方案协议状态适合场景
SeaweedFSApache 2.0生产验证 10 年海量小文件,要久经考验
RustFSApache 2.0活跃迭代,较新单容器部署,MinIO 迁移成本最低
GarageAGPL v3生产可用地理分布式集群
Ceph RGWLGPL企业级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 节从它复制数据。


二、迁移准备

整个迁移流程一张图:

mermaid diagram

流程只做一件事:数据先落位,服务后启动。不存在"先跑起来再迁数据"的折返——容器只启动一次,命令不重复执行。

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 开启匿名读

旧桶策略随数据带过来;如果是新建的桶(或旧桶没开过),博客图床、前端直链场景要开匿名下载。操作路径两步:

  1. 左侧菜单第一项对象浏览 → 存储桶列表 → 找到目标桶,点行末的设置按钮
  2. 进入桶详情后,在访问与共享 → 访问策略处点击编辑,改为公开,保存后出现“修改成功”提示即生效

在这里插入图片描述

在这里插入图片描述

不开的话,外链图片一律 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-bootSpring Boot 4 权限管理后端,本文文件存储所在项目Gitee · GitHub · AtomGit
vue3-element-admin配套 Vue3 前端Gitee · GitHub · AtomGit

在线体验vue.youlai.tech(PC) · app.youlai.tech(移动端)

如果你的 MinIO 还在带病运行,别等下一个 CVE——S3 兼容的替代方案,切换成本比你想的低得多。