RustFS 分布式 4 节点 4 盘 E2E 测试指南:拓扑设计、覆盖范围与本地运行
2026/9/10 3:07:57 网站建设 项目流程

RustFS 分布式 4 节点 4 盘 E2E 测试指南:拓扑设计、覆盖范围与本地运行

【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs

本文是 RustFS 仓库内e2e-distributed分布式端到端测试车道的完整技术指南。该车道在单台机器上以127.0.0.1上不同端口真实拉起 4 个 rustfs 进程(每节点 4 块数据盘),用于验证 S3 语义、对象锁、版本控制、跨集群复制、配额、池扩容/下线/再平衡、站点复制、混沌故障注入与升级兼容性。读完本文,你将掌握该车道的拓扑表达约束、测试覆盖清单、与其他 CI 车道的边界划分,以及如何在本机复现这套 4×4 分布式测试。

测试拓扑:单机多进程的集群仿真

分布式 E2E 测试的核心思想是:不依赖真实多主机基础设施,而是在同一台机器上以127.0.0.1的不同端口运行多个 rustfs 进程,每个进程扮演一个集群节点。这套 in-tree 测试框架对应 crates/e2e_test/src/common.rs 中的RustFSTestClusterEnvironment,它负责为每个节点分配独立端口、独立数据目录,并通过RUSTFS_VOLUMES环境变量组装节点的磁盘布局。

文档给出了三种基础拓扑形态,均通过ClusterTopology的构造函数表达:

布局构造函数用途
4 节点 × 4 盘,单池ClusterTopology::single_pool_multidrive(4, 4)S3、对象锁、版本控制、配额、可观测性、并发、混沌
4 节点 × 1 盘,单池ClusterTopology::single_pool(4)双站点复制(共 8 个进程);从固定历史版本做直接/滚动升级
1 个单节点池 × 4 盘,随后三次append_single_node_pool扩容种子池扩容,随后下线 / 再平衡 / 完整性

源码侧,ClusterTopology位于 crates/e2e_test/src/common.rs,除了上述两个构造函数外还提供per_node_pools(drives_per_node, pools)(每个池独占一个节点)以及append_single_node_pool()(向已停止的多池集群追加一个新的单节点 erasure 池,用于模拟池扩容)。ClusterTopology::validate()会在启动前完成拓扑合法性校验:节点必须全部被分配且只属于一个池、多池拓扑要求drives_per_node >= 2(服务端解析器拒绝单盘省略号池drive{0...0})、跨多个 localhost 节点的池不可表达。

单机拓扑的表达约束:为什么多节点条带池不可表达

127.0.0.1上表达多池布局存在一个硬性限制:任何跨越多个 localhost 端口的池都是不可表达的。原因是RUSTFS_VOLUMES的 host 省略号语法(如127.0.0.1:900{0...1})会强制这些端口共享同一磁盘路径,从而在物理路径上发生冲突。因此,多池拓扑中每个池只能拥有一个节点。

从源码看,build_volumes_arg()(crates/e2e_test/src/common.rs)对单池布局逐条枚举每个 (node, drive) 端点并用空格连接;对多池布局则为每个单节点池生成一个省略号参数http://<addr><node-base>/drive{0...N-1}。服务端按空格拆分RUSTFS_VOLUMES,每个省略号参数成为一个独立池。多主机条带化扩容池仍然属于硬件功能链车道(backlog #1313 / #1314)的职责范围,本车道有意不表达该形态。

此外,当拓扑为多盘(drives_per_node > 1)时,with_topology()会给每个节点追加RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true:这些盘位于同一临时文件系统上,服务端的"物理磁盘唯一性"安全检查会拒绝启动,必须显式绕过。

数据搬迁用例的 fail-closed 语义

本车道对 decommission(下线)与 rebalance(再平衡)用例采用了**失败即关闭(fail closed)**的严格判定策略,杜绝"空跑通过"。文档明确规定,一个通过的下线或再平衡测试必须同时满足:

  • 观察到成功的启动响应;
  • 观察到 active(进行中)状态;
  • 观察到干净的终态(terminal state);
  • 移动计数器非零;
  • 操作结束后对象完整性校验通过。

