浏览知识库目录

后端相关

MinIO 从入门到实践:对象存储学习指南

系统介绍 MinIO 与 S3 对象存储,覆盖 Docker Compose、mc、boto3、预签名 URL、权限、版本控制、生命周期、安全、运维与排障。

MinIO 从入门到实践:对象存储学习指南

MinIO 是一个提供 S3 兼容 API 的对象存储服务。它适合保存图片、视频、安装包、日志归档、数据库备份等非结构化数据,也常被用来在本地或私有环境中模拟 Amazon S3。

本文从对象存储模型讲起,使用 Docker Compose 搭建可重复的实验环境,再通过 mc 和 Python boto3 完成上传、下载、权限控制、预签名 URL、版本控制与生命周期管理。最后给出生产设计、安全清单和常见故障排查方法。

版本说明:本文写于 2026 年 8 月。示例为了可重复学习,固定使用 MinIO Object Store 的历史 Community Edition 镜像 RELEASE.2025-04-22T22-12-26Z,不要把它理解为当前最新生产版本。MinIO 当前官方文档主要面向需要许可证的 AIStor;新项目上线前必须重新核对发行版、许可证、支持范围和升级路径,不能直接复制实验配置。


1. 学习目标

完成本文后,你应该能够:

  • 解释对象、Bucket、Object Key、元数据和版本 ID。
  • 区分对象存储、文件存储和块存储的适用场景。
  • 使用 Docker Compose 启动单节点 MinIO 实验环境。
  • 使用 mc 创建桶、上传、下载、查看和删除对象。
  • 设计私有桶、公开桶以及最小权限应用账号。
  • 在 Python 后端中通过 boto3 访问 MinIO。
  • 正确生成预签名下载、上传 URL。
  • 理解版本控制、生命周期、对象锁和服务端加密。
  • 排查 AccessDeniedSignatureDoesNotMatch、浏览器打不开预签名 URL 等问题。
  • 判断哪些配置只能用于学习,哪些要求必须在生产环境补齐。

建议先具备 Docker、HTTP 和环境变量的基本知识。本文示例在 Linux shell 中执行;PowerShell 的环境变量写法不同,但 MinIO 和 S3 概念相同。


2. 先理解对象存储

2.1 三种常见存储模型

存储模型 管理单位 典型访问方式 常见场景
块存储 固定大小的数据块 挂载为磁盘,由文件系统管理 数据库磁盘、虚拟机系统盘
文件存储 目录和文件 POSIX、NFS、SMB 共享目录、需要随机修改文件的应用
对象存储 对象 HTTP/S3 API 图片、视频、备份、归档、静态资源

对象存储不是远程文件系统。应用通常不能对对象中间的几个字节直接修改,而是重新上传一个完整对象或新版本。它换来的优势是统一的 HTTP API、扁平命名空间、丰富元数据,以及更适合海量非结构化数据的扩展方式。

2.2 Bucket、Object Key 和 Object

可以把对象地址拆成三部分:

s3://app-private/users/42/avatar/550e8400-e29b-41d4-a716-446655440000.webp
     └────┬────┘ └───────────────────────┬────────────────────────┘
        Bucket                         Object Key
  • Bucket(桶):对象的顶层命名空间,也是权限、版本控制、生命周期等配置的主要边界。
  • Object Key(对象键):对象在桶内的唯一名称。
  • Object(对象):二进制内容,加上 Content-Type、缓存策略、自定义标签等元数据。

users/42/avatar/ 看起来像目录,但通常只是 Key 的前缀。对象存储本身不依赖真实目录树,因此“重命名目录”往往等价于复制一批对象到新 Key,再删除旧对象。

2.3 不要误解 ETag

ETag 可以帮助客户端判断对象是否变化,但不能一概当作文件 MD5。分片上传、加密和不同 S3 实现都可能让 ETag 不等于内容的 MD5。需要端到端校验时,应单独保存并验证 SHA-256 等明确的校验值。

