首页
/ MinIO 存储池下线(Pool Decommission)全流程操作与源码原理解析

MinIO 存储池下线(Pool Decommission)全流程操作与源码原理解析

2026-09-07 09:59:45作者:胡易黎Nicole

技术指南定位:本文基于 docs/distributed/DECOMMISSION.md,系统讲解 MinIO 分布式部署中“存储池(Server Pool)下线 / 退役”机制的完整操作链路——包括启动、状态查询、取消、失败恢复、完成后的清理步骤,以及驱动该机制的核心数据结构与后台搬迁逻辑。读完本文,你将能够安全地把旧硬件池上的数据在线迁移至新池,并正确地从启动参数中摘除退役池。

什么是 Pool Decommissioning:为什么需要下线一个存储池

MinIO 支持把一组独立启动的驱动器组合(通常由 MINIO_VOLUMES 环境变量或命令行参数里的一组端点表示,如 http://minio{1...4}/data{1...4})组织成一个 Server Pool(存储池)。集群可以横向扩展:新增一个 pool 即扩容,但反过来——当旧硬件需要更换、集群需要收缩或整体迁移到性能更好的硬件上时,就需要一种安全、在线、可恢复的机制把旧 pool 中的数据搬出去,这就是 decommissioning(下线/退役)

其工作方式(与同目录 docs/distributed/DECOMMISSION.md 描述一致)是:

  • 下线是“排空并扩散”的过程:把被下线 pool(如 pool1)中的数据均匀扩散到剩余的所有 pool(如 pool2pool3)上,而不是整体搬去某一个池;
  • 全程 在线进行,读写不中断,由管理员在后台编排执行。

三个核心特性

  1. 退役期仍可读、新写入自动绕开:处于 decommission 状态的 pool 仍然允许对其全部内容进行 READ 访问;同时,新产生的 WRITE 请求会被自动调度到“非 decommission 状态”的 pool 上,避免把新数据写入即将退役的池(docs/distributed/DECOMMISSION.md)。
  2. 版本化顺序保持:对于所有开启了版本化的 bucket,对象在被迁移到其他 pool 后,各“版本”的相对顺序保持不变——这对依赖 GetBucketVersions / 版本链语义的应用至关重要。
  3. 断点续跑:下线过程中若被打断(例如集群重启),任务会从上次的进度处自动恢复,而非从头再来。

前置概念:集群中的 Pool 状态机

从源码结构看,每个 pool 的下线状态被持久化在名为 pool.bin 的元数据文件中(格式常量见 cmd/erasure-server-pool-decom.go#L478-L483),并由以下三层结构描述(cmd/erasure-server-pool-decom.go#L46-L163):

  • poolMeta:集群级的 pool 元数据清单(版本号 + 各 pool 状态数组),save() 会把该文件写入所有 pool,以保证对第一块盘退役操作本身的高可用;
  • PoolStatus:单个 pool 的登记信息,含 ID、命令行标识 CmdLine、最后更新时间 LastUpdate 和可选的 Decommission 详情;
  • PoolDecommissionInfo:真正承载下线进度,包括 StartTimeStartSize/TotalSize/CurrentSizeComplete/Failed/Canceled 状态位,以及逐 bucket、逐前缀、逐对象的游标(QueuedBuckets/DecommissionedBuckets/Bucket/Prefix/Object)和统计计数(已迁移/失败的对象数与字节数)。

因此 mc admin decommission 显示的状态位与上面结构一一对应,我们可以归纳出完整的生命周期:

状态 含义 对应结构位
Active 正常服役中,未参与下线 Decommission == nil
Draining 正在排空数据(下线进行中) Decommission 存在,三个结束位均为 false
Draining(Canceled) 下线被取消 Canceled = true
Draining(Failed) 下线失败 Failed = true
Complete 下线完成,可安全摘除 pool Complete = true

状态位互斥切换由 poolMeta.DecommissionComplete / DecommissionFailed / DecommissionCancel 方法保证(cmd/erasure-server-pool-decom.go#L184-L217)。

开始下线:mc admin decommission start

使用 mc admin decommission start 把目标 pool(用与启动参数一致的通配端点表达式标识)标记为排空。这里的 aliasmc 中已配置的 MinIO 集群别名:

λ mc admin decommission start alias/ http://minio{1...2}/data{1...4}

从管理侧 API 的实现看(cmd/admin-handlers-pools.go#L39-L116),该命令最终会命中 REST 接口 POST /minio/admin/v3/pools/decommission(注册于 cmd/admin-router.go#L184)。服务端会做几层合法性校验:

  • 仅支持新式(ellipses 风格)启动参数,globalEndpoints.Legacy() 的旧式集群返回 ErrNotImplemented
  • 集群必须多于一个 pool,且对象层必须是 *erasureServerPools
  • 若已有下线任务在跑(IsDecommissionRunning()),返回 errDecommissionAlreadyRunning
  • 若 rebalance 已启动,则拒绝并发执行;
  • 请求还会被代理到目标 pool 的首个端点所在节点执行(proxyDecommissionRequest),以就近操作。

请求需要具备 admin 权限 DecommissionAdminAction(对应 mc 中的 mc admin decommission)。

查看下线状态:mc admin decommission status

不带参数:列出所有 pool 的概况

λ mc admin decommission status alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬────────┐
│ ID  │ Pools                           │ Capacity                         │ Status │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Active │
│ 2nd │ http://minio{3...4}/data{1...4} │ 329 GiB (used) / 421 GiB (total) │ Active │
└─────┴─────────────────────────────────┴──────────────────────────────────┴────────┘

表内的 Capacity 一列来自对 pool 容量(已用 / 总量)的探测,Status 列则对应当前状态机中的状态。服务端对应 GET /minio/admin/v3/pools/statuscmd/admin-router.go#L182)。

指定 pool:查看迁移进度

λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
Decommissioning rate at 36 MiB/sec [4 TiB/50 TiB]
Started: 1 minute ago

进度以“迁移速率 + 累计搬迁量/总量”的方式呈现,底层计数即 PoolDecommissionInfo.BytesDoneTotalSize 等字段。需要说明的是,文档(docs/distributed/DECOMMISSION.md 的 TODO 一节)指出:目前还没有更丰富的进度 UImc 只展示数据传输速率与用量增长;更精细的进度可视化将在后续版本中完善。

下线完成时

当所有数据搬移完毕后,状态查询会提示可以安全地从启动参数中移除该 pool:

λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
Decommission of pool http://minio{1...2}/data{1...4} is complete, you may now remove it from server command line

未参与下线的 pool

对未处于 decommission 状态的 pool 执行 status 会得到明确的错误提示:

λ mc admin decommission status alias/ http://minio{1...2}/data{1...4}
ERROR: This pool is not scheduled for decommissioning currently.

取消下线:mc admin decommission cancel

当系统负载过高、希望把下线调度到更合适的时间点时,可以停止一个正在进行的下线任务。

不带参数时列出所有正在进行的下线任务:

λ mc admin decommission cancel alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬──────────┐
│ ID  │ Pools                           │ Capacity                         │ Status   │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining │
└─────┴─────────────────────────────────┴──────────────────────────────────┴──────────┘

指定 pool 后真正执行取消:

λ mc admin decommission cancel alias/ http://minio{1...2}/data{1...4}
┌─────┬─────────────────────────────────┬──────────────────────────────────┬────────────────────┐
│ ID  │ Pools                           │ Capacity                         │ Status             │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining(Canceled) │
└─────┴─────────────────────────────────┴──────────────────────────────────┴────────────────────┘

重要警告:取消不会让 pool 回到 Active

NOTE: Canceled decommission will not make the pool active again, since we might have potentially partial namespace on the other pools, to avoid this scenario be absolutely sure to make decommissioning a planned well thought activity. This is not to be run on a daily basis.

这是一条关键约束(见 docs/distributed/DECOMMISSION.md):由于迁移过程中数据可能已经被部分地写入其他 pool(产生 partial namespace),取消下线后该 pool 无法回退为 Active,只能处于 Draining(Canceled) 这一“静止排空”态。因此,decommission 应当被当作一次经过充分规划的迁移活动,而不是日常运维动作。

在管理 API 的实现中,取消会校验请求权限 DecommissionAdminAction、拒绝旧式集群,并通过 pools.DecommissionCancel(ctx, idx) 把状态位置为 Canceled 并清空 StartTimecmd/admin-handlers-pools.go#L118-L158cmd/erasure-server-pool-decom.go#L207-L217)。

失败状态

若下线过程因任何原因失败,状态列会显示 Draining(Failed)

λ mc admin decommission status alias/
┌─────┬─────────────────────────────────┬──────────────────────────────────┬──────────────────┐
│ ID  │ Pools                           │ Capacity                         │ Status           │
│ 1st │ http://minio{1...2}/data{1...4} │ 439 GiB (used) / 561 GiB (total) │ Draining(Failed) │
│ 2nd │ http://minio{3...4}/data{1...4} │ 329 GiB (used) / 421 GiB (total) │ Active           │
└─────┴─────────────────────────────────┴──────────────────────────────────┴──────────────────┘

重启被取消或失败的下线

对于 CanceledFailed 的 pool,可以直接再次执行 start 来恢复迁移任务:

λ mc admin decommission start alias/ http://minio{1...2}/data{1...4}

对应的服务端逻辑允许在 Complete/Failed/Canceled 三个终态之上重新初始化一个新的 PoolDecommissionInfo(重新记录 StartTime 与空间基线);只有“正在排空中”的任务(三者皆非)才会拒绝二次 start(errDecommissionAlreadyRunning,见 cmd/erasure-server-pool-decom.go#L289-L308)。

断点续跑:集群重启后的自动恢复机制

“被打断后从上次位置恢复”在源码中有清晰实现,这也是 Decommission 最值得信赖的特性之一:

  • 当集群启动、erasureServerPools.Init() 初始化时,会先从盘上读取 pool.bincmd/erasure-server-pool-decom.go#L377-L416),随后通过 poolMeta.validate() 与当前命令行指定的 pools 做交叉比对,若发现仍有残留的未完成 pool,就据此重建元数据;
  • returnResumablePools() 只挑出既非 Complete 也非 Canceled 的 pool——也就是说 Complete 与 Canceled 不会被续跑,其余任何中间态都会在重启后被列入恢复清单(cmd/erasure-server-pool-decom.go#L167-L182);
  • 恢复动作并非立刻执行:只有 local 端点上的 leader 节点会启动后台 goroutine,先 sleep 3 * time.Minute 等待集群稳定,再调用 z.Decommission();若发现任务其实已经在跑(errDecommissionAlreadyRunning),则直接以 doDecommissionInRoutine 重新接管各 pool 的迁移例程(cmd/erasure-server-pool-decom.go#L519-L558);
  • 续跑所依赖的游标正是 PoolDecommissionInfo 中持久化的 Bucket/Prefix/ObjectQueuedBuckets/DecommissionedBuckets(见 ResumeBucketObjectbucketPop 等实现),从而做到逐对象级别的精确续传。

数据是怎么“搬”过去的:对象级迁移原理

深入 cmd/erasure-server-pool-decom.go 的排空循环,可以还原一条数据迁移的完整链路:

  1. 逐 bucket 排空decommissionPool 针对 decomBucketInfo{Name, Prefix},逐 erasure set 启动并发 worker。worker 数默认等于该 pool 的 set 数,每额外增加一个 set 还会再加一个 List worker(合计 2 * len(pool.sets) 规模),并可通过内部环境变量 _MINIO_DECOMMISSION_WORKERS 覆盖(cmd/erasure-server-pool-decom.go#L744-L761)。
  2. 以版本为单位处理:读取对象的全部版本(fileInfoVersions),然后用 versionsSorter.reverse()时间逆序排序,以保证目标 pool 上重建出与源一致、正确的版本栈顺序(cmd/erasure-server-pool-decom.go#L824-L826)。
  3. 不同对象类型走不同路径
    • 普通/版本化对象:以 GetObjectNInfo 读回,再用 PutObject 写入其他 pool,迁移时显式设置 DataMovement: trueSrcPoolIdxMTimeUserDefined,并通过 PreserveETagIndexCB 保留原始 ETag 与分片索引,保证压缩/校验和等元数据语义一致(cmd/erasure-server-pool-decom.go#L675-L695);
    • 多段上传(multipart)对象:重建为一个新的 multipart 会话,逐 part 写入并 CompleteMultipartUpload,保留每个 part 的 ETag、Index 及各类 checksum(cmd/erasure-server-pool-decom.go#L618-L668);
    • 删除标记(delete marker):以 DeleteObject + Versioned: true + DeleteMarker: true + SkipDecommissioned: true 的方式在目标池重建一个等价的删除标记,从而保住版本链的完整性(cmd/erasure-server-pool-decom.go#L855-L896)。
  4. 迁移前先消化生命周期规则:每个对象在搬迁前都会按 bucket 的版本化配置、生命周期策略、对象锁与复制配置做一次评估,已过期(expired)的对象/版本会被跳过并由生命周期系统按原计划清理,不再搬去新池(filterLifecycle,见 cmd/erasure-server-pool-decom.go#L791-L810)。
  5. 成功与失败都记账CountItem 精确累计成功/失败的对象数与字节数,并周期性把进度写回所有 pool 的 pool.binupdateAfter 按距上次落盘超过阈值才写,见 cmd/erasure-server-pool-decom.go#L433-L476)。

当状态变为 Complete:如何安全摘除 pool

一旦所有数据排空,状态列为 Complete,就表示现在可以安全地把第一个 pool 参数从 MinIO 启动命令行中移除了。三种典型部署形态的操作如下(docs/distributed/DECOMMISSION.md):

  • 裸机(baremetal):假设当前环境变量为 MINIO_VOLUMES="http://minio{1...2}/data{1...4} http://minio{3...4}/data{1...4}", 则删除第一段 http://minio{1...2}/data{1...4} 更新 MINIO_VOLUMES,然后并行重启所有 server:systemctl restart minio
  • Kubernetes:修改 StatefulSet 中 MinIO 容器的命令行输入参数,应用变更:kubectl apply -f statefulset.yaml
  • MinIO Operator:修改 tenant.yamlpools: 段,把两个条目缩减为单个条目,再执行 kubectl apply -f tenant.yaml

硬性约束:没有 Complete 状态标记的 ActiveDraining pool 一律不允许从配置中移除。这一点在源码侧同样被强约束:poolMeta.validate() 在发现命令行中仍包含一个“已标记为 complete”的 pool 时会持续输出形如 “pool(1st) = ... is decommissioned, please remove from server command line” 的警告(cmd/erasure-server-pool-decom.go#L356-L358),提醒你完成摘除动作。

管理 API 一览

mc 客户端命令与服务端 REST 端点的对应关系(注册见 cmd/admin-router.go#L182-L185):

mc 命令 服务端端点 说明
mc admin decommission status alias/ GET /minio/admin/v3/pools/status 查询全部/指定 pool 下线状态
mc admin decommission start alias/ <pool> POST /minio/admin/v3/pools/decommission 启动/恢复下线
mc admin decommission cancel alias/ <pool> POST /minio/admin/v3/pools/cancel 取消进行中的下线

三个端点均要求具备 policy.DecommissionAdminAction 权限,并且都会在“对象层不是 *erasureServerPools”或“pool 数不足 2”等前提下返回 ErrNotImplemented

当前限制与 Roadmap

根据文档(docs/distributed/DECOMMISSION.md),该机制尚有以下边界与规划中的能力:

  • 空删除标记不迁移:只有纯 delete marker、且对象没有其他后继版本的“空删除标记”不会迁移到新池,以避免在目标池产生空的元数据。如果确有迁移这类空删除标记的需求,官方建议在 GitHub 上提交 issue 讨论。
  • 进度 UI 仍显简陋:目前只有数据传输速率与已用空间增长的展示,更丰富的进度界面将在后续版本补齐。
  • 热分层(Hot Tier)与 ILM Transition 兼容性:文档指出 pooled setup 下“过渡的热分层尚不被支持”,试图对带 ILM Transition 的 bucket 执行下线会被服务端拒绝,规划在未来版本支持。作为佐证,当前仓库的迁移循环里已经为远端(remote/tiered)版本预留了独立分支 DecomTieredObject(见 cmd/erasure-server-pool-decom.go#L899-L919),说明远端对象处理是持续演进的关注点。实际使用前请务必结合你所部署版本的 Release Notes 确认 ILM Transition 桶的兼容性。
  • Console UI 暂未开放:嵌入式的 MinIO Console 目前还不提供通过界面触发 decommission 的能力,该能力规划在后续版本中支持;现阶段一律通过 mc 完成。

在本地验证整个下线流程

仓库提供了可复现的端到端验证脚本 docs/distributed/decom.sh,它完整覆盖了“构建版本化数据 → 创建温层(warm tier)→ 触发下线 → 校验”的场景,适合作为学习与演练素材。其关键步骤包括:

  • minio server http://localhost:9000/tmp/xl/{1...10}/disk{0...1} 拉起多盘本地集群,设置 CI=trueMINIO_SCANNER_SPEED=fastest 加速测试;
  • 通过 mc 创建用户、策略并建立开启版本化的 bucketmc mb -l myminio/versioned);
  • mc mirror internal myminio/versioned/ 灌入真实源码目录作为对象数据,随后执行软删除(mc rm -r --force)制造 delete marker,再二次 mirror 生成新版本,以构造多版本、含删除标记的复杂命名空间;
  • 额外拉起一个 :9002 端口的小集群作为温层,创建 bucket 与 lifecycle 策略,验证分层对象参与数据排空的行为;
  • 配合同目录下的多份变体脚本(如 decom-compressed-sse-s3.shdecom-encrypted.shdecom-encrypted-kes.sh 等)还可覆盖压缩、SSE-S3/SSE-KMS 加密等组合场景,并最终校验对象 checksum 与用户/策略计数以确认迁移无损。

仓库内的单元测试 cmd/erasure-server-pool-decom_test.go*_gen.go(msgp 序列化生成)亦可作为进一步研究状态机与持久化格式的参考。关于多池集群的容量规划与扩展模型,还可结合 docs/distributed/DESIGN.mddocs/distributed/SIZING.md 阅读。

结语

Pool Decommissioning 是 MinIO 多池架构里“可进可退”的关键一环:它让集群在硬件换代、容量收缩时无需整体重建即可把旧池数据在线扩散到新池,并以 pool.bin 持久化 + 启动续跑的方式保证了过程的可靠与可审计。正确使用它只需记住三条铁律:把下线当作规划性变更、不随意取消、未出现 Complete 之前绝不摘除 pool。配合 mc admin decommission 的三条命令与本文梳理的源码链路,你就可以在裸机、Kubernetes 与 Operator 三种部署形态下安全地完成存储池的平滑退役。

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