MinIO / 对象存储
本文范围
本文讲 MinIO 和对象存储在私有化环境里的落地用法:对象存储适合放什么,MinIO 怎么部署,bucket 怎么规划,access key 和 policy 怎么做最小权限,mc 命令怎么管理对象,上传下载怎么接业务系统,生命周期和备份怎么区分,反向代理和 HTTPS 怎么配置,容量和纠删码边界怎么判断,磁盘故障、权限错误、上传失败怎么排查。
本文不展开阿里云 OSS、腾讯云 COS、AWS S3 的全量产品手册。它们会作为边界参照出现,例如 S3 API、预签名 URL、bucket、policy、生命周期这些概念为什么相似,但不会逐项讲云厂商控制台、计费、跨区域复制、归档存储和 CDN 产品能力。
前置条件
默认读者有三类环境:
- 本地或测试环境:用 Docker 单机快速跑起来,理解对象、bucket、policy 和上传下载链路。
- 普通 Linux 服务器:用二进制、systemd 或 Docker/Compose 管理 MinIO,接 Nginx、HTTPS、内网域名和应用系统。
- 中国大陆公网、企业内网、完全离线环境:不能默认 Docker Hub、GitHub、Maven Central、官方下载站稳定可达,要提前准备镜像、二进制、
mc客户端、SDK 依赖、校验和、私有仓库和证书。
截至 2026-07-09,MinIO 的资料口径要特别校准:
- 开源 MinIO 仓库仍然以 S3-compatible object store 为定位,许可证是 GNU AGPL v3.0。
- MinIO 官方文档主站现在大量内容以 AIStor 命名,AIStor 使用 MinIO Software License。开源 MinIO、AIStor、旧版 MinIO 教程不要混着当成同一套交付物。
- GitHub 上开源 MinIO 的最新 release 显示为
RELEASE.2025-10-15T17-29-55Z,release note 建议通过源码安装或构建容器镜像。部署前必须同时校准 GitHub Release、安全公告、官方下载目录和你实际使用的镜像仓库 tag。 MINIO_ACCESS_KEY、MINIO_SECRET_KEY这类旧变量不要继续沿用,使用MINIO_ROOT_USER、MINIO_ROOT_PASSWORD。有些旧教程还会写MINIO_SERVER_URL,当前 AIStor 文档已经把它列入 deprecated 设置,反向代理场景优先确认MINIO_BROWSER_REDIRECT_URL和实际版本文档。
这几条看起来像版本细节,但它们会直接影响生产交付:你到底从哪里拿包、镜像是否存在、许可证是否满足公司要求、线上升级有没有安全补丁、旧配置项是否还会生效。
先把对象存储定位说清楚
对象存储不是“另一个文件夹”。它的核心模型是:
Endpoint
-> Bucket
-> Object Key
-> Object Data
-> Metadata
-> Version / Tag / ACL / Lifecyclebucket 是桶,类似一个顶层命名空间。object 是对象,通常就是一段二进制数据和元数据。object key 是对象名,可以写成 tenant-a/avatar/2026/07/09/xxx.png,但这里的 / 只是 key 字符串的一部分,不是文件系统目录。
对象存储适合:
| 场景 | 典型对象 | 为什么适合 |
|---|---|---|
| 业务附件 | 图片、PDF、合同、Excel、工单附件 | 文件数量多,业务库只保存元数据和 object key |
| 静态资源 | 图片、视频、安装包、前端构建产物 | 可以通过内网域名、网关、Nginx 或 CDN 分发 |
| 日志归档 | 历史日志、审计文件、压缩包 | 冷数据多,按生命周期清理或转存 |
| 备份落点 | MySQL 备份、配置备份、镜像包、巡检报告 | 适合跨机器、跨机房保存大文件 |
| 数据交换 | 导入文件、导出文件、离线批处理输入输出 | 可以用预签名 URL 降低后端转发压力 |
对象存储不适合直接当数据库,不适合当高频小文件 POSIX 文件系统,也不适合把应用运行时临时目录直接映射进去。它的访问方式是 HTTP/S3 API,读写语义和 open/write/rename/fsync 那套文件系统语义不一样。
和文件系统、块存储、云厂商对象存储的边界可以这样记:
| 类型 | 典型访问方式 | 适合 | 不适合 |
|---|---|---|---|
| 本地文件系统 | ls、open、write、挂载路径 | 单机程序、低延迟本地读写 | 跨服务共享、海量对象、权限隔离 |
| 块存储 | 云盘、SAN、裸盘 | 数据库、文件系统底层卷 | 直接给业务 HTTP 下载 |
| 对象存储 | S3 API、HTTP、SDK、mc | 附件、图片、备份、归档、大对象 | 强 POSIX 语义、数据库事务 |
| 云 OSS/COS/S3 | 云厂商控制台和 S3/兼容 API | 公有云托管、弹性容量、跨区域能力 | 私有化、离线、完全内网依赖 |
| MinIO | S3-compatible API、mc、SDK | 私有对象存储、内网文件服务、备份落点 | 想省掉磁盘、备份、监控、容量治理 |
MinIO 的价值不是“把文件上传到另一个地方”这么简单,而是把文件从业务服务和本地磁盘里拆出去:业务系统保存文件元数据,MinIO 保存对象数据,权限和生命周期按 bucket、prefix、policy 管理。
中国大陆、内网和离线环境先说清
部署 MinIO 前先把依赖闭环做出来。不要到现场再临时 docker pull、curl GitHub、mvn dependency:resolve。
交付物清单
建议至少准备这些内容:
| 交付物 | 用途 | 验证方式 |
|---|---|---|
| MinIO Server 二进制、deb/rpm 包或容器镜像 | 启动对象存储服务 | minio --version、docker image inspect |
mc 客户端二进制、deb/rpm 包或容器镜像 | 管理 bucket、policy、对象、生命周期 | mc --version |
| 镜像 digest 或 sha256 校验文件 | 防止离线包被替换或损坏 | sha256sum -c、docker image inspect --format '{{.RepoDigests}}' |
| Java/Python/Go/Node SDK 依赖 | 业务系统接入 | Maven/Nexus/npm/pip 私服解析成功 |
| Nginx 或负载均衡配置模板 | 暴露 API 和 Console | nginx -t、curl 上传测试 |
| TLS 证书和内部 CA | HTTPS、内网可信访问 | openssl s_client、浏览器证书链 |
| 初始化脚本 | 创建 bucket、user、policy、CORS、生命周期 | mc ls、mc admin user info、mc ilm rule ls |
| 恢复演练脚本 | 验证备份可恢复 | 隔离环境恢复并抽样下载对象 |
公网可达环境
公网可达时可以下载二进制和 mc,但仍然要固定版本并保存校验文件。以 Linux x86_64 为例:
mkdir -p /opt/offline/minio
cd /opt/offline/minio
curl -fLO https://dl.min.io/server/minio/release/linux-amd64/minio
curl -fLO https://dl.min.io/server/minio/release/linux-amd64/minio.sha256sum
curl -fLO https://dl.min.io/client/mc/release/linux-amd64/mc
curl -fLO https://dl.min.io/client/mc/release/linux-amd64/mc.sha256sum
sha256sum -c minio.sha256sum
sha256sum -c mc.sha256sum
chmod +x minio mc
./minio --version
./mc --version如果校验文件里记录的文件名和本地文件名不一致,不要忽略错误。先打开校验文件确认它校验的是 minio 还是带 release 后缀的文件,再用明确文件名校验:
sha256sum minio
cat minio.sha256sum离线包要记录:
组件: minio
版本: RELEASE.xxxx
架构: linux-amd64
获取渠道: 官方下载目录、企业制品仓库或源码构建记录
sha256: ...
获取时间: yyyy-MM-dd
验证人: ...容器镜像方案
容器部署不要只写 latest。应该固定 release tag,并在私有仓库里保存 digest。
如果使用已有官方镜像:
MINIO_IMAGE="quay.io/minio/minio:<fixed-release-tag>"
docker pull "$MINIO_IMAGE"
docker image inspect "$MINIO_IMAGE" --format '{{json .RepoDigests}}'
docker save -o minio-image.tar "$MINIO_IMAGE"
sha256sum minio-image.tar > minio-image.tar.sha256导入内网:
sha256sum -c minio-image.tar.sha256
docker load -i minio-image.tar
docker tag "$MINIO_IMAGE" registry.internal.example.com/base/minio:<fixed-release-tag>
docker push registry.internal.example.com/base/minio:<fixed-release-tag>如果 GitHub Release 提供的最新开源版本没有对应的现成容器镜像,就按 release note 从源码构建,再推送企业私有仓库:
git clone https://github.com/minio/minio.git
cd minio
git checkout RELEASE.2025-10-15T17-29-55Z
TAG=registry.internal.example.com/base/minio:RELEASE.2025-10-15T17-29-55Z make docker
docker push registry.internal.example.com/base/minio:RELEASE.2025-10-15T17-29-55Z构建机也要记录 Go 版本、构建日志、镜像 digest 和源码 commit。不要让“我在一台有网机器上构建过”变成唯一交付证据。
Java SDK 依赖闭环
业务系统如果用 Java 接入,先把 SDK 依赖同步到 Nexus 或企业 Maven 私服。官方 SDK 文档当前示例版本是 8.6.0,实际落地要结合 Maven Central、公司安全扫描和兼容性测试确定。
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.6.0</version>
</dependency>内网构建验证:
mvn -U -DskipTests dependency:tree | grep -i minio
mvn -o -DskipTests package-o 离线构建能通过,才说明依赖闭环真的做好了。
部署前规划
先规划好端口、域名、磁盘、目录和凭据。MinIO 很容易几条命令就跑起来,也很容易因为这些基础项没设计好,后面迁移一堆对象。
MinIO 私有对象存储落地全链路
部署前先把对象从业务上传到存储、再从存储被下载或备份的链路画清楚。链路清楚以后,端口、域名、凭据、反向代理、生命周期和备份策略才不会各配各的。
落地时把 9000 当业务数据面,把 9001 当管理面;两者的域名、访问控制、证书和审计策略最好从一开始就分开。
端口和域名
MinIO 常用两个端口:
| 端口 | 用途 | 是否建议公网暴露 |
|---|---|---|
9000 | S3 API,应用和 mc 访问对象 | 视业务需要,建议通过 HTTPS 和网关控制 |
9001 | Console 管理界面 | 不建议直接公网暴露,至少加 VPN、内网、堡垒机或访问控制 |
生产环境建议使用两个域名:
s3.example.com -> S3 API -> 9000
minio-console.example.com -> Console -> 9001不要把 Console 和业务下载混在一个路径下强行转发。路径转发可以做,但排障复杂,尤其是 Console 重定向、WebSocket、预签名 URL、Host 头、CORS 和 HTTPS 方案混在一起时。
目录和磁盘
单机示例可以这样规划:
/data/minio/
data/ # 对象数据
config/ # 环境文件、初始化脚本、备份导出
logs/ # 运行日志或外部采集目录生产环境要把数据目录放在明确的数据盘上,不要放在容器可写层,不要放在系统盘根目录,也不要混用应用日志、数据库、备份和 MinIO 数据。
检查磁盘:
lsblk -f
df -hT /data/minio
mount | grep /data/minio如果是裸盘部署,优先在上线前确定文件系统、挂载参数、开机挂载和权限。磁盘扩容、替换、快照都要写进运维手册。
root 凭据
MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 是 root 管理账号。它只适合初始化和平台管理,不应该给业务系统使用。
生成强密码:
openssl rand -base64 32
openssl rand -hex 24至少遵守:
- root 凭据放到密钥系统、受控环境变量或受控配置文件,不写进 Git。
- 业务系统使用独立 user 或 access key。
- 不同业务、不同环境、不同权限边界使用不同凭据。
- 凭据轮换要有停机窗口或灰度切换方案。
- 离职、系统下线、应用迁移后及时禁用或删除旧 key。
Docker 单机部署
Docker 单机适合学习、开发、测试、小规模内网文件服务,不要把“一个容器加一个目录”误认为高可用对象存储。
准备目录
sudo mkdir -p /data/minio/data
sudo mkdir -p /data/minio/config
sudo mkdir -p /data/minio/logs
sudo chown -R 1000:1000 /data/minio如果镜像内用户不是 1000:1000,按实际镜像调整。权限不确定时先用测试容器验证写入:
docker run --rm -v /data/minio/data:/data alpine:3.20 sh -c 'id && touch /data/.write-test && ls -l /data/.write-test'直接 docker run
MINIO_IMAGE="registry.internal.example.com/base/minio:<fixed-release-tag>"
docker run -d \
--name minio \
--restart unless-stopped \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER="minio-root" \
-e MINIO_ROOT_PASSWORD="replace-with-strong-password" \
-v /data/minio/data:/data \
"$MINIO_IMAGE" \
server /data --address ":9000" --console-address ":9001"验证容器:
docker ps --filter name=minio
docker logs --tail=100 minio
curl -f http://127.0.0.1:9000/minio/health/live
curl -f http://127.0.0.1:9000/minio/health/ready浏览器访问:
http://服务器IP:9001看到登录页不代表生产可用。还要继续验证 bucket、上传、下载、权限和反向代理。
Compose 示例
services:
minio:
image: registry.internal.example.com/base/minio:<fixed-release-tag>
container_name: minio
restart: unless-stopped
command: server /data --address ":9000" --console-address ":9001"
environment:
MINIO_ROOT_USER: minio-root
MINIO_ROOT_PASSWORD: replace-with-strong-password
MINIO_BROWSER_REDIRECT_URL: https://minio-console.example.com
ports:
- "9000:9000"
- "9001:9001"
volumes:
- /data/minio/data:/data
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"启动:
docker compose up -d
docker compose ps
docker compose logs --tail=100 minio如果端口起不来:
ss -lntp | grep -E ':9000|:9001'
docker logs minio单机 Docker 的边界
单机单盘没有纠删码,没有高可用,磁盘坏了就是故障。单机多盘可以有纠删码能力,但机器故障、主板故障、系统盘故障、机房断电仍然会让服务不可用。
小规模业务可以先从单机开始,但要提前写清楚:
- 数据盘如何备份。
- MinIO 配置和 policy 如何导出。
- 备份多久演练一次。
- 数据量增长到多少必须迁移到分布式或托管对象存储。
- 业务系统如何切换 endpoint。
Linux systemd 部署
如果不想用 Docker,可以用二进制或 deb/rpm 包配合 systemd。下面用通用二进制方式示例。
安装二进制
sudo install -m 755 minio /usr/local/bin/minio
sudo install -m 755 mc /usr/local/bin/mc
minio --version
mc --version创建用户和目录:
sudo groupadd --system minio-user
sudo useradd --system --shell /sbin/nologin --gid minio-user minio-user
sudo mkdir -p /data/minio/data
sudo mkdir -p /etc/minio
sudo chown -R minio-user:minio-user /data/minio
sudo chmod 750 /data/minio
sudo chmod 750 /etc/minio写环境文件:
sudo tee /etc/default/minio >/dev/null <<'EOF'
MINIO_ROOT_USER="minio-root"
MINIO_ROOT_PASSWORD="replace-with-strong-password"
MINIO_VOLUMES="/data/minio/data"
MINIO_OPTS="--address :9000 --console-address :9001"
MINIO_BROWSER_REDIRECT_URL="https://minio-console.example.com"
EOF
sudo chmod 640 /etc/default/minio写 systemd unit:
sudo tee /etc/systemd/system/minio.service >/dev/null <<'EOF'
[Unit]
Description=MinIO Object Storage
Documentation=https://min.io/docs/
Wants=network-online.target
After=network-online.target
[Service]
User=minio-user
Group=minio-user
EnvironmentFile=-/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=1048576
TasksMax=infinity
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
EOF启动:
sudo systemctl daemon-reload
sudo systemctl enable --now minio
systemctl status minio --no-pager
journalctl -u minio -n 100 --no-pager健康检查:
curl -f http://127.0.0.1:9000/minio/health/live
curl -f http://127.0.0.1:9000/minio/health/ready防火墙
只在需要的网段放通:
sudo firewall-cmd --permanent --add-port=9000/tcp
sudo firewall-cmd --permanent --add-port=9001/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports如果 Console 只允许内网访问,就不要在公网安全组里放 9001/tcp。管理入口越少,后面排查安全风险越省心。
安装和配置 mc
mc 是 MinIO 日常管理的主工具。它不只管 MinIO,也能操作 S3-compatible 服务。本文示例把当前 MinIO 配成 local alias。
mc alias set local http://127.0.0.1:9000 minio-root replace-with-strong-password
mc alias ls
mc admin info local
mc ready local如果通过 HTTPS 域名访问:
mc alias set prod https://s3.example.com minio-root replace-with-strong-password
mc admin info prod自签名证书或内部 CA 不被信任时,不要长期使用 --insecure。正确做法是把企业根证书导入操作系统信任区、容器镜像信任区和 JVM truststore。临时排障可以用:
mc --insecure alias set prod https://s3.example.com minio-root replace-with-strong-password但脚本里长期写 --insecure,等于把证书校验这道门拆掉了。
Bucket 和对象管理
创建 bucket
mc mb local/app-private
mc mb local/app-public
mc mb --with-versioning local/app-backup
mc ls local
mc version info local/app-backupbucket 命名建议:
- 用小写字母、数字、短横线。
- 不要用中文、空格、下划线和业务密语。
- 不要把环境混在同一个 bucket,例如
prod-app和test-app分开。 - 不要把所有业务都塞进
files、upload这种泛名 bucket。
上传和下载
准备测试文件:
echo "hello minio" > hello.txt上传:
mc cp hello.txt local/app-private/demo/hello.txt
mc ls local/app-private/demo/
mc stat local/app-private/demo/hello.txt下载:
mc get local/app-private/demo/hello.txt ./hello-download.txt
cat hello-download.txt递归上传目录:
mkdir -p upload-demo/images
echo "a" > upload-demo/a.txt
echo "b" > upload-demo/images/b.txt
mc cp --recursive upload-demo/ local/app-private/upload-demo/
mc ls --recursive local/app-private/upload-demo/同步目录:
mc mirror ./upload-demo/ local/app-private/upload-demo/
mc diff ./upload-demo/ local/app-private/upload-demo/mc cp 更像复制一次,mc mirror 更适合同步目录。带 --remove 的 mirror 会删除目标端多余对象,生产脚本里必须谨慎使用。
删除对象
删除单个对象:
mc rm local/app-private/demo/hello.txt删除 prefix:
mc rm --recursive --force local/app-private/upload-demo/删除 bucket:
mc rb local/empty-bucket不要用删除命令做“清空过期数据”的长期方案。过期数据用 lifecycle 管,误删风险和审计都更清楚。
object key 不是目录
上传这个对象:
mc cp hello.txt local/app-private/a/b/c/hello.txt你看到的 a/b/c/ 只是前缀。对象存储没有真正的目录 inode,也没有传统文件系统里的 rename 目录操作。业务系统设计 object key 时要把 key 当稳定 ID,而不是把它当能随便移动的文件路径。
推荐 key 设计:
tenant/{tenantId}/biz/{bizType}/{yyyy}/{MM}/{dd}/{uuid}.{ext}不要直接用用户上传的原始文件名当 key:
../../etc/passwd
合同(最终)(1)(新).docx
张三身份证正反面.png原始文件名可以放业务库元数据里,object key 应该由系统生成。
权限和 Policy
对象存储权限的基本原则是:root 只做管理,业务账号只拿自己需要的 bucket 和 prefix 权限。
创建只允许访问指定 bucket 的 policy
下面这个 policy 允许对 app-private 下 tenant-a/ 前缀读写对象,并允许列 bucket 时看到这个前缀。
cat > app-tenant-a-readwrite.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::app-private"
],
"Condition": {
"StringLike": {
"s3:prefix": [
"tenant-a/*"
]
}
}
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::app-private/tenant-a/*"
]
}
]
}
EOF
mc admin policy create local app-tenant-a-readwrite app-tenant-a-readwrite.json
mc admin policy info local app-tenant-a-readwrite创建用户并绑定 policy:
APP_SECRET="$(openssl rand -base64 32)"
mc admin user add local app-tenant-a "$APP_SECRET"
mc admin policy attach local app-tenant-a-readwrite --user app-tenant-a
mc admin user info local app-tenant-a用业务账号验证:
mc alias set tenant-a http://127.0.0.1:9000 app-tenant-a "$APP_SECRET"
mc cp hello.txt tenant-a/app-private/tenant-a/hello.txt
mc get tenant-a/app-private/tenant-a/hello.txt ./tenant-a-hello.txt
mc cp hello.txt tenant-a/app-private/tenant-b/hello.txt最后一条应该失败。如果没有失败,说明 policy 范围过大。
access key 管理
新版本 mc 支持管理用户 access key。可以给一个用户生成更细粒度或便于轮换的 key:
mc admin accesskey create local app-tenant-a
mc admin accesskey ls local app-tenant-a
mc admin accesskey info local <access-key>禁用和启用:
mc admin accesskey disable local <access-key>
mc admin accesskey enable local <access-key>删除:
mc admin accesskey rm local <access-key>业务系统里不要共用 root key,也不要把一个 key 发给多个应用。一个 key 失控时,你需要能快速判断它影响哪个系统、哪个 bucket、哪个 prefix。
匿名访问
公开下载要非常克制。一般只给静态资源 bucket 或某个 prefix 开 anonymous download。
mc anonymous set download local/app-public
mc anonymous get local/app-public验证:
mc cp hello.txt local/app-public/hello.txt
curl -I http://127.0.0.1:9000/app-public/hello.txt关闭匿名访问:
mc anonymous set none local/app-public不要为了让前端能上传,把整个 bucket 改成 public readwrite。浏览器上传应该走预签名 URL 或后端代理上传。
CORS
浏览器直传或浏览器直接下载时,需要配置 CORS。示例只允许业务域名发起 GET、PUT、HEAD:
cat > cors-app-private.xml <<'EOF'
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>https://app.example.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
<MaxAgeSeconds>3600</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
EOF
mc cors set local/app-private cors-app-private.xml
mc cors get local/app-private如果浏览器报 CORS,不要只看后端日志。打开浏览器 DevTools,看 OPTIONS 预检请求、响应头、状态码和 Access-Control-Allow-*。
上传下载和内网文件服务
常见接入方式有三种。
后端代理上传
流程:
浏览器/客户端 -> 业务后端 -> MinIO优点:
- 业务鉴权、文件大小、文件类型、病毒扫描、元数据写库都在后端控制。
- 前端不接触对象存储 endpoint 和任何 key。
- 排障简单,所有失败都能在后端日志里看到。
缺点:
- 大文件会占用后端带宽和连接。
- 后端要处理超时、限流、临时文件、流式转发。
- 多服务上传时容易重复造轮子。
适合:管理后台、小文件、内网系统、对安全控制要求高的业务。
预签名 URL 上传
流程:
浏览器 -> 业务后端申请上传 URL
业务后端 -> MinIO 生成 presigned PUT/POST URL
浏览器 -> MinIO 直传
浏览器/回调 -> 业务后端确认元数据命令行生成一次上传 URL:
mc share upload --expire 30m local/app-private/tenant-a/demo-upload.txt生成下载 URL:
mc share download --expire 30m local/app-private/tenant-a/demo-upload.txt优点:
- 大文件不经过业务后端。
- URL 有过期时间。
- 可以按对象 key、Content-Type、权限策略做限制。
缺点:
- CORS、HTTPS、反向代理、客户端时间、签名参数都要正确。
- 业务后端要处理“对象传上去了,但元数据写库失败”的补偿。
- URL 泄露后,在过期前可能被使用。
生产建议:
- 预签名 URL 有效期按分钟级设计,不要给几天。
- object key 由后端生成,不让前端自由决定最终路径。
- 上传完成后后端用
statObject或mc stat验证对象存在、大小、content type、etag。 - 元数据表记录
bucket、object_key、size、sha256、content_type、owner_id、status。 - 元数据写库失败时,把对象标记为 orphan 并定时清理,或者立即删除对象。
预签名上传权限链路图
这条链路适合在业务接入评审时单独画出来。预签名上传不是“前端拿一个 URL 直接传”这么简单,它背后有业务鉴权、临时权限、bucket policy、对象 key、过期时间、请求约束和上传后校验。下面这张图可以作为上传接口评审和泄漏风险排查的检查入口:
上线前按这张图复核:前端不能自定义最终 object key,业务服务必须先鉴权再签名,签名 URL 要有短过期,上传完成后用 statObject 或 mc stat 校验大小、类型和元数据。排障时如果报 SignatureDoesNotMatch,优先查外部 endpoint、Host、scheme、时间和 URL 是否被二次编码;如果对象存在但业务库没有记录,就按 orphan 清理或补偿任务处理。
内网文件服务
内网文件服务通常有两种做法:
- 私有 bucket + 后端鉴权下载:用户请求业务系统,业务系统校验权限后返回文件流或短期下载 URL。
- 公开只读 bucket + 内网域名:适合静态资源、安装包、无敏感内容的公共下载。
不要把 Console 当文件下载入口。Console 是管理工具,不是业务文件服务。
内网文件服务最小验证:
mc cp hello.txt local/app-public/downloads/hello.txt
curl -f http://s3.example.internal/app-public/downloads/hello.txt如果通过 Nginx HTTPS 访问:
curl -k -I https://s3.example.internal/app-public/downloads/hello.txt-k 只用于排障。如果必须 -k 才能访问,说明客户端还没有信任内部 CA。
生命周期、版本和对象锁
生命周期不是备份。它解决的是对象过期、非当前版本清理、冷数据转移,不解决误删后一定能找回的问题。
开启 versioning
mc version enable local/app-private
mc version info local/app-private查看版本:
mc ls --versions local/app-private/tenant-a/暂停 versioning:
mc version suspend local/app-private暂停不会自动删除历史版本。历史版本占用容量,必须配合生命周期清理。
生命周期规则
90 天后删除 tmp/ 前缀下对象:
mc ilm rule add --prefix "tmp/" --expire-days 90 local/app-private
mc ilm rule ls local/app-private版本 bucket 中,30 天后删除非当前版本:
mc ilm rule add --noncurrent-expire-days 30 local/app-private
mc ilm rule ls local/app-private导出生命周期配置:
mc ilm rule export local/app-private > app-private-lifecycle.json删除生命周期规则前先列出 rule id:
mc ilm rule ls local/app-private
mc ilm rule rm --id "<rule-id>" local/app-private不要一上来给生产 bucket 配 --expire-days 7。先在测试 bucket 验证,再对生产 prefix 小范围启用,最后观察对象数量和业务访问日志。
对象锁
对象锁用于 WORM、合规保留、防止被删除。它不是普通业务默认配置。需要对象锁的 bucket 通常要在创建时开启:
mc mb --with-lock local/audit-archive对象锁一旦和合规保留策略结合,删除会受到严格限制。不要在没想清楚保留周期、合规要求、恢复流程前给普通业务 bucket 开锁。
备份和恢复
MinIO 经常有两个角色:
- MinIO 作为备份落点:数据库、配置、日志、镜像包备份到 MinIO。
- MinIO 自己也需要备份:bucket 数据、policy、user、生命周期、CORS、访问凭据和部署配置都要可恢复。
这两个角色不要混淆。把 MySQL 备份放进 MinIO,不代表 MinIO 自己就不用备份。
把备份上传到 MinIO
示例:上传 MySQL 备份目录。
BACKUP_DATE="$(date +%Y%m%d_%H%M%S)"
mc mb --ignore-existing local/ops-backup
mc cp /backup/mysql/full/${BACKUP_DATE}/all-databases.sql.gz \
local/ops-backup/mysql/full/${BACKUP_DATE}/all-databases.sql.gz
mc cp /backup/mysql/full/${BACKUP_DATE}/all-databases.sql.gz.sha256 \
local/ops-backup/mysql/full/${BACKUP_DATE}/all-databases.sql.gz.sha256
mc stat local/ops-backup/mysql/full/${BACKUP_DATE}/all-databases.sql.gz批量同步:
mc mirror /backup/mysql/full/ local/ops-backup/mysql/full/
mc diff /backup/mysql/full/ local/ops-backup/mysql/full/备份脚本必须输出日志:
mc mirror /backup/mysql/full/ local/ops-backup/mysql/full/ \
>> /var/log/minio-backup-upload.log 2>&1失败要告警,不要只靠定时任务默默跑。
MinIO 到异地 MinIO
简单同步:
mc alias set prod https://s3.example.com "$PROD_AK" "$PROD_SK"
mc alias set dr https://s3-dr.example.com "$DR_AK" "$DR_SK"
mc mb --ignore-existing dr/app-private
mc mirror --overwrite prod/app-private/ dr/app-private/
mc diff prod/app-private/ dr/app-private/如果使用 --remove:
mc mirror --overwrite --remove prod/app-private/ dr/app-private/它会删除目标端多余对象。只有当你确认目标端就是镜像副本,且有误删保护时,才应该使用。
导出配置和元数据
对象数据之外,还要保存管理元数据:
mkdir -p minio-metadata-backup/policies
mkdir -p minio-metadata-backup/buckets
mc admin config export local > minio-metadata-backup/admin-config.json
mc admin policy ls local > minio-metadata-backup/policy-list.txt
mc admin user ls local > minio-metadata-backup/user-list.txt
mc admin policy info local app-tenant-a-readwrite \
--policy-file minio-metadata-backup/policies/app-tenant-a-readwrite.json
mc version info local/app-private > minio-metadata-backup/buckets/app-private-version.txt
mc ilm rule export local/app-private > minio-metadata-backup/buckets/app-private-lifecycle.json
mc cors get local/app-private > minio-metadata-backup/buckets/app-private-cors.xml注意:用户 secret key 通常无法从服务端反查。密钥必须在创建时进入密钥管理系统。只备 mc admin user info,不能恢复业务系统的 secret。
恢复演练
恢复演练不要在生产 bucket 上做。准备隔离环境:
mc alias set restore http://restore-minio:9000 "$RESTORE_AK" "$RESTORE_SK"
mc mb --ignore-existing restore/app-private
mc mirror ./restore-data/app-private/ restore/app-private/
mc ls --recursive restore/app-private/ | head抽样校验:
mc get restore/app-private/tenant-a/demo/hello.txt ./restore-hello.txt
sha256sum ./restore-hello.txt恢复演练记录至少包括:
- 备份对象数量和总大小。
- 传输耗时。
- 恢复耗时。
- 抽样校验结果。
- policy、CORS、lifecycle 是否恢复。
- 业务系统切换 endpoint 后是否可上传、可下载、可鉴权。
反向代理和 HTTPS
生产环境不要让用户直接访问 http://ip:9000。至少应该通过 Nginx 或负载均衡提供 HTTPS endpoint。
Nginx 反向代理示例
S3 API:
upstream minio_api {
server 127.0.0.1:9000;
}
server {
listen 443 ssl http2;
server_name s3.example.com;
ssl_certificate /etc/nginx/certs/s3.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/s3.example.com/privkey.pem;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_pass http://minio_api;
}
}Console:
upstream minio_console {
server 127.0.0.1:9001;
}
server {
listen 443 ssl http2;
server_name minio-console.example.com;
ssl_certificate /etc/nginx/certs/minio-console.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/minio-console.example.com/privkey.pem;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://minio_console;
}
}检查并加载:
sudo nginx -t
sudo systemctl reload nginx
curl -I https://s3.example.com/minio/health/live
curl -I https://minio-console.example.comclient_max_body_size 0、proxy_buffering off、proxy_request_buffering off 是大文件上传下载的关键。没有这些配置时,常见现象是:
- Nginx 返回
413 Request Entity Too Large。 - 大文件上传到一半连接断开。
- Nginx 先把请求体缓冲到本地磁盘,磁盘被临时文件打满。
- 客户端超时,但 MinIO 服务端日志不一定有明显错误。
Console 重定向
如果 Console 放在反向代理后面,设置:
MINIO_BROWSER_REDIRECT_URL="https://minio-console.example.com"修改后重启:
sudo systemctl restart minioDocker 则重建或重启容器:
docker compose up -d旧教程里常见的 MINIO_SERVER_URL 在当前 AIStor 文档里已经标记为 deprecated。存量开源 MinIO 版本是否需要它,要按你实际版本文档和验证结果判断,不要无脑复制。
HTTPS 验证
证书链:
openssl s_client -connect s3.example.com:443 -servername s3.example.com </dev/nullS3 API:
mc alias set https-prod https://s3.example.com "$AK" "$SK"
mc ls https-prod
mc cp hello.txt https-prod/app-private/tenant-a/https-test.txt
mc get https-prod/app-private/tenant-a/https-test.txt ./https-test.txt如果 mc 正常、Java SDK 不正常,优先检查 JVM truststore 是否信任内部 CA。
容量规划和纠删码边界
MinIO 的高可用能力来自分布式部署和纠删码,不来自“容器 restart always”。
单机单盘
1 台机器 + 1 块数据盘特点:
- 部署简单。
- 没有纠删码。
- 磁盘坏、机器坏、系统坏都会影响服务。
- 只能依赖外部备份恢复。
适合开发、测试、小规模非核心内网文件服务。
单机多盘
1 台机器 + 多块数据盘特点:
- 可以使用纠删码承受部分磁盘故障。
- 机器故障时整个服务不可用。
- 不等于高可用。
适合预算有限、但希望比单盘更抗磁盘故障的场景。仍然必须做异机备份。
分布式多节点
N 台机器 + 每台多块数据盘 + 负载均衡特点:
- 任意节点可以处理请求,前面放负载均衡统一入口。
- 通过纠删码在磁盘或节点故障时保持读写能力,具体取决于拓扑和 parity。
- 需要统一时间、DNS、证书、磁盘、网络、监控和升级流程。
官方文档强调:纠删码至少需要两块 drive。单节点单盘只适合非生产测试。纠删码的 parity 可以在 erasure set 大小的 0 到 1/2 之间设置。比如 erasure set 有 16 块盘,最高 parity 可以到 EC:8。
容量粗算:
原始容量 = 磁盘数量 * 单盘容量
可用容量约等于 原始容量 * 数据分片数 / (数据分片数 + 校验分片数)例如 16 块盘,EC:4 可以粗略理解为 12 份数据、4 份校验,可用比例约为 12/16 = 75%。这只是粗算,实际还要扣除文件系统、元数据、小对象、版本、删除标记、生命周期延迟和预留水位。
生产容量建议:
- 不要按 100% 可用容量规划,至少预留 20% 到 30% 水位。
- 开启 versioning 后,容量增长可能远超当前对象大小。
- 大量小对象会带来更高元数据和扫描成本。
- 备份 bucket、日志 bucket、临时上传 bucket 要单独估算。
- 先用真实对象大小分布做压测,不要只用一个大文件测吞吐。
MinIO 分布式存储与故障恢复图
分布式 MinIO 的设计评审要把 server pool、erasure set、drive 和 quorum 讲清楚。很多事故不是 MinIO 不会修,而是团队只知道有“纠删码”,却不知道当前 parity 能扛几块盘、heal 要看什么、替换磁盘后怎么确认对象已经恢复。
上线检查时,用 mc admin info 确认节点、磁盘、pool 和可用容量,用 mc admin heal -r --dry-run 或 mc admin heal 观察 healing 状态,用 mc stat 抽样确认对象元数据。排障时先确认故障盘位、挂载点和序列号,再替换磁盘;不要在 heal 期间做大规模删除、迁移或压测。parity 越高,容错越强但可用容量越低,这个取舍要在容量预算阶段确认,而不是等磁盘坏了再讨论。
扩容边界
生产扩容不是随便给已有目录加一块盘。分布式 MinIO 通常按 server pool 扩展,新 pool 要满足当前纠删码和 parity 要求。扩容还要同步更新负载均衡、DNS、监控、告警和备份策略。
扩容前至少确认:
mc admin info prod
mc admin prometheus generate prod
mc du --recursive prod/app-private并记录:
- 当前节点、磁盘、容量、水位。
- 当前 erasure set 和 parity。
- 新增节点和磁盘规格。
- 负载均衡 upstream 变更。
- 回滚方案。
- 压测基线。
磁盘故障处理
磁盘故障不要先重启一通。先确认故障范围和集群健康。
发现故障
mc admin info prod
mc admin heal prod
mc admin logs prod --last 100系统层检查:
lsblk -f
df -hT
dmesg -T | tail -n 100
journalctl -u minio -n 200 --no-pager常见现象:
mc admin info显示某块盘 offline。- 日志里出现 drive unavailable、I/O error、permission denied、disk not found。
- 系统层
dmesg有磁盘错误。 - MinIO 进入 degraded 状态。
替换磁盘
通用步骤:
- 确认故障盘设备名、挂载点、序列号,不要拔错盘。
- 下线或维护节点,按硬件流程替换磁盘。
- 新盘分区、格式化、挂载到原挂载点。
- 确认挂载目录为空、权限正确、文件系统正确。
- 启动 MinIO 或等待服务识别。
- 观察 heal。
示例:
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,SERIAL
sudo mkfs.xfs -f /dev/sdX
sudo mount /dev/sdX /data/minio/disk1
sudo chown -R minio-user:minio-user /data/minio/disk1
sudo systemctl restart minio
journalctl -u minio -n 200 --no-pager
mc admin heal prod
mc admin info prod官方恢复文档建议通过 mc admin logs 或 journalctl -u minio 观察服务端日志,并用 mc admin heal 监控整体 healing 状态。不要在 heal 期间做大规模删除、迁移和压测。
单机单盘故障
单机单盘没有 heal 能力。处理路径是:
- 停止服务。
- 更换磁盘并重新挂载。
- 从异机、异地或对象副本恢复数据。
- 恢复配置、policy、用户和生命周期。
- 业务系统抽样验证上传下载。
如果没有备份,就不要假装有对象存储高可用。这个结论冷冰冰,但它能救一次事故复盘。
Java / Spring Boot 接入
业务系统接入 MinIO,不要只写一个上传工具类。至少要把配置、凭据、bucket、object key、元数据、权限、回滚和验证都设计清楚。
配置示例
storage:
minio:
endpoint: https://s3.example.com
access-key: app-tenant-a
secret-key: ${MINIO_APP_SECRET}
bucket: app-private
public-bucket: app-publicsecret 从环境变量、配置中心加密项、密钥系统注入,不写进源码。
MinioClient
import io.minio.MinioClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig {
@Bean
@ConfigurationProperties(prefix = "storage.minio")
public MinioProperties minioProperties() {
return new MinioProperties();
}
@Bean
public MinioClient minioClient(MinioProperties properties) {
return MinioClient.builder()
.endpoint(properties.getEndpoint())
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
}
}public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
private String publicBucket;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
public String getPublicBucket() {
return publicBucket;
}
public void setPublicBucket(String publicBucket) {
this.publicBucket = publicBucket;
}
}上传对象
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.UUID;
public class ObjectStorageService {
private final MinioClient minioClient;
private final MinioProperties properties;
public ObjectStorageService(MinioClient minioClient, MinioProperties properties) {
this.minioClient = minioClient;
this.properties = properties;
}
public String upload(String tenantId, String bizType, String originalName,
String contentType, long size, InputStream inputStream) throws Exception {
String bucket = properties.getBucket();
ensureBucket(bucket);
String ext = guessExtension(originalName);
String objectKey = buildObjectKey(tenantId, bizType, ext);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucket)
.object(objectKey)
.stream(inputStream, size, -1)
.contentType(contentType)
.build()
);
return objectKey;
}
private void ensureBucket(String bucket) throws Exception {
boolean exists = minioClient.bucketExists(
BucketExistsArgs.builder().bucket(bucket).build()
);
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
}
private String buildObjectKey(String tenantId, String bizType, String ext) {
LocalDate now = LocalDate.now();
return "tenant/" + tenantId
+ "/biz/" + bizType
+ "/" + now.getYear()
+ "/" + String.format("%02d", now.getMonthValue())
+ "/" + String.format("%02d", now.getDayOfMonth())
+ "/" + UUID.randomUUID()
+ ext;
}
private String guessExtension(String originalName) {
if (originalName == null) {
return "";
}
int index = originalName.lastIndexOf('.');
if (index < 0 || index == originalName.length() - 1) {
return "";
}
String ext = originalName.substring(index).toLowerCase();
if (ext.length() > 16) {
return "";
}
return ext;
}
}生产代码还要补:
- 文件大小限制。
- MIME 嗅探,不只信前端传来的
Content-Type。 - 文件名清洗,原始文件名只做展示。
- 病毒扫描或隔离区。
- 图片、视频、Office 文件的安全处理策略。
- 上传后
statObject校验大小和元数据。 - 元数据写库失败时删除对象或记录补偿任务。
生成预签名下载 URL
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import java.util.concurrent.TimeUnit;
public String presignedDownloadUrl(String bucket, String objectKey) throws Exception {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(objectKey)
.expiry(10, TimeUnit.MINUTES)
.build()
);
}预签名 URL 适合临时下载,不适合永久外链。永久外链要么走公开 bucket,要么走业务鉴权接口。
元数据表
业务库至少记录:
| 字段 | 说明 |
|---|---|
file_id | 业务文件 ID |
bucket | bucket 名 |
object_key | 对象 key |
original_name | 原始文件名 |
content_type | MIME 类型 |
size | 文件大小 |
sha256 | 可选,内容校验 |
owner_id | 所属用户、租户或业务主体 |
biz_type | 业务类型 |
status | uploading、available、deleted、orphan |
created_at | 创建时间 |
数据库事务不能覆盖 MinIO 对象写入。常见做法:
- 先生成 file_id 和 object_key。
- 上传对象。
- 写元数据为 available。
- 如果写库失败,删除对象或记录 orphan 清理任务。
- 如果对象上传失败,不写 available。
删除文件也不要直接物理删除。业务上可以先标记 deleted,再由异步任务删除对象。开启 versioning 的 bucket 还要考虑 delete marker 和历史版本。
排障手册
权限错误 AccessDenied
现象:
AccessDenied排查:
mc alias set app https://s3.example.com "$AK" "$SK"
mc ls app
mc ls app/app-private
mc cp hello.txt app/app-private/tenant-a/permission-test.txt
mc admin user info prod app-tenant-a
mc admin policy info prod app-tenant-a-readwrite
mc admin policy entities prod --user app-tenant-a常见原因:
- policy 没有 attach 到用户。
- 只有
s3:PutObject,没有s3:ListBucket。 - resource 写成 bucket ARN,没有写 object ARN。
- prefix 条件不匹配。
- 用错环境 key,例如 test key 访问 prod bucket。
- anonymous policy 以为会影响已认证用户权限。
修复后验证同一条上传下载命令,不要只看 Console 配置。
签名错误 SignatureDoesNotMatch
现象:
SignatureDoesNotMatch
The request signature we calculated does not match the signature you provided排查:
date -u
timedatectl
curl -vk https://s3.example.com/minio/health/live
mc --debug ls prod常见原因:
- access key 或 secret key 错。
- 客户端和服务端时间偏差过大。
- 反向代理改了
Host、scheme 或路径。 - 预签名 URL 被前端二次编码或截断。
- HTTP 生成的预签名 URL 被 HTTPS 访问,或者反过来。
- SDK 使用 virtual-hosted style,但域名和证书不支持 bucket 子域名。
修复方向:
- 校准 NTP。
- 保留原始 Host 头。
- 预签名 URL 生成和访问使用同一个外部 endpoint。
- Java/AWS SDK 接入 MinIO 时优先启用 path-style。
上传失败 413
现象:
413 Request Entity Too Large原因通常在 Nginx,不在 MinIO。
检查:
sudo nginx -T | grep -n "client_max_body_size"
tail -n 100 /var/log/nginx/error.log修复:
client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;重新加载:
sudo nginx -t
sudo systemctl reload nginx大文件上传超时或中断
排查顺序:
df -h
df -ih
ss -s
iostat -x 1
docker logs --tail=200 minio
journalctl -u minio -n 200 --no-pager
tail -n 100 /var/log/nginx/error.log常见原因:
- Nginx 请求缓冲占满磁盘。
- 代理超时时间太短。
- 客户端网络不稳定。
- 上传对象超过客户端或 SDK 限制。
- 磁盘 I/O 慢或空间不足。
- multipart 上传参数不合适。
命令行可以用 mc put 调整并行和 part size:
mc put big-file.bin local/app-private/big/big-file.bin --parallel 8 --part-size 64MiB业务 SDK 也要按文件大小走 multipart,不要把大文件一次性读进内存。
Console 登录异常
现象:
- Console 页面能打开,但登录失败。
- 登录后跳回内网 IP。
- 反向代理后静态资源 404。
排查:
journalctl -u minio -n 100 --no-pager
curl -I https://minio-console.example.com
curl -I http://127.0.0.1:9001重点检查:
MINIO_ROOT_USER和MINIO_ROOT_PASSWORD是否和实际启动环境一致。MINIO_BROWSER_REDIRECT_URL是否是外部可访问的 Console 地址。- Nginx 是否转发 Upgrade、Host、X-Forwarded-Proto。
- 浏览器是否被旧 cookie 干扰,换无痕窗口验证。
CORS 报错
现象:
Access to fetch at ... has been blocked by CORS policy排查:
mc cors get prod/app-private
curl -i -X OPTIONS https://s3.example.com/app-private/tenant-a/test.txt \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT'常见原因:
- 没有配置 bucket CORS。
AllowedOrigin和真实前端域名不一致。- 预签名 URL 用了 HTTP,但页面是 HTTPS。
- 浏览器请求带了自定义 header,但 CORS 没允许。
磁盘满
排查:
df -h
df -ih
du -sh /data/minio/*
mc admin info prod
mc du --recursive prod/app-private
mc ilm rule ls prod/app-private常见原因:
- versioning 打开后历史版本未清理。
- 临时 bucket 没有生命周期。
- 大量失败 multipart 未清理。
- 备份 bucket 和业务 bucket 放在同一组磁盘里。
- Nginx 临时文件或容器日志打满系统盘,被误以为是 MinIO 数据满。
修复原则:
- 先确认哪些 bucket 增长。
- 再按 prefix 和业务归属处理。
- 删除前导出清单。
- 配生命周期后持续观察,不要上来手工大删。
Java SSLHandshakeException
现象:
javax.net.ssl.SSLHandshakeException
PKIX path building failed原因:JVM 不信任 MinIO HTTPS 证书链,常见于企业内部 CA。
验证:
openssl s_client -connect s3.example.com:443 -servername s3.example.com </dev/null修复方向:
- 把内部 CA 导入 JVM truststore。
- 容器镜像里同步安装 CA。
- 确保证书 SAN 包含
s3.example.com。 - 不要在生产代码里关闭 TLS 校验。
mc 版本和 Server 不兼容
现象:
please upgrade the client
unsupported admin API排查:
mc --version
minio --version
mc admin info prod修复:
- 升级
mc到和服务端相近的 release。 - 内网同步
mc时同步 sha256。 - 自动化脚本里打印
mc --version,避免现场机器上混用旧客户端。
落地工程深水区
对象存储上生产后,运维真正要守的是“对象能不能长期找回、按权限访问、按成本留存”。MinIO 不是简单文件目录,不能用传统共享盘思路治理。
| 维度 | 生产约束 | 验证命令 |
|---|---|---|
| 凭据 | root 凭据只做平台初始化,业务用独立 access key 和 policy | mc admin user list local、mc admin policy info |
| Bucket | bucket、prefix、object key、生命周期、版本化按业务 owner 管理 | mc ls local、mc ilm ls local/bucket |
| 纠删码 | 节点和磁盘数量要满足故障域设计,不能把所有磁盘挂在同一风险点 | mc admin info local |
| 不可变 | Object Lock 和保留期一旦启用会影响删除和成本,必须先评审 | mc retention info local/bucket/object |
| 接入层 | 反向代理要放行大对象上传、长连接和超时,不让网关先失败 | Nginx access/error log |
一个可执行的交付模板可以这样验收:
# 平台健康
mc admin info local
mc admin heal -r --dry-run local
# 业务 bucket
mc mb local/app-prod
mc anonymous get local/app-prod
mc admin policy attach local app-prod-policy --user app-prod-user
# 上传、下载、删除和生命周期
mc cp ./sample.bin local/app-prod/test/sample.bin
mc stat local/app-prod/test/sample.bin
mc rm local/app-prod/test/sample.bin对象存储最怕“只管上传,不管治理”。上线前要逼近真实业务回答四个问题:
- 对象误删后靠版本化、复制、备份还是业务重传恢复。
- 大对象、碎片小对象、临时文件和审计文件的生命周期分别多久。
- 跨机房复制失败时,业务读写走主站、备站还是暂停写入。
- SDK 超时、重试、分片上传和临时凭据刷新由谁负责验证。
生产化清单
上线前:
上线后:
故障时:
常用命令速查
# alias
mc alias set local http://127.0.0.1:9000 minio-root replace-with-strong-password
mc alias ls
# 服务状态
mc admin info local
mc ready local
mc admin logs local --last 100
# bucket
mc mb local/app-private
mc mb --with-versioning local/app-backup
mc ls local
mc rb local/empty-bucket
# object
mc cp hello.txt local/app-private/demo/hello.txt
mc get local/app-private/demo/hello.txt ./hello.txt
mc stat local/app-private/demo/hello.txt
mc ls --recursive local/app-private/
mc rm local/app-private/demo/hello.txt
# mirror / diff
mc mirror ./data/ local/app-private/data/
mc diff ./data/ local/app-private/data/
# anonymous
mc anonymous set download local/app-public
mc anonymous get local/app-public
mc anonymous set none local/app-public
# user / policy
mc admin user add local app-user replace-with-strong-password
mc admin policy create local app-policy app-policy.json
mc admin policy attach local app-policy --user app-user
mc admin user info local app-user
# access key
mc admin accesskey create local app-user
mc admin accesskey ls local app-user
mc admin accesskey disable local <access-key>
# CORS
mc cors set local/app-private cors.xml
mc cors get local/app-private
mc cors remove local/app-private
# versioning
mc version enable local/app-private
mc version info local/app-private
mc ls --versions local/app-private/
# lifecycle
mc ilm rule add --prefix "tmp/" --expire-days 90 local/app-private
mc ilm rule add --noncurrent-expire-days 30 local/app-private
mc ilm rule ls local/app-private
mc ilm rule export local/app-private > app-private-lifecycle.json
# presigned URL
mc share upload --expire 30m local/app-private/tenant-a/upload.txt
mc share download --expire 30m local/app-private/tenant-a/upload.txt
# heal
mc admin heal local官方文档入口
- MinIO 开源仓库:https://github.com/minio/minio
- MinIO 开源 Releases:https://github.com/minio/minio/releases
- MinIO Server 下载目录:https://dl.min.io/server/minio/release/
- MinIO Client 下载目录:https://dl.min.io/client/mc/release/
- MinIO Client 仓库:https://github.com/minio/mc
- AIStor 文档入口:https://docs.min.io/aistor/
- AIStor Client /
mc命令参考:https://docs.min.io/aistor/reference/cli/ - 反向代理和负载均衡:https://docs.min.io/aistor/installation/linux/load-balancing/
- IAM 和 Policy:https://docs.min.io/aistor/administration/iam/access/
mc admin accesskey:https://docs.min.io/aistor/reference/cli/admin/mc-admin-accesskey/- 匿名访问策略:https://docs.min.io/aistor/reference/cli/mc-anonymous/mc-anonymous-set/
- CORS 配置:https://docs.min.io/aistor/administration/cors-configuration/
- 生命周期管理:https://docs.min.io/aistor/administration/object-lifecycle-management/
- 纠删码:https://docs.min.io/aistor/operations/core-concepts/erasure-coding/
- 阈值和限制:https://docs.min.io/aistor/reference/aistor-server/thresholds/
- 磁盘故障恢复:https://docs.min.io/aistor/operations/failure-and-recovery/recover-after-drive-failure/
- Java SDK:https://github.com/minio/minio-java