2.4 MinIO 中的两个端口

MinIO 常见默认端口为:

  • 9000:S3 API,供 SDK、mc、应用和健康检查访问。
  • 9001:Web Console,供管理员操作。

生产环境通常只把 API 端点通过 HTTPS 提供给需要的客户端;Console 应限制在管理网络、VPN 或身份代理之后,不能直接裸露在公网。


3. 产品与版本边界

学习 MinIO 时要区分“概念兼容”和“产品版本一致”:

  1. S3 的 Bucket、Object、签名、版本控制等核心概念可以迁移。
  2. 不同 MinIO Object Store、AIStor 和 mc 版本支持的命令或功能可能不同。
  3. 当前官方 AIStor 发行版需要有效许可证,不同许可层级的单节点、分布式、复制与生命周期能力也不同。
  4. 历史 Community Edition 仓库采用 AGPLv3,且官方 GitHub 仓库已经归档;固定旧镜像只适合已有系统兼容、学习或受控测试,不代表长期维护方案。

因此,生产环境不要使用浮动的 latest,也不要在不了解变更的情况下直接追新。更稳妥的流程是:

确定产品与许可证
        ↓
阅读对应版本发行说明
        ↓
在隔离环境验证数据、权限和 SDK
        ↓
备份并演练回滚
        ↓
使用不可变镜像版本或 digest 发布

4. 用 Docker Compose 搭建实验环境

4.1 准备目录

mkdir minio-lab
cd minio-lab

创建 .env

MINIO_ROOT_USER=minio-lab-admin
MINIO_ROOT_PASSWORD=replace-with-a-long-random-secret

.env 加入 .gitignore

.env

这里的 root 凭据只用于本机实验。生产应用不能使用 root 用户,更不能把真实密码提交到 Git、写入镜像或发到日志中。

4.2 编写 compose.yaml

services:
  minio:
    image: quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
    ports:
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9001:9001"
    volumes:
      - minio_data:/data
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 5s
      timeout: 5s
      retries: 20
      start_period: 10s
    restart: unless-stopped

volumes:
  minio_data:

端口绑定到 127.0.0.1,避免实验服务被同一局域网直接访问。命名卷让容器重建后数据仍然存在。

4.3 启动并验证

docker compose up -d
docker compose ps
docker compose logs --tail=100 minio
curl -fsS http://127.0.0.1:9000/minio/health/ready

然后可以访问:

S3 API:     http://127.0.0.1:9000
Web Console: http://127.0.0.1:9001

停止服务:

docker compose down

docker compose down 默认保留命名卷。下面的命令会连同实验数据一起删除,执行前必须确认当前目录和卷的用途:

docker compose down -v

5. 使用 mc 管理对象

mc 是 MinIO 提供的命令行客户端,定位类似面向对象存储的 cpls 和管理工具。安装与服务端匹配的版本后,先加载实验环境变量,再设置别名:

set -a
source .env
set +a

mc alias set local \
  http://127.0.0.1:9000 \
  "$MINIO_ROOT_USER" \
  "$MINIO_ROOT_PASSWORD"

mc alias list

mc alias set 会把连接信息保存在当前用户的 mc 配置目录。生产运维应使用专用账号和秘密管理系统,不要在共享机器上保存 root 凭据。

5.1 创建桶

mc mb local/app-private
mc mb local/app-public
mc ls local

推荐的 Bucket 名称应尽量兼容 S3 DNS 命名规则:使用小写字母、数字和连字符,避免下划线、空格、中文和类似 IP 地址的名称。

5.2 上传、查看和下载

echo "hello minio" > hello.txt

mc cp hello.txt local/app-private/demo/hello.txt
mc ls --recursive local/app-private
mc stat local/app-private/demo/hello.txt
mc cat local/app-private/demo/hello.txt
mc cp local/app-private/demo/hello.txt downloaded.txt

