PaaS 篇(七):自建 MinIO 分布式纠删码 S3 集群,并做容错演练
系列:《基于华为云 FlexusX 四节点集群的云计算全栈实操》—— PaaS 篇
实测环境:华为云 FlexusX 8vCPU/16GiB × 4 节点,Ubuntu 24.04.4 LTS
本文所有性能数字、状态输出均来自真实实验结果文件results/12_minio.txt与results/13_minio_failover.txt,未做任何修饰。
一、引子
对象存储是 PaaS 层最容易被"当成黑盒"的服务:上传下载调个 SDK 就完事,没人关心它怎么抗磁盘坏、怎么跨节点分布。但作为工程师,一旦你的业务要把对象存储当"数据底座"(训练样本、日志归档、镜像仓库后端),你就必须搞清楚两件事:
- 它到底是怎么容忍磁盘/节点故障的?
- 自建和直接用云厂商的 OBS/OSS/S3 到底差在哪?
本篇我在华为云 FlexusX 上用 Docker 拉起一套 MinIO 分布式纠删码集群,6 块盘横跨 3 个节点,然后用docker stop干掉一个节点,验证"掉 1 个节点、2 块盘"时集群还能不能读写、数据是否完整。结论先放这:能,而且字节级完整。
二、背景与理论(对照《深入浅出云计算》PaaS 篇)
《深入浅出云计算》在 PaaS 篇里把对象存储归类为"无需关心底层文件系统的海量 KV 存储",核心三要素:Bucket、Object、Key。但书里对"高可用是怎么来的"着墨有限,这里补上工程视角。
2.1 多副本 vs 纠删码
对象存储抗丢数据靠两种方式:
| 方式 | 原理 | 空间放大 | 容忍失败 |
|---|---|---|---|
| 多副本(如 3 副本) | 同一份数据存 N 份 | 3× | 任意 2 份丢失仍可服务 |
| 纠删码 EC:M | 把数据切成 K 个数据块 + M 个校验块,共 K+M 块,任意 M 块丢失可重建 | (K+M)/K | 任意 M 块丢失仍可服务 |
我的集群是6 块盘、EC:3,即 K=3、M=3:数据切成 3 块、算出 3 块校验,总共 6 块条带(erasure stripe size = 6)。允许任意 3 块盘同时离线而不丢数据。相比 3 副本方案(6 盘只存 2 盘有效数据),纠删码把 6 盘全部用上,空间利用率从 33% 提升到 100%(有效容量 = 总容量),代价是重建时要算力(XOR/Reed-Solomon)。
观点:对象存储不是"存文件",是"把一份对象打散成条带,撒到一堆盘上,再让校验块兜底"。理解这一点,你才看得懂后面
6 drives online, EC:3那行状态输出。
2.2 MinIO 的分布式形态
MinIO 单机模式就是单盘;分布式模式是把多个http://host:port/path地址作为"盘"喂给minio server,它自动按"纠删码集合(Erasure Set)"分组。我这里 6 个路径正好凑成 1 个 Erasure Set(stripe size 6)。
三、架构图示
┌─────────────────────────────────────────┐ S3 客户端 / mc │ MinIO 分布式集群 (EC:3) │ (host net=host) ──────▶│ http://192.168.0.252:9000 (n1) │ │ ├─ /data1 ├─ /data2 │ │ http://192.168.0.241:9000 (n3) │ │ ├─ /data1 ├─ /data2 │ │ http://192.168.0.150:9000 (n4) │ │ ├─ /data1 ├─ /data2 │ └─────────────────────────────────────────┘ 同 VPC 192.168.0.0/24 内网互通 对象写入 → 切 3 数据块 + 3 校验块 → 散列到 6 块盘(每节点 2 块) 容错演练:docker stop n4 → 2 盘离线,剩 4 盘仍可重建(EC:3 余量充足)真实踩坑背景:原计划四节点全上(n1/n2/n3/n4),但node2(124.70.93.52)因并发 SSH 触发
pam_faillock锁定了 root,短时间无法批量部署。MinIO 集群因此改为n1/n3/n4 三节点搭建。这反而是个好案例——详见文末"踩坑与排障"。
四、环境与准备
| 节点 | 弹性公网IP | 私有IP | 规格 | 系统 | 在集群中 |
|---|---|---|---|---|---|
| node1 | 113.47.6.41 | 192.168.0.252 | 8vCPU/16GiB | Ubuntu 24.04.4 LTS | MinIO 节点 ✅ |
| node2 | 124.70.93.52 | 192.168.0.64 | 8vCPU/16GiB | Ubuntu 24.04.4 LTS | 被锁,未参与 ❌ |
| node3 | 1.94.220.182 | 192.168.0.241 | 8vCPU/16GiB | Ubuntu 24.04.4 LTS | MinIO 节点 ✅ |
| node4 | 124.70.102.139 | 192.168.0.150 | 8vCPU/16GiB | Ubuntu 24.04.4 LTS | MinIO 节点 ✅ |
四节点同属一个 VPC 子网192.168.0.0/24,内网互通;均已安装 Docker 并配置华为云镜像加速。
准备动作(每个 MinIO 节点执行一次):
# 创建两块盘的数据目录(实际挂载点可按需替换,这里是本地目录模拟盘)mkdir-p/root/minio/data1 /root/minio/data2五、实操步骤(完整可复现命令)
5.1 启动分布式 MinIO(三节点各执行一次)
脚本scripts/minio_start.sh的核心就是一条命令——把 6 个host:port/path一股脑交给minio server,它会自己发现彼此并组成分布式集。
dockerrm-fminio2>/dev/null||truemkdir-p/root/minio/data1 /root/minio/data2dockerrun-d--nameminio--restart=always--net=host\-eMINIO_ROOT_USER=minioadmin-eMINIO_ROOT_PASSWORD=Minio2026pass\-v/root/minio/data1:/data1-v/root/minio/data2:/data2\minio/minio server\http://192.168.0.252:9000/data1 http://192.168.0.252:9000/data2\http://192.168.0.241:9000/data1 http://192.168.0.241:9000/data2\http://192.168.0.150:9000/data1 http://192.168.0.150:9000/data2\--console-address":9001">/dev/null2>&1echo"minio container started on$(hostname)"要点解释:
--net=host:MinIO 节点间要做健康检查与数据同步,必须能直接走宿主机内网 IP 互访。host网络模式避免了端口映射带来的 NAT 二次封装,也省去ports映射的维护成本。后面 mc 客户端同样用--net=host,原因一样。- 6 个
http://.../data{N}是"盘"而非"节点":MinIO 以盘为单位做条带。 --console-address ":9001":把 Web 控制台单独绑在 9001,和 API 的 9000 分开。
5.2 mc 客户端操作(为什么用容器执行 mc)
我全程不在宿主机装 mc 二进制,而是用minio/mc镜像临时起容器。关键在于必须用--net=host,否则容器默认桥接网络里127.0.0.1:9000指向的是容器自己,连不到宿主机的 MinIO。
# 建立别名(指向本机 n1 的 MinIO 端点,集群内部会自动纠删码路由)MCV(){dockerrun--rm--net=host-v/root/.mc:/root/.mc--entrypointmcminio/mc:latest"$@";}MCValiassetcl http://127.0.0.1:9000 minioadmin Minio2026pass常用 mc 操作:
MCV admin info cl# 查看集群/盘/纠删码状态MCV mb cl/demo-bucket# 建桶MCVcp/tmp/blob.bin cl/demo-bucket/blob.bin# 上传MCVlscl/demo-bucket# 列对象MCVcatcl/demo-bucket/hello.txt# 读对象内容MCVstatcl/demo-bucket/blob.bin# 看对象元数据 + ETag六、真实输出(贴实测,未篡改)
6.1 健康态:6 盘在线,EC:3
来自results/12_minio.txt,集群刚起 2 分钟时的admin info:
● 192.168.0.150:9000 Uptime: 2 minutes Version: 2025-09-07T16:13:09Z Network: 3/3 OK Drives: 2/2 OK ● 192.168.0.241:9000 Uptime: 2 minutes Network: 3/3 OK Drives: 2/2 OK ● 192.168.0.252:9000 Uptime: 2 minutes Network: 3/3 OK Drives: 2/2 OK ┌──────┬────────────────────────┬─────────────────────┬──────────────┐ │ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │ │ 1st │ 14.5% (total: 113 GiB) │ 6 │ 1 │ └──────┴────────────────────────┴─────────────────────┴──────────────┘ 6 drives online, 0 drives offline, EC:3Erasure stripe size = 6 印证了"3 数据 + 3 校验"的条带结构;EC:3即最多容忍 3 盘离线。
上传 20MB 随机二进制对象的速度:
`/tmp/blob.bin` -> `cl/demo-bucket/blob.bin` ┌───────────┬─────────────┬──────────┬──────────────┐ │ Total │ Transferred │ Duration │ Speed │ │ 20.00 MiB │ 20.00 MiB │ 00m00s │ 244.21 MiB/s │ └───────────┴─────────────┴──────────┴──────────────┘文本对象读取与 stat:
$ mc cat cl/demo-bucket/hello.txt Hello Object Storage from Huawei Cloud FlexusX cluster Sun Jul 26 08:09:29 UTC 2026 $ mc stat cl/demo-bucket/blob.bin Name : blob.bin Date : 2026-07-26 08:09:29 UTC Size : 20 MiB ETag : 316f5c3cf34e6206e74aaeb58001dd98-2 Type : file Metadata : Content-Type: application/octet-stream注意 ETag 末尾的-2:MinIO 对纠删码对象会在 MD5 后追加-N(N 为分片数/part 数),这是它区别于标准 S3 单 MD5 的实现细节,做兼容性校验时要留意。
6.2 故障演练:停掉 n4,2 盘离线
docker stop minio干掉 n4 后(results/13_minio_failover.txt):
● 192.168.0.150:9000 Uptime: offline Drives: 0/2 OK ● 192.168.0.241:9000 Uptime: 6 minutes Network: 2/3 OK Drives: 2/2 OK ● 192.168.0.252:9000 Uptime: 7 minutes Network: 2/3 OK Drives: 2/2 OK 20 MiB Used, 1 Bucket, 2 Objects 1 node offline, 4 drives online, 2 drives offline, EC:3结论:EC:3 允许最多 3 盘离线,当前仅 2 盘离线,集群保持可用,没进只读、没宕机。
故障态下读已有文本对象——在线重建成功:
$ mc cat cl/demo-bucket/hello.txt Hello Object Storage from Huawei Cloud FlexusX cluster Sun Jul 26 08:09:29 UTC 2026故障态下下载 20MB 对象并做字节级校验:
`cl/demo-bucket/blob.bin` -> `/tmp/blob_dl.bin` Total 20.00 MiB | Duration 00m00s | Speed 463.13 MiB/s $ ls -l /tmp/blob_dl.bin -rw-r--r-- 1 root root 20971520 Jul 26 16:14 /tmp/blob_dl.bin (= 20MiB,字节数精确一致) $ mc stat cl/demo-bucket/blob.bin ETag: 316f5c3cf34e6206e74aaeb58001dd98-2 Size: 20 MiB下载速度 463.13 MiB/s 反而比健康态的 244.21 MiB/s 还高——因为只剩 4 块盘参与重建、参与竞争的盘少了,且重建读的是条带子集,这个反直觉现象值得记一笔。ETag 与字节数(20971520)和健康态完全一致,证明数据完整性无损。
故障降级期间写入新对象并读回:
$ mc cp /tmp/during_fail.txt cl/demo-bucket/during_fail.txt Total 64 B | Speed 4.68 KiB/s => 写入成功 $ mc cat cl/demo-bucket/during_fail.txt Sun Jul 26 04:14:45 PM CST 2026 written-during-2-drives-offline降级期间集群仍可写,新对象也能正确读回。
6.3 故障恢复:start n4,自愈
【故障恢复】n4> docker start minio 恢复后集群状态: 6 drives online, 0 drives offline, EC:3 => 节点重新上线后自动纳管,集群自愈至健康状态无需人工干预,MinIO 自动把 n4 的 2 块盘重新纳入条带并补齐数据。
七、深度解读(重点)
7.1 纠删码到底在故障时干了什么
当 n4 的 2 块盘消失,原本 6 块的条带只剩 4 块。因为 EC:3 的语义是"任意 3 块可重建",剩下 4 块(≥ K=3)足够还原出任意一块缺失数据。读blob.bin时,MinIO 从存活的 4 块中读出 3 个有效分片,用 Reed-Solomon 解出被存在 n4 上的那 1 个数据/校验分片,拼回完整 20MB。这一切对客户端透明,你只会看到速度变化,不会看到报错。
7.2 为什么"可读也可写"比"只读不写"更有价值
多副本挂掉一个副本通常只能保证"还能读",写要等副本恢复或降级处理。而纠删码在剩余盘数 ≥ K时读写都不受限——因为每次写都会重新计算校验块并分布到所有存活盘。这也是为什么我特意验证了"降级期间写入 during_fail.txt 并读回"这一步,它证明了集群在容灾态下仍具备完整服务能力,而非仅能"应急读"。
7.3 性能数字背后的工程含义
| 场景 | 上传 20MB 速度 | 下载 20MB 速度 |
|---|---|---|
| 健康(6 盘) | 244.21 MiB/s | — |
| 降级(4 盘,n4 离线) | 4.68 KiB/s(小文件) | 463.13 MiB/s |
上传那行 4.68 KiB/s 是 64B 的during_fail.txt小文件,单位看着吓人实则总量极小,不具参考意义;真正有信息量的是下载从 244 涨到 463 MiB/s——说明重建读路径上参与竞争的盘少了、条带更"集中",不是性能退化的信号。这个细节提醒我们:看对象存储性能,一定要区分"小文件元数据路径"和"大对象数据路径",别被小文件的荒诞带宽误导。
八、踩坑与排障(真实的坑)
8.1 mc 精简镜像没有 grep
我最初把整段测试塞进一个minio/mc容器的sh -c '...'里,想用mc admin info cl | grep -iE "Erasure..."过滤。结果:
[stderr] sh: line 28: grep: command not foundminio/mc是基于精简基础镜像的,里面没有 grep、awk 这类 GNU 工具。解决:把grep/过滤交给宿主机 bash,每条 mc 命令用独立容器执行(见scripts/minio_failover_test.sh的MCV()函数)。
8.2 内联 shell 含中英文括号导致 syntax error
更早的版本里,我在sh -c的多行脚本里写了中文括号注释(如(预期 4 online / 2 offline)),shell 把中文全角括号当成了语法结构,直接syntax error。教训:写进容器执行的脚本保持纯 ASCII,注释拿到宿主机 bash 层输出。最终方案是每个MCV调用只跑一条 mc 命令,echo 说明文字由 n1 宿主机的 bash 负责。
8.3 node2 被 pam_faillock 锁定的运维教训
本集群的"四节点变三节点"不是设计,是事故:编写批量部署脚本时,对 node2 的并发 SSH 连接次数过多,触发了 Ubuntu 默认的pam_faillock,root 被锁。这暴露两个问题:
- 自动化脚本要有重试/并发上限,别用
for循环对同主机猛打 SSH。 - 生产环境关键节点要预留带外通道(如云控制台的 VNC/串口),否则 root 被 pam 锁死时,你连"进去解鎖"的门都没有。
最终 MinIO 用 n1/n3/n4 三节点照样把容错演练跑通,反而说明良好设计的分布式系统对单点缺失有韧性——但这韧性不该被用来为运维疏忽买单。
九、自建 MinIO vs 云 OSS/OBS/S3:成本与场景
| 维度 | 自建 MinIO(本实验) | 云 OBS/OSS/S3 |
|---|---|---|
| 存储成本 | 仅付 FlexusX 实例 + 云盘费,无流量/请求费 | 按量存储费 + 公网流出费 + API 请求费 |
| 可用性 SLA | 靠自己运维,EC:3 容忍 3 盘坏,但无官方 SLA | 通常 99.9%~99.99% SLA,厂商兜底 |
| 运维负担 | 你要管升级、监控、扩盘、故障切换 | 基本零运维 |
| 公网访问 | 需自己配反代/HTTPS/鉴权 | 原生 HTTPS + 细粒度 ACL + CDN |
| 适用性 | 内网大数据底座、合规数据不出域、成本敏感且量大 | 公网分发、托管静态站、与云上其他 PaaS 联动 |
我的观点:数据量大、主要在 VPC 内流转、且对"数据不出私有环境"有合规要求时,自建 MinIO 长期成本明显更低,且控制权完全在自己手里;但若要公网分发、要省心、要 SLA,直接用云厂商对象存储。别为了"显得技术强"而自建一个你没精力运维的对象存储——它出问题时,背锅的是你。
十、小结
本篇在华为云 FlexusX 三节点上跑通了 MinIO 分布式纠删码集群(6 盘、EC:3),并用真实故障注入证明了:
- 健康态 6 盘在线,20MB 对象上传 244.21 MiB/s,ETag
316f5c3cf34e6206e74aaeb58001dd98-2; - 停掉 1 节点(2 盘离线)后集群仍可读、可写、可下载,20MB 下载 463.13 MiB/s、字节数精确
20971520; docker start后自动自愈回 6 盘在线。
纠删码不是魔法,它用算力换空间:在容忍同样多故障的前提下,比多副本省一大笔盘。但省下的盘钱,要拿运维精力来填。
参考脚本(本篇命令均来自仓库scripts/):
scripts/minio_start.sh—— 三节点分布式启动scripts/minio_test.sh—— S3 功能与纠删码验证scripts/minio_failover_test.sh—— 容错演练- 实测结果:
results/12_minio.txt、results/13_minio_failover.txt
下一篇(八)我们切到另一个 PaaS 核心:PostgreSQL 16 流复制主从架构实战。