首页
/ MinIO 自动站点复制(Automatic Site Replication):跨集群 IAM/存储桶/对象同步的原理与实战

MinIO 自动站点复制(Automatic Site Replication):跨集群 IAM/存储桶/对象同步的原理与实战

2026-09-06 18:02:54作者:温玫谨Lighthearted

本文基于 MinIO 仓库中的 自动站点复制文档 及其配套多站点演练脚本、cmd/site-replication.go 核心实现展开。它系统讲解如何把多个使用同一身份提供方(IDP)的 MinIO 站点配置为互相复制的“对等站点”(peer sites),覆盖 IAM 用户/组/策略、存储桶与对象、桶级特性(策略、标签、Object Lock、加密)的自动同步;读完后你不仅能完成 mc admin replicate 的完整配置与验证流程,还能从源码层面理解状态持久化、站点恢复(heal)与全量重新同步(resync)机制。

一、什么是自动站点复制

自动站点复制允许多个相互独立的 MinIO 站点(或集群)在共用同一外部身份提供方(IDP)的前提下被配置为复制关系。被纳入复制关系的站点集合称为 peer sites(对等站点)。当这组站点启用 site-replication 后,以下变更会自动复制到所有其他站点:

  • 存储桶与对象的创建和删除
  • 所有 IAM 用户、组、策略的创建和删除,以及它们与用户/组的映射关系
  • STS 凭据的创建
  • 服务账号(Service Account)的创建和删除(root 用户拥有的服务账号除外);
  • 桶级特性的变更,包括:
    • Bucket Policy(桶策略);
    • Bucket Tags(桶标签);
    • Bucket Object-Lock 配置(含 retention 与 legal hold 配置);
    • Bucket 加密配置(Encryption configuration)。

注意:所有新创建和已存在的桶会在所有被复制站点上自动启用版本控制(versioning)。这一点在源码中也有对应实现——对等站点建桶时通过 PeerBucketMakeWithVersioningHandler 强制携带 versioningEnabled=true 选项(见 cmd/site-replication.go),因此跨站点的对象删除会以版本化的 tombstone 形式同步,保证删除操作也能在各站点间保持一致。

以下桶级特性不会被复制,设计上允许各站点保持不同:

  • Bucket notification(桶事件通知)配置;
  • Bucket lifecycle / ILM(桶生命周期)配置。

这一取舍的原因是合理的:通知目标和生命周期规则通常绑定站点本地的下游系统(如各站点的消息队列、各站点的归档存储桶),跨站点强制统一反而会破坏本地语义。

二、前置条件(Pre-requisites)

在配置站点复制之前,必须满足以下条件,这也是 cmd/site-replication.goAddPeerClusters 加入对等站点时校验的内容:

  1. 初始数据只能存在于一侧站点。配置复制时,只有加入复制的站点中的一个可以已有数据;站点复制配置成功后,该数据会被复制到其余(初始为空的)站点。此后对象可以写入任意站点,并自动复制到所有其他站点。
  2. 站点一经加入复制组便不允许移除(“Removing a site is not allowed from a set of replicated sites once configured”)。运维上应把它当作不可逆的拓扑决策。
  3. 所有站点必须使用相同的外部 IDP(若使用了 IDP)。源码中对应的校验逻辑是 validateIDPSettings,它逐站取回各站的 IDPSettings 并比对,不一致时返回 errSRIAMConfigMismatch(见 cmd/site-replication.go),错误信息会明确指出是哪两个站点的 IDP 配置不一致。
  4. 使用 SSE-S3 / SSE-KMS 加密时,所有站点必须能访问同一中心 KMS 部署。可以通过一个中心 KES 服务器实现,也可以通过多个 KES 服务器(例如每站点一个)挂接到同一个中心 KMS(如 Vault)实现。原因在于站点复制同步的是对象的密文与元数据,若各站点密钥独立,对端站点将无法解密。

三、配置步骤:mc 命令实操

3.1 为每个站点配置 alias

首先为每个站点配置 mc alias。假设你有三个 MinIO 站点,可以运行:

mc alias set minio1 https://minio1.example.com:9000 adminuser adminpassword
mc alias set minio2 https://minio2.example.com:9000 adminuser adminpassword
mc alias set minio3 https://minio3.example.com:9000 adminuser adminpassword

或者改用环境变量方式声明各站点凭据:

export MC_HOST_minio1=https://adminuser:adminpassword@minio1.example.com
export MC_HOST_minio2=https://adminuser:adminpassword@minio2.example.com
export MC_HOST_minio3=https://adminuser:adminpassword@minio3.example.com

3.2 加入站点复制

mc admin replicate add minio1 minio2 minio3