对象 Key 是 demo/hello.txt,其中的 / 只是前缀分隔习惯。

上传一个目录:

mc cp --recursive ./photos/ local/app-private/photos/

持续同步目录时可以使用 mc mirror,但要特别小心删除选项。先不带删除参数执行并检查结果,再决定是否需要让目标端删除多余对象。

5.3 删除对象

mc rm local/app-private/demo/hello.txt

未启用版本控制时,删除通常不可恢复。批量或递归删除前应先执行 mc ls --recursive 确认范围,并验证目标别名、Bucket 和前缀。

5.4 公开下载策略

如果 app-public 只保存确定可以公开的静态资源,可以允许匿名下载:

mc anonymous set download local/app-public
mc anonymous get local/app-public

撤销匿名访问:

mc anonymous set none local/app-public

不要为了让一张图片可访问而公开整个业务私有桶。常见设计是:

app-private  原图、附件、备份,只允许授权访问
app-public   明确公开的缩略图或发布副本,只允许匿名 GET

公开桶策略不是跨域 CORS 配置。匿名权限决定“能不能读”,CORS 决定“浏览器页面能不能从另一个 Origin 调用接口”,两者需要分别配置。


6. 最小权限账号与策略

root 账号只用于初始化和少量管理操作。每个应用、环境和用途都应使用独立账号,例如:

dev-image-service
staging-image-service
prod-backup-writer
prod-backup-reader