反之,任何不支持的响应、HTTP 5xx、缺失状态字段、清理告警或零进度的终态响应都会导致用例失败。仅凭操作前后 S3 可用并不能证明数据移动确实发生过——这对应 crates/e2e_test/src/distributed/harness.rs 中的wait_for_decommission_active/wait_for_decommission_complete/wait_for_rebalance_active/wait_for_rebalance_complete以及 inventory 完整性断言。

具体到测试用例,crates/e2e_test/src/distributed/expand_decommission_rebalance_test.rs 中的four_pool_decommission_moves_objects_without_loss先把基线对象、版本历史和多部分(multipart)数据写入池 0,随后扩展到四个池,最后下线池 0(DECOMMISSION_POOL_ID = 0)。这样,通过结果证明的是用户数据的真实迁移,而非仅仅一个内部元数据计数器的变化。同样,four_node_pool_expand_preserves_objects_then_rebalance先写入 64 个 256 KiB 对象构成 inventory,逐步从 2 个池扩展到 4 个池,每次扩展后都从新节点和旧节点双重校验对象完整性。

池扩容对隔离文件系统的要求

池扩容用例依赖每个池报告独立的容量。问题在于:runner 文件系统上的四个目录返回完全相同的statfs总量,RustFS 会据此判定"没有任何池比集群平均值更空闲",从而不执行任何再平衡。为解决这个问题,CI 作业运行在 GitHub 托管的ubuntu-latest上,挂载四个独立的 1 GiB tmpfs 文件系统,并通过RUSTFS_E2E_POOL_ROOTS导出它们的绝对路径。

文档特别解释了为什么不使用自托管的sm-standard-4ARC pods:这些 pod 无法创建文件系统——mount -o loop因缺少/dev/loop-controlNo such file or directorymount -t tmpfs因初始命名空间缺少CAP_SYS_ADMINcannot mount tmpfs read-only带大小的 tmpfs 仍然会报告不同的st_dev和独立的 1 GiBstatfs容量,足以满足校验。

对应的校验逻辑在 crates/e2e_test/src/distributed/harness.rs 的validate_pool_storage_roots():它拒绝缺失、重复、相对路径、不存在或同设备(st_dev相同)的根目录,而不是允许一次空洞的移动"通过"。RUSTFS_E2E_POOL_ROOTS必须恰好包含 4 个绝对路径。

扩容流程本身也遵循严格的过程纪律:

  • 计划内的池追加以SIGTERM 优雅停止所有进程;硬杀进程仅保留给混沌故障用例;
  • 第四个池加入后,harness 执行一次完整的优雅持久重启:这证明扩展后的池映射在重启后仍然存在,并确保只有在每个副本都能加载收敛后的元数据之后,移动才会开始;
  • 扩容夹具是全当前版本二进制的机群,因此以文档化的V3 写入与机群确认门RUSTFS_POOL_META_V3_WRITE=trueRUSTFS_POOL_META_V3_FLEET_CONFIRMED=true,见 harness.rs 的POOL_META_V3_ENV)初始化池元数据。

该车道覆盖的测试范围

cargo nextest run --profile e2e-distributed -p e2e_test通过 nextest 的默认过滤器选择distributed::*模块(见 .config/nextest.toml 中[profile.e2e-distributed]default-filter = 'package(e2e_test) & test(/^distributed::/)')。测试源码位于 crates/e2e_test/src/distributed/,模块清单在 mod.rs 中声明,包括:s3_basic_testobject_lock_testversioning_testreplication_quota_testobservability_testexpand_decommission_rebalance_testconcurrent_data_movement_tests3_during_data_movement_testdata_integrity_movement_testsite_replication_testchaos_testconcurrency_stability_testupgrade_testextra_test和共享的harness.rs