该命令会以 minio1 为发起方,通过管理 API SiteReplicationAdd(见 cmd/admin-handlers-site-replication.go)把三个站点加入同一复制组。执行期间,站点间会互相校验 IDP 配置、生成本站的服务账号并交换凭据。

3.3 查询复制配置

mc admin replicate info minio1

3.4 关于凭据模型的重要变更

原文档特别强调了一点:早期站点复制要求各对等站点的 root 凭据完全一致,现在不再需要。因为 STS token 现在改用**站点复制服务账号(site replicator service account)**的凭据签名,从而允许各站点的 root 账号独立管理,并具备最终禁用 root 账号的能力。

这一变更在源码中可以得到印证:

  • 服务账号固定名为 site-replicator-0(常量 siteReplicatorSvcAcc,见 cmd/site-replication.go);
  • AddPeerClusters 在本地创建该服务账号,并把其 access key 写入持久化状态 srStateV1.ServiceAccountAccessKey(见 cmd/site-replication.go),对等站点则依据该 access key 在自身侧创建对应的服务账号。

随之而来的两个运维注意事项(原文档明确说明):

  • 升级到包含该变更的版本后,此前由 root 凭据签名的 STS token 将全部失效,需要按常规方式重新生成;
  • 如果站点复制被解除(removed),STS token 同样会失效,也需要重新生成。

四、端到端演练:三站点 OIDC 复制场景

仓库提供了完整的可运行演练脚本,最典型的是 docs/site-replication/run-multi-site-oidc.sh。它在本机拉起三个 MinIO 站点(各 4 盘),共用一个 Dex OIDC 身份提供方,然后逐步验证文档中列出的每一项复制能力。以下按脚本顺序讲解。

4.1 启动三个使用相同 IDP 的站点

三个站点使用完全相同的 IDP 环境变量(满足“所有站点必须使用相同 IDP”的前提),仅监听端口和回调地址不同:

export MINIO_ROOT_USER="minio"
export MINIO_ROOT_PASSWORD="minio123"
export MINIO_IDENTITY_OPENID_CONFIG_URL="http://localhost:5556/dex/.well-known/openid-configuration"
export MINIO_IDENTITY_OPENID_CLIENT_ID="minio-client-app"
export MINIO_IDENTITY_OPENID_CLIENT_SECRET="minio-client-app-secret"
export MINIO_IDENTITY_OPENID_CLAIM_NAME="groups"
export MINIO_IDENTITY_OPENID_SCOPES="openid,groups"

export MINIO_IDENTITY_OPENID_REDIRECT_URI="http://127.0.0.1:10000/oauth_callback"
minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4} &
export MINIO_IDENTITY_OPENID_REDIRECT_URI="http://127.0.0.1:11000/oauth_callback"
minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4} &
export MINIO_IDENTITY_OPENID_REDIRECT_URI="http://127.0.0.1:12000/oauth_callback"
minio server --address ":9003" --console-address ":12000" /tmp/minio3/{1...4} &

若使用 LDAP 作为 IDP,可参考 docs/site-replication/ldap.yaml 用 Docker 快速起一个 OpenLDAP 实例,并改用 docs/site-replication/run-multi-site-ldap.sh 演练;使用 MinIO 自身作为 IDP 的完整流程见 docs/site-replication/run-multi-site-minio-idp.sh

随后声明各站点 alias 并确认就绪:

export MC_HOST_minio1=http://minio:minio123@localhost:9001
export MC_HOST_minio2=http://minio:minio123@localhost:9002
export MC_HOST_minio3=http://minio:minio123@localhost:9003

./mc ready minio1 && ./mc ready minio2 && ./mc ready minio3

4.2 启用站点复制

./mc admin replicate add minio1 minio2 minio3

4.3 验证 IAM 层复制:策略

在 minio1 上创建一个名为 projecta 的策略(策略内容取自 docs/site-replication/rw.json,即 admin:* + s3:* 全放行):

./mc admin policy create minio1 projecta ./docs/site-replication/rw.json
sleep 5
./mc admin policy info minio2 projecta   # 应成功
./mc admin policy info minio3 projecta   # 应成功

删除同样是双向的:

./mc admin policy remove minio3 projecta
sleep 10
./mc admin policy info minio1 projecta   # 应失败
./mc admin policy info minio2 projecta   # 应失败

4.4 验证 STS 凭据跨站点可用

脚本通过 docs/site-replication/gen-oidc-sts-cred.go 模拟一次完整的 OIDC 用户交互:先由 Dex 换取 OIDC token,再调用 MinIO 的 STS AssumeRoleWithWebIdentity 接口拿到临时凭据:

STS_CRED=$(MINIO_ENDPOINT=http://localhost:9001 go run ./docs/site-replication/gen-oidc-sts-cred.go)
MC_HOST_foo=http://${STS_CRED}@localhost:9001 ./mc ls foo   # minio1 可用
MC_HOST_foo=http://${STS_CRED}@localhost:9002 ./mc ls foo   # minio2 可用
MC_HOST_foo=http://${STS_CRED}@localhost:9003 ./mc ls foo   # minio3 可用

这正是“STS token 用站点复制服务账号凭据签名”的实际收益:在任意一个站点签发的 STS 凭据,可以在复制组内所有站点使用,而不需要各站点 root 凭据一致。

4.5 验证服务账号复制

用 STS 用户的 access key 在 minio2 上创建服务账号:

STS_ACCESS_KEY=$(echo ${STS_CRED} | cut -d ':' -f 1)
./mc admin user svcacct add minio2 $STS_ACCESS_KEY --access-key testsvc --secret-key testsvc123
sleep 10
./mc admin user svcacct info minio1 testsvc   # 已同步到 minio1
./mc admin user svcacct info minio2 testsvc   # 本地可见

删除同样同步:

./mc admin user svcacct rm minio1 testsvc
sleep 10
./mc admin user svcacct info minio2 testsvc   # 应失败
./mc admin user svcacct info minio3 testsvc   # 应失败

4.6 验证桶与对象复制(含大对象分片校验)

./mc mb minio1/bucket2
./mc mb minio1/newbucket

# 上传 17MB 大对象(会触发 multipart 上传)到 minio1
truncate -s 17M lrgfile
./mc cp ./lrgfile minio1/newbucket
sleep 5
./mc stat --no-list minio2/newbucket    # 桶与对象已在 minio2 出现
./mc stat --no-list minio3/newbucket    # 桶与对象已在 minio3 出现

# 反向:写入 minio2 的对象应复制到 minio1/minio3
./mc cp README.md minio2/newbucket/
sleep 5
./mc stat --no-list minio1/newbucket/README.md
./mc stat --no-list minio3/newbucket/README.md

删除同样同步:

./mc rm minio3/newbucket/README.md
sleep 5
./mc stat --no-list minio2/newbucket/README.md   # 应失败
./mc stat --no-list minio1/newbucket/README.md   # 应失败

对 multipart 大对象,脚本还会在 minio3 上重新下载并做 md5 比对,确认分片对象复制后内容一致:

sleep 10
./mc stat --no-list minio3/newbucket/lrgfile
actual_checksum=$(./mc cat minio3/newbucket/lrgfile | md5sum)
# 与上传前 lrgfile 的 md5sum 比对,不一致即视为复制失败

带版本删除(--versions)后各站点对象也应永久消失:

./mc rm -r --versions --force minio1/newbucket/lrgfile
sleep 5
./mc stat --no-list minio1/newbucket/lrgfile   # 应失败

4.7 验证 Object Lock 与桶标签复制

在 minio3 上创建启用 Object Lock 的桶,两个对等站点应都能观察到 Object Lock 已启用:

./mc mb --with-lock minio3/newbucket-olock
sleep 5
./mc stat --json minio2/newbucket-olock | jq -r .ObjectLock.enabled   # Enabled
./mc stat --json minio1/newbucket-olock | jq -r .ObjectLock.enabled   # Enabled

桶标签更新同样同步:

./mc tag set minio2/newbucket "key=val1"
sleep 10
./mc tag list minio1/newbucket --json | jq -r .tagset | jq -r .key    # val1

4.8 站点离线期间的变更恢复(heal)

演练的最后一部分验证了故障恢复能力:先 kill -9 掉 minio1,然后在 minio2 上更新标签(key=val2)、创建新桶 newbucket2、删除 bucket2;再重启 minio1,等待约 200 秒后:

# 最新标签更新已补齐
./mc tag list minio1/newbucket --json | jq -r .tagset | jq -r .key    # val2

# 离线期间发生过的桶创建/删除也已补齐
diff -q <(./mc ls minio1) <(./mc ls minio2)    # 无差异

这个“自动补齐”能力来自源码中的站点级 heal 例程SiteReplicationSys.Init 在启动时即拉起 startHealRoutine(见 cmd/site-replication.go),由 healBucketshealBucketPolicieshealTagMetadatahealVersioningMetadatahealSSEMetadatahealOLockConfigMetadatahealIAMSystemhealUsershealGroupshealPolicies 等一批恢复函数(见 cmd/site-replication.go)负责在对等站点短暂不可用后,按“最新状态优先”原则把桶元数据、IAM 实体等差异对齐。

4.9 解除复制后的全量重新同步(resync)

脚本最后演示了 resync 流程:先彻底移除站点复制,人为制造不一致,再重新建立并触发全量重同步:

./mc admin replicate rm --all --force minio1
./mc rb minio2 --force --dangerous
./mc admin replicate add minio1 minio2
./mc admin replicate resync start minio1 minio2
sleep 30

# 比对两站点的对象版本清单,应完全一致
./mc ls -r --versions minio1/newbucket > /tmp/minio1.txt
./mc ls -r --versions minio2/newbucket > /tmp/minio2.txt
diff -qpruN /tmp/minio1.txt /tmp/minio2.txt

resync 的实现在 startResync(见 cmd/site-replication.go):整体站点级重同步状态保存在 .minio.sys/buckets/site-replication/resync/<deployment-id>.meta,单个桶的重同步进度复用桶复制的 replication/resync.bin。相关状态查询与指标由 cmd/site-replication-utils.gocmd/site-replication-metrics.go 提供。

五、源码级实现要点

5.1 复制状态如何持久化

站点复制的组内状态保存在每站的系统桶中。从源码看(cmd/site-replication.go):

  • srStatePrefix = minioConfigPrefix + "/site-replication"srStateFile = "state.json"
  • 状态结构 srStateV1 记录组名 Name、按部署 ID 索引的对等站点表 Peers map[string]madmin.PeerInfo、本站复制服务账号的 access key ServiceAccountAccessKey 以及 UpdatedAt 时间戳(见 cmd/site-replication.go)。

SiteReplicationSys.Init 启动时会带指数退避地反复从磁盘加载状态(cmd/site-replication.go),因此即使站点重启,复制关系也会自动恢复,无需重新执行 mc admin replicate add

5.2 站点间如何互相“推”变更

对等站点之间的变更传递走 HTTP 管理 API。服务端关键入口在 cmd/admin-handlers-site-replication.go

  • SiteReplicationAdd:处理 mc admin replicate add
  • SiteReplicationInfo / SiteReplicationStatus / SiteReplicationMetaInfo:查询复制配置与状态;
  • SRPeerReplicateIAMItem / SRPeerReplicateBucketItem:接收来自对等站点的 IAM 项与桶项变更(策略、用户、桶元数据等的落地入口);
  • SiteReplicationEdit / SiteReplicationRemove:编辑/移除对等站点;
  • SiteReplicationResyncOp:触发 resync。

发起站点在本地完成变更后,调用这些接口把变更广播给其余对等站点;对等站点落地时以 UpdatedAt 时间戳做新旧判定,避免旧数据覆盖新数据(例如服务账号同步逻辑中会先检查 sa.UpdatedAt.After(updatedAt),见 cmd/site-replication.go)。

5.3 版本控制为何“自动开启”

站点复制的对象删除需要以版本化 tombstone 的形式复制(否则对端无法区分“删除”与“桶/对象从未存在”)。因此在加入复制组时,服务端会给已有桶启用版本控制、并对后续新建桶强制携带 versioningEnabled=truecmd/site-replication.go)。这与文档中“所有新桶和已存在的桶都会在所有被复制站点上自动启用 versioning”的说明一一对应。

六、更多配套演练脚本

docs/site-replication/ 目录下还有一组面向不同加密与复制组合的端到端脚本,可直接作为回归测试模板参考:

脚本 用途
run-multi-site-oidc.sh 三站点 + OIDC(Dex)IDP 的完整站点复制演练(本文第四节依据的脚本)
run-multi-site-ldap.sh 三站点 + LDAP IDP 演练,ldap.yaml 提供 OpenLDAP 容器配置
run-multi-site-minio-idp.sh 以 MinIO 自身作为 IDP 的多站点演练
run-sse-kms-object-replication.sh SSE-KMS 加密对象在站点间复制(对应“所有站点必须能访问中心 KMS”的前置条件)
run-ssec-object-replication.sh SSE-C 客户自带密钥对象的站点复制
run-ssec-object-replication-with-compression.sh SSE-C + 传输压缩组合场景
run-replication-with-checksum-header.sh 带校验和请求头的复制场景

七、运维要点小结

  1. 拓扑不可逆:站点加入复制组后不允许移除;规划站点成员时要预留足够余地。
  2. IDP 一致性是硬约束:新增站点前先用 mc admin idp info 核对各站 IDP 配置,validateIDPSettings 会在 add 阶段拒绝配置不一致的对端。
  3. STS 凭据要纳入密钥轮转计划:升级跨越“服务账号签名”变更的版本后,或任何一次 mc admin replicate rm 之后,此前签发的 STS token 都会失效,需重新生成。
  4. 依赖自愈机制兜底:站点重启后由 heal 例程自动对齐离线期间的变更;若曾整体移除过复制关系并重新建立,则应显式执行 mc admin replicate resync start <本站> <对端> 做全量重同步,并用 mc ls -r --versions 比对结果。
  5. KMS 中心化:SSE-S3/SSE-KMS 场景下,各站点必须共享中心 KMS(中心 KES 或多 KES 挂中心 Vault),否则复制过去的对象无法解密。

参考资料

登录后查看全文
热门项目推荐
相关项目推荐