下面的策略只允许访问 app-private/uploads/ 前缀。Bucket 本身和 Bucket 内对象使用不同的 ARN:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads"
      ],
      "Resource": ["arn:aws:s3:::app-private"],
      "Condition": {
        "StringLike": {
          "s3:prefix": ["uploads", "uploads/*"]
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": ["arn:aws:s3:::app-private/uploads/*"]
    }
  ]
}

保存为 app-uploader-policy.json 后,可由管理员创建策略和账号:

export MINIO_APP_SECRET='replace-with-another-long-random-secret'

mc admin policy create local app-uploader app-uploader-policy.json
mc admin user add local app-uploader-user "$MINIO_APP_SECRET"
mc admin policy attach local app-uploader --user app-uploader-user

不同版本的 mc 管理命令可能有变化,执行前应查看当前客户端帮助:

mc --version
mc admin policy --help

设计权限时遵守以下原则:

  • 默认拒绝,只开放业务确实需要的动作、Bucket 和前缀。
  • 上传服务通常不需要 s3:*,备份写入账号也未必需要删除权限。
  • 读写账号分离,开发、测试、生产凭据分离。
  • 定期轮换访问密钥;轮换时允许新旧凭据短暂并存,验证后撤销旧凭据。
  • 服务端日志、异常页面和前端构建产物中不得出现 Secret Key。

7. Python 后端接入 MinIO

MinIO 提供自己的 SDK,也可以使用 AWS SDK。boto3 适合希望保持 S3 接口可迁移性的 Python 项目。

7.1 安装和环境变量

python -m pip install boto3
S3_ENDPOINT_URL=http://127.0.0.1:9000
S3_ACCESS_KEY_ID=app-uploader-user
S3_SECRET_ACCESS_KEY=replace-with-app-secret
S3_REGION=us-east-1
S3_BUCKET_PRIVATE=app-private

不要用 MINIO_ROOT_USERMINIO_ROOT_PASSWORD 作为应用凭据。

7.2 创建客户端

from __future__ import annotations

import os

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError


def s3_client():
    return boto3.client(
        "s3",
        endpoint_url=os.environ["S3_ENDPOINT_URL"],
        aws_access_key_id=os.environ["S3_ACCESS_KEY_ID"],
        aws_secret_access_key=os.environ["S3_SECRET_ACCESS_KEY"],
        region_name=os.getenv("S3_REGION", "us-east-1"),
        config=Config(
            signature_version="s3v4",
            s3={"addressing_style": "path"},
            retries={"max_attempts": 4, "mode": "standard"},
        ),
    )

本地环境使用 path-style 地址更直观:

http://127.0.0.1:9000/app-private/uploads/example.webp

生产环境如果使用 virtual-hosted-style,则通常需要 Bucket 子域名解析和匹配的 TLS 证书。地址风格必须与网关、DNS 和证书设计一致。

7.3 生成安全的 Object Key

不要直接把用户上传的文件名当作 Key。可将业务归属、日期和随机 ID 组合起来:

from datetime import UTC, datetime
from pathlib import Path
from uuid import uuid4


ALLOWED_SUFFIXES = {".jpg", ".jpeg", ".png", ".webp", ".pdf"}


def build_object_key(user_id: int, original_name: str) -> str:
    suffix = Path(original_name).suffix.lower()
    if suffix not in ALLOWED_SUFFIXES:
        raise ValueError("unsupported file type")
    day = datetime.now(UTC).strftime("%Y/%m/%d")
    return f"uploads/users/{user_id}/{day}/{uuid4().hex}{suffix}"

数据库中建议保存 Bucket + Key + 业务元数据,而不是保存临时预签名 URL。预签名 URL 会过期,域名也可能迁移;需要访问时再根据 Bucket 和 Key 生成。

7.4 上传、读取元数据和下载

from pathlib import Path


def upload_file(local_path: Path, object_key: str, content_type: str) -> None:
    client = s3_client()
    client.upload_file(
        str(local_path),
        os.environ["S3_BUCKET_PRIVATE"],
        object_key,
        ExtraArgs={
            "ContentType": content_type,
            "Metadata": {"source": "backend"},
        },
    )


def object_exists(object_key: str) -> bool:
    client = s3_client()
    try:
        client.head_object(
            Bucket=os.environ["S3_BUCKET_PRIVATE"],
            Key=object_key,
        )
        return True
    except ClientError as exc:
        status = exc.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
        if status == 404:
            return False
        raise


def download_file(object_key: str, target: Path) -> None:
    s3_client().download_file(
        os.environ["S3_BUCKET_PRIVATE"],
        object_key,
        str(target),
    )

upload_filedownload_file 会使用 boto3 的托管传输机制,大文件达到阈值后可以自动采用分片上传。生产服务还应设置文件大小上限、Content-Type 白名单、恶意文件扫描、超时和并发限制。

7.5 分页列举对象

一次 list_objects_v2 不保证返回整个 Bucket。要使用 paginator:

def iter_keys(prefix: str):
    paginator = s3_client().get_paginator("list_objects_v2")
    for page in paginator.paginate(
        Bucket=os.environ["S3_BUCKET_PRIVATE"],
        Prefix=prefix,
    ):
        for item in page.get("Contents", []):
            yield item["Key"]

业务查询不应依赖每次扫描整个 Bucket。常见做法是在数据库保存对象记录,通过数据库检索业务对象,只把 S3 列举用于运维、对账或修复任务。

7.6 删除对象

def delete_object(object_key: str) -> None:
    s3_client().delete_object(
        Bucket=os.environ["S3_BUCKET_PRIVATE"],
        Key=object_key,
    )

数据库和对象存储无法天然组成同一个事务。更可靠的删除流程通常是:

  1. 数据库先把记录标记为待删除。
  2. 提交数据库事务。
  3. 后台任务删除对象,失败则重试。
  4. 成功后记录审计信息或清除墓碑记录。

上传也有类似问题:对象上传成功但数据库事务失败时会产生孤儿对象,可通过状态表、幂等 Key 和定期对账清理。


8. 预签名 URL

预签名 URL 把“允许某个 S3 操作的临时签名”放进 URL 查询参数。浏览器不需要知道 Secret Key,也不必让整个 Bucket 公开。

8.1 私有对象临时下载

def presigned_download(object_key: str, expires: int = 900) -> str:
    return s3_client().generate_presigned_url(
        "get_object",
        Params={
            "Bucket": os.environ["S3_BUCKET_PRIVATE"],
            "Key": object_key,
        },
        ExpiresIn=expires,
    )

使用前先在业务层验证当前用户是否有权读取该对象。预签名 URL 本身近似一张临时通行证,在过期前拿到它的人通常都可以访问,因此不要写入公开日志、分析参数或长期缓存。

8.2 浏览器直接上传

后端可以生成带大小和类型限制的预签名 POST,让浏览器把文件直接上传到对象存储:

def presigned_upload(object_key: str, content_type: str) -> dict[str, object]:
    return s3_client().generate_presigned_post(
        Bucket=os.environ["S3_BUCKET_PRIVATE"],
        Key=object_key,
        Fields={"Content-Type": content_type},
        Conditions=[
            {"Content-Type": content_type},
            ["content-length-range", 1, 10 * 1024 * 1024],
        ],
        ExpiresIn=600,
    )

典型流程如下:

浏览器 ──申请上传──> 业务后端
  │                    │
  │<──URL、表单字段、Key─┘
  │
  ├──文件直传──> MinIO
  │
  └──提交完成信息──> 业务后端 ──HEAD 校验──> MinIO

后端不能只相信浏览器说“上传完成”,还应通过 HEAD 校验对象是否存在、大小是否合理,并结合业务需要做内容检测。

8.3 最常见的 endpoint 陷阱

签名时使用的 scheme、host、port、path 和部分请求头都会参与校验。例如后端容器使用:

http://minio:9000

生成出的 URL 对浏览器通常不可达,因为 minio 只是 Docker 内部 DNS 名称。不能简单把它替换成公网域名,替换后签名可能失效并返回 SignatureDoesNotMatch

正确做法是让“用于生成浏览器预签名 URL 的 endpoint”本身就是浏览器可访问的 HTTPS 地址,并让反向代理保留正确的 Host 与路径。内部 SDK 访问地址和外部签名地址可以由应用明确分开配置。


9. 版本控制与删除语义

9.1 启用版本控制

mc version enable local/app-private
mc version info local/app-private

启用后,同一个 Key 再次上传不会直接覆盖旧内容,而是创建新版本:

echo "version one" > report.txt
mc cp report.txt local/app-private/reports/report.txt

echo "version two" > report.txt
mc cp report.txt local/app-private/reports/report.txt

mc ls --versions local/app-private/reports/

9.2 Delete Marker 不是立即擦除

在启用版本控制的 Bucket 中,不指定版本 ID 的删除通常会创建一个 Delete Marker。普通读取看到它后表现得像对象不存在,但旧版本仍占用空间并可按版本 ID 读取。

显式删除某个版本通常不可恢复。执行任何带版本 ID、--versions 或递归删除的命令前,都应确认保留策略和备份。

9.3 版本控制不是备份

版本控制可以降低误覆盖、误删风险,但它仍和源数据处于同一套管理与故障域中。管理员误操作、凭据泄漏、集群灾难或生命周期配置错误仍可能破坏所有版本。

真正的恢复能力至少需要:

  • 独立故障域中的第二份数据或备份。
  • 受限且与日常应用分离的备份凭据。
  • 对象版本、数据库记录和加密密钥的一致恢复方案。
  • 定期从备份恢复并验证,而不只是看到“备份任务成功”。

10. 生命周期管理

生命周期规则可以自动过期当前对象、非当前版本或 Delete Marker。下面的示例让当前对象保留 90 天、非当前版本保留 30 天:

mc ilm rule add \
  --expire-days 90 \
  --noncurrent-expire-days 30 \
  local/app-private

mc ilm rule ls local/app-private

只处理某个前缀:

mc ilm rule add \
  --prefix "temporary/" \
  --expire-days 7 \
  local/app-private

注意:

  • 生命周期扫描是后台过程,对象满足条件后不保证在精确时刻立即删除。
  • 版本 Bucket 中只过期当前版本可能留下非当前版本和 Delete Marker。
  • 生命周期删除属于真实数据删除,上线前要在测试 Bucket 验证。
  • 规则变更应经过评审,并监控对象数量、版本数量和容量变化。
  • 如果当前许可层级不支持某项生命周期或分层能力,命令存在也不代表生产可用。

对象锁用于 WORM(Write Once Read Many)场景,防止对象版本在保留期内被删除或覆盖。它依赖版本控制。

对于本文固定的旧版本,应在创建 Bucket 时启用锁定能力:

mc mb --with-lock local/audit-archive
mc retention set --default governance 30d local/audit-archive

常见模式:

  • Governance:具有特定绕过权限的管理员可以越过保留限制,适合一般治理。
  • Compliance:保留期内任何普通管理操作都不能缩短或移除保护,风险更高,应在明确合规要求和恢复流程后使用。
  • Legal Hold:没有固定到期时间,在解除 Hold 前持续保护指定版本。

对象锁配置错误可能导致数据在很长时间内无法删除,持续占用容量。不要在不了解法规、许可和业务保留要求时直接启用 Compliance 模式。


12. 加密、TLS 与密钥

对象存储安全至少包含两层:

12.1 传输中加密

生产 API 和 Console 都应使用受信任证书的 TLS。不要让应用通过公网 HTTP 传输访问密钥、签名请求或对象内容。

如果在反向代理终止 TLS,要同时保护代理到 MinIO 的内部链路,并正确传递 Host、协议和客户端信息。还要根据业务设置上传体积、连接超时和请求缓冲策略,避免大文件在代理层被拒绝或重复落盘。

12.2 静态数据加密

MinIO 的 SSE-S3、SSE-KMS 等服务端加密需要正确的 KMS 或密钥配置。应用可以请求:

client.put_object(
    Bucket="app-private",
    Key="reports/annual.pdf",
    Body=data,
    ContentType="application/pdf",
    ServerSideEncryption="AES256",
)

但只有服务端密钥体系、权限和恢复流程全部有效,加密才真正可用。生产环境不要依赖写在普通环境文件中的长期静态主密钥;应使用经过备份、审计和高可用设计的 KMS。

最重要的事实是:丢失加密密钥等价于丢失数据。 备份对象而不备份密钥,无法形成可恢复的系统;把密钥与数据放在同一个故障域,也不能有效抵御灾难。


13. 后端系统中的推荐设计

13.1 数据库保存什么

数据库可以保存:

id
bucket
object_key
original_filename
content_type
size
sha256
owner_id
visibility
status
created_at

不要保存 Secret Key,也不要把会过期的预签名 URL 当作对象永久地址。

13.2 私有原件与公开副本

对于头像、文章图片等内容,可采用:

上传
  ↓
私有桶保存原件
  ↓ 扫描、校验、转码
公开桶生成发布副本
  ↓
CDN / 公共媒体域名

这样即使公开副本策略配置错误,也不会同时暴露私有原件。发布副本可以设置明确的 Content-TypeCache-Control,更新内容时使用新 Key,避免 CDN 旧缓存混淆。

13.3 Key 命名原则

一个可维护的 Key 通常应:

  • 稳定、唯一,不依赖可变标题。
  • 包含有限的业务前缀,便于授权、生命周期和对账。
  • 使用随机 ID 避免同名覆盖和枚举。
  • 不包含访问密钥、身份证号等敏感信息,因为 Key 可能进入日志和 URL。
  • 不把原始文件名直接拼入路径;如需展示,单独保存在数据库。

例如:

private/users/42/uploads/2026/08/03/01K1ABC....webp
public/articles/108/revisions/3/cover.webp
backups/mysql/production/2026/08/03/database.sql.gz

13.4 幂等与补偿

网络超时不代表请求一定失败:服务端可能已保存对象,只是客户端没收到响应。因此上传、复制和删除流程应具备幂等 Key、重试上限和结果核验。

典型发布流程可以使用状态机:

pending_upload → uploaded → verified → published
                      │
                      └→ rejected / cleanup_pending

对失败状态设置后台补偿任务,而不是在一个 HTTP 请求中假设数据库和对象存储能原子提交。


14. 高可用、纠删码与备份

单容器、单节点、单磁盘只适合开发和学习。它可能有持久卷,但没有消除主机、磁盘、误操作和机房故障。

MinIO 的分布式部署可以使用纠删码把对象拆成数据分片和校验分片,允许在一定数量的磁盘或节点故障时继续读取和修复数据。但要牢记:

  • 纠删码解决部分硬件故障,不等于异地备份。
  • 多个容器都运行在同一台主机上,不是真正的节点高可用。
  • RAID、虚拟磁盘、网络盘和 MinIO 纠删码如何组合,需要按对应版本的官方架构建议设计。
  • 节点数、磁盘数、奇偶校验、网络吞吐和恢复窗口必须通过容量计算与故障演练确定。
  • 当前 AIStor 的分布式、复制等能力受许可证层级约束,设计前先确认授权。

生产备份还必须覆盖:

  • 对象内容和所需版本。
  • 数据库中的 Bucket、Key 和业务关联。
  • Bucket 策略、生命周期、对象锁和复制配置。
  • KMS 配置、密钥及其独立恢复方式。
  • DNS、证书、代理和应用 endpoint 配置。

15. 监控与日常运维

15.1 健康检查

常用 HTTP 探针包括:

GET /minio/health/live
GET /minio/health/ready

例如:

curl -fsS https://s3.example.com/minio/health/ready

存活只表示进程能够响应,不能替代真实业务烟测。更完整的检查应使用最小权限测试账号,在专用前缀执行 PUT → HEAD/GET → DELETE,并验证返回内容。

15.2 应监控什么

  • API 可用性、延迟、错误率和超时。
  • 容量使用率、对象数、版本数和增长速度。
  • 磁盘、节点、网络和纠删码修复状态。
  • 认证失败、拒绝访问和异常删除。
  • KMS、证书到期、时钟同步和 DNS。
  • 生命周期、复制、备份与恢复任务。
  • 大文件分片上传未完成所占用的空间。

15.3 升级原则

升级前至少完成:

  1. 阅读当前版本到目标版本之间的全部发行说明。
  2. 核对服务端、mc、SDK、Console、KMS 和许可证兼容性。
  3. 在接近生产的数据规模和策略下验证。
  4. 备份配置和数据,并实际演练恢复。
  5. 记录不可逆的数据格式或配置变化。
  6. 使用固定版本或镜像 digest 分批发布并观察。

不要把“容器能启动”当成升级成功。必须验证读、写、列举、删除、预签名 URL、权限、版本和恢复路径。


16. 常见故障排查

16.1 AccessDenied

按顺序检查:

  1. Access Key 是否属于预期环境和账号。
  2. 策略的 Action 是否包含当前操作。
  3. Bucket ARN 与 Object ARN 是否写对。
  4. 前缀条件是否覆盖实际 Key。
  5. 是否存在显式 Deny;Deny 会覆盖 Allow。
  6. Bucket 是否公开与 CORS 是否配置是两个不同问题。

不要通过临时赋予 s3:* 并长期保留来“解决”权限问题。

16.2 SignatureDoesNotMatch

重点检查:

  • endpoint 的 http/https、host、port 和路径是否与签名时一致。
  • 反向代理是否改写 Host 或路径。
  • 客户端与服务端时钟是否同步。
  • region 和签名版本是否一致。
  • path-style 与 virtual-hosted-style 是否匹配 DNS 和证书。
  • 预签名请求是否少了签名时包含的 Content-Type 等请求头。
  • Secret Key 是否多了换行、空格或取错环境。

16.3 后端能访问,浏览器不能访问

如果 URL 中出现 minio:9000localhost 或内网 IP,通常是后端用内部 endpoint 生成了外部用户无法访问的 URL。应配置浏览器可达的 HTTPS 签名 endpoint,而不是生成后再替换字符串。

如果网络可达但浏览器 JavaScript 报跨域错误,再检查 CORS;直接在地址栏下载成功并不代表跨域 fetch 一定允许。

16.4 上传返回 413 Request Entity Too Large

这通常来自 Nginx、Ingress、API Gateway 或应用自己的请求大小限制,而不一定是 MinIO。检查整条链路的 body size、超时、缓冲和临时磁盘设置。

16.5 容器重建后数据消失

检查 /data 是否挂载了持久卷,以及启动的是不是同一 Compose project 和同一个卷。容器可写层不是永久存储。不要在未确认卷名和备份的情况下执行 down -v、删除卷或更换挂载目录。

16.6 删除后容量没有马上下降

可能原因包括:

  • Bucket 启用了版本控制,删除只创建了 Delete Marker。
  • 旧版本仍受保留期或对象锁保护。
  • 生命周期扫描尚未执行到这些对象。
  • 存在未完成分片上传。

先检查版本、生命周期和保留配置,不要直接操作 MinIO 数据目录中的底层文件。


17. 综合练习

按照下面的步骤完成一次完整实验:

  1. 使用 Compose 启动 MinIO,并通过 readiness endpoint。
  2. 创建 lab-privatelab-public 两个 Bucket。
  3. lab-public 只允许匿名下载,确认不能匿名上传。
  4. lab-private/uploads/ 创建最小权限应用账号。
  5. boto3 上传一个文本文件,保存正确的 Content-Type
  6. 使用 HEAD 校验大小,再用预签名 URL 下载。
  7. 生成限制 1~10 MiB 的预签名 POST,完成浏览器直传。
  8. 启用版本控制,连续上传两个同 Key 文件并列出版本。
  9. 删除当前对象,观察 Delete Marker,再读取一个旧版本。
  10. temporary/ 添加 7 天生命周期规则并导出检查。
  11. 使用错误前缀测试最小权限策略确实拒绝访问。
  12. 模拟 SDK 超时后的重试,验证 Key 和数据库记录不会重复。
  13. 停止并重建容器,确认命名卷中的对象仍存在。
  14. 写出一份恢复步骤,说明对象、数据库和密钥如何一起恢复。

完成标准不是“命令都执行过”,而是能够解释每一步的权限、数据状态和失败后果。


18. 命令速查

# 连接
mc alias set local http://127.0.0.1:9000 ACCESS_KEY SECRET_KEY

# Bucket
mc mb local/my-bucket
mc ls local

# Object
mc cp file.txt local/my-bucket/path/file.txt
mc ls --recursive local/my-bucket
mc stat local/my-bucket/path/file.txt
mc cp local/my-bucket/path/file.txt ./file.txt
mc rm local/my-bucket/path/file.txt

# 匿名读取
mc anonymous set download local/my-public-bucket
mc anonymous get local/my-public-bucket
mc anonymous set none local/my-public-bucket

# 版本控制
mc version enable local/my-bucket
mc version info local/my-bucket
mc ls --versions local/my-bucket

# 生命周期
mc ilm rule add --expire-days 90 local/my-bucket
mc ilm rule ls local/my-bucket

# 服务状态
mc admin info local
curl -fsS http://127.0.0.1:9000/minio/health/ready

删除、递归同步、版本清理、生命周期和对象锁都可能产生难以恢复的后果,不能只凭速查命令直接操作重要环境。


19. 延伸阅读

学习时优先掌握 S3 对象模型、权限与失败语义;落地时再针对实际 MinIO 发行版、许可证和部署拓扑核对对应文档。