车道覆盖的具体行为包括:

  • S3 基础语义:put / get / head / list / copy / rename / delete / presign,范围读与条件读、特殊字符 key、元数据、标签、分页、空对象、multipart 完成与中止;
  • 对象锁(Object Lock):COMPLIANCE、GOVERNANCE 与绕过(bypass)、legal hold、bucket 默认保留期、非锁桶拒绝;
  • 版本控制:精确历史读取、删除标记移除、挂起状态的 null 版本覆盖语义;
  • 跨集群桶复制:两个 4 节点集群之间复制(含元数据/标签、目标故障重试)、硬配额准入与拒绝 key 的缺席验证;
  • 可观测性:每个节点的 ready/live 探针、精确的 4 server / 16 disk 清单、每个节点的实时指标、关联的审计 webhook 投递。可观测性用例运行在 WARN 级别并挂起两个节点进程以模拟无响应对端:缓存盘立即变为 unknown、精确的每节点 HTTP PUT 增量暴露亚法定人数(sub-quorum)失败、本地元数据快照不会虚构写锁、恢复的对端允许新写入且从每个节点读到字节一致的数据——这模拟的是停滞进程而非物理网络分区;
  • 池生命周期:池扩容、下线、再平衡、校验和完整性、版本化与 multipart 数据、移动期间的 S3 可用性;
  • 双站点复制:双向收敛以及两个站点的 enabled/synchronized 对等状态;
  • 并发与压力:24 worker 的混合 PUT/HEAD/GET/COPY/DELETE 负载、活跃下线期间的并发 PUT;
  • 故障注入:节点 kill/重启、完整进程重启、节点面向 TCP 黑洞/恢复、跨对端 kill 的进行中流式 GET、以及通过物理xl.meta/ part-shard 盘点验证的新盘替换;
  • 列表一致性:multipart、跨节点列表、list-buckets 一致性;
  • 升级兼容:从固定历史版本做直接与滚动升级,之后历史对象、版本化历史与 IAM 用户 AK/SK 仍正常工作。

与其他 CI 车道的边界:本车道不替代什么

该车道定位是填补 in-tree 的 4×4 覆盖空白,既有的其他测试套件保持不变。文档给出了完整的边界对照表:

既有车道空白
rustfs-*-test.yml功能链克隆私有rustfs/auto-testing,运行在三台共享 VM(vm000vm002)上,continue-on-error: true,不是合并信号,也不是 4 节点。硬件rustfs-upgrade-test.yml仍留在那里
e2e-upgrade.yml单节点 SSE/multipart/delete-marker 契约加混合版本列表;不在 4 节点集群上固定 IAM 用户 AK/SK
e2e-smoke/e2e-full多数选定用例是单节点;分布式模块有意由本串行车道独占
e2e-nightly4 节点集群故障与修复,而非 S3/锁/版本控制/配额/下线矩阵
e2e-repl-nightly在 1–3 个单节点进程上进行站点与桶复制
e2e-s3tests.ymlmulti每周对 Docker 4 节点运行 ceph/s3-tests;不含锁/WORM、下线、混沌或校验和完整性
crates/e2e_test/src/chaos.rs仅单节点磁盘故障

硬件层面的断电、物理网卡拔除、带认证的节点间分区、固件/介质错误与替换服务器供应仍属于硬件验证 VM 的职责。本车道提供的是确定性的进程 kill、全新本地卷替换与节点面向 TCP 黑洞的模拟手段,不宣称物理故障认证。

本地运行指南

本地复现这套 4×4 分布式测试的完整流程如下:

cargo build -p rustfs --bins # 扩容/下线/再平衡用例需要四条位于不同文件系统上的路径。 # 如果没有四块磁盘,带大小的 tmpfs 就够了: # for p in 0 1 2 3; do # sudo mkdir -p /mnt/rustfs-pool-$p # sudo mount -t tmpfs -o size=1G,nosuid,nodev,mode=1777 tmpfs /mnt/rustfs-pool-$p # done export RUSTFS_E2E_POOL_ROOTS=/mnt/rustfs-pool-0:/mnt/rustfs-pool-1:/mnt/rustfs-pool-2:/mnt/rustfs-pool-3 # 升级用例需要固定的历史二进制(CI 会下载它)。 export RUSTFS_UPGRADE_SOURCE_BINARY=/path/to/rustfs-1.0.0-rc.2 cargo nextest run --profile e2e-distributed -p e2e_test

两个环境变量都是硬性前置条件,缺失时对应用例失败关闭

  • 没有RUSTFS_UPGRADE_SOURCE_BINARY,两个distributed::upgrade_test::*用例会失败;
  • 没有四条不同的RUSTFS_E2E_POOL_ROOTS,扩容与数据搬迁用例会失败。

如果本地运行不检查升级,可以过滤掉升级用例:

cargo nextest run --profile e2e-distributed -p e2e_test -E 'not test(/^distributed::upgrade_test::/)'

升级拓扑使用ClusterTopology::single_pool(4)(4 节点 × 1 盘),这与 crates/e2e_test/src/upgrade_compatibility_test.rs 中已被验证的混合版本夹具一致;4×4 localhost 磁盘会被历史版本的同设备磁盘检查拒绝

测试成员的归属由 .config/e2e-distributed-selection.txt 固定(内含 Linux 与 Darwin 平台的选择摘要哈希)。新增或重命名用例后,需要按文档所述用python3 ./scripts/check_test_wiring.py --update-profile e2e-distributed <listing.json> <platform>更新 Linux 与 Darwin 两条条目,以保证 CI 能检测到成员变更。

CI 工作流:tmpfs 挂载与失败告警

.github/workflows/e2e-distributed.yml 是这条车道的 CI 载体,其设计要点与本地运行一一对应:

  • 触发条件:对涉及存储栈的路径(Cargo.lockCargo.toml.config/nextest.tomlcrates/e2e_test/**crates/ecstore/**rustfs/**等)的 PR、手动workflow_dispatch(可选-E过滤器输入)以及每天 05:53 UTC 的定时任务;
  • runner 选择ubuntu-latest(GitHub 托管 VM),因为 loop 与 tmpfs 挂载在这里可用;sm-standard-4ARC pod 两者都拒绝;
  • 准备步骤:在RUNNER_TEMP/rustfs-e2e-pools下挂载四个 1 GiB tmpfs(size=1G,nosuid,nodev,mode=1777),并导出RUSTFS_E2E_POOL_ROOTS环境变量;作业结束时(含失败)统一卸载;
  • 历史版本下载:从 release 下载固定版本1.0.0-rc.2的 zip 包,校验 SHA-256 后解压,导出RUSTFS_UPGRADE_SOURCE_BINARY
  • 成员校验cargo nextest list --profile e2e-distributed --message-format json生成清单,随后python3 ./scripts/check_test_wiring.py --check-profile e2e-distributed核对成员(与 scripts/check_test_wiring.py 中实现的 selection 摘要机制配合);
  • 执行cargo nextest run --profile e2e-distributed -p e2e_test --no-tests=fail(手动触发时可传入-E过滤器);
  • 串行化.config/nextest.toml[profile.e2e-distributed]将整个package(e2e_test) & test(/^distributed::/)归入e2e-cluster-nightly测试组(max-threads = 1),因为每个用例会启动四个 rustfs 进程和多达十六个数据目录,多个 4×4 集群绝不能同时运行;同时设置slow-timeout = { period = "120s", terminate-after = 6 }覆盖下线/再平衡用例长达 180 秒的轮询等待;
  • 失败告警:定时运行失败时由alert-on-failure作业通过.github/actions/schedule-failure-issue打开或更新跟踪 issue,并上传 junit.xml、成员清单与节点日志供排障。

小结

e2e-distributed车道把"4 节点 × 4 盘真实集群"压缩进一台 CI VM:通过 distinct 端口上的多进程、RUSTFS_VOLUMES的池布局表达、隔离 tmpfs 的独立容量校验、以及 fail-closed 的数据搬迁断言,在合并门禁中持续验证 RustFS 的多节点 S3 语义、数据耐久性与集群生命周期操作。对开发者而言,本地只要备好四个隔离文件系统与固定的历史版本二进制,就能完整复现这条与生产多节点形态最接近的自动化测试链。

【免费下载链接】rustfs🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询