MongoDB 与 Antithesis:网络模糊测试拓扑构建与测试编排实践指南
【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo
导读
本文基于 MongoDB 官方仓库中的 docs/antithesis/README.md 编写,系统讲解如何将 MongoDB 各种集群拓扑(单节点、副本集、分片集群等)打包成 Docker 镜像并上传到 Antithesis 第三方平台,在其网络模糊测试(network fuzzing)环境中运行测试套件并自动生成缺陷报告。读完本文,你将掌握 Antithesis 基础镜像体系(mongo-binaries、workload)、拓扑镜像与docker-compose.yml的编写规范、初始化脚本的存活要求、如何在 Evergreen 中创建新拓扑任务,以及 Normal resmoke testing 与 Test Composer 两种测试模式的配置方式,并辅以仓库源码(docker_cluster_image_builder.py、antithesis_image_build_and_push.sh)作为实现级佐证。
Antithesis 是什么:第三方网络模糊测试环境
Antithesis 是一家第三方供应商,它提供一个可以进行网络模糊测试的环境。MongoDB 团队将包含docker-compose.yml文件的镜像上传到 Antithesis Docker 仓库,这些docker-compose.yml描述了各种 MongoDB 拓扑(例如分片集群、副本集)。Antithesis 会在自己的环境中对镜像执行docker-compose up,从而拉起对应的多容器应用并运行测试套件。
关键流程如下:
- 构建镜像:把描述拓扑的文件(
docker-compose.yml、初始化脚本、日志目录等)打包为"拓扑镜像",上传到 Antithesis Docker registry; - 启动拓扑:Antithesis 基于该镜像执行
docker-compose up,在真实多容器环境中启动 MongoDB 拓扑; - 注入故障:在测试套件运行期间,Antithesis 会对拓扑执行网络模糊测试(故障注入);
- 生成报告:测试结束后,Antithesis 生成一份缺陷报告,标识出被发现的 bug。
整个流程在 CI(Evergreen)侧由 evergreen/antithesis_image_build_and_push.sh 承载:它负责登录 Antithesis 仓库、调用 resmoke 构建三类镜像、在本地做一次 sanity check,然后推送镜像,并在 patch 场景下通过 Antithesis API 触发测试任务。
基础镜像:构建 MongoDB 测试拓扑的积木
base_images目录(仓库中位于 buildscripts/antithesis/base_images/)包含了创建 MongoDB 测试拓扑的"积木",这些镜像会在 Evergreen 的 nightly 构建中通过antithesis image build and push任务上传到 Antithesis Docker registry。目录下有两个子镜像:mongo_binaries与workload。
mongo_binaries:系统被测(SUT)镜像
该镜像包含最新的mongo、mongos、mongod二进制,可以用来启动mongod实例、mongos实例或执行mongo命令。它是构建"被测系统(System Under Test)"拓扑的核心积木。
从 mongo_binaries/Dockerfile 可以看到它的关键细节:
- 基础镜像为
ubuntu:22.04; EXPOSE 20000-20100:因为 resmoke 从 20000 开始为每个 mongo{d,s} 进程递增分配端口;- 安装
curl、python3、llvm-12、libc6-dbg、gdb等调试与运行依赖(通过 retry_apt.sh 进行最多 6 次指数退避重试,以应对镜像同步期间的 apt 抖动); - 复制
tsan.suppressions(来自 etc/tsan.suppressions); - 通过构建参数注入
ASAN_OPTIONS、UBSAN_OPTIONS、TSAN_OPTIONS,并自动追加external_symbolizer_path(指向 LLVM symbolizer)以及 TSAN 的 suppressions 路径; - 将
bin/*复制到/usr/bin/、lib/*复制到/usr/lib/,并将libvoidstar.so安装到/usr/lib/libvoidstar.so——libvoidstar.so是 Antithesis 的插桩库,二进制必须链接到它才能被 Antithesis 观测(CI 构建时会用ldd校验这一点,见 docker_cluster_image_builder.py 的_fetch_mongodb_binaries); - 将 MongoDB 源码复制到
/mongo,最后运行/usr/bin/mongo --version验证安装。
本地开发构建时,由于开发者一般没有真正的libvoidstar.so,构建器会写入一个stub 文件占位(见 docker_cluster_image_builder.py 的_add_libvoidstar_to_build_context);而在 Evergreen 中则必须使用系统真实的/usr/lib/libvoidstar.so并断言其存在。
workload:测试执行容器
workload镜像包含最新的mongo二进制以及resmoke测试运行器。workload 容器不属于实际拓扑的一部分,它的职责是:
- 执行
mongo命令完成拓扑的初始化设置; - 在已有拓扑上运行测试套件,例如:
buildscripts/resmoke.py run --suite antithesis_concurrency_sharded_with_stepdowns_and_balancer每个拓扑必须恰好有 1 个 workload 容器。
注意:在workload镜像构建期间,evergreen/antithesis_image_build_and_push.sh 运行后会生成"antithesis 兼容"的测试套件,并以antithesis_前缀命名。这些套件才是可以在 Antithesis 中运行的套件,它们从workload容器内部可用。
从 workload/Dockerfile 可以看到它与 mongo_binaries 镜像的区别:
- 额外安装
git、software-properties-common,并通过 deadsnakes PPA 安装Python 3.13(resmoke 项目要求 >= 3.13),把python3.13软链为系统python/python3; - 复制 QA 仓库到
/QA(并软链为/mongo/jstests/qa_tests)、jstestfuzz 仓库到/jstestfuzz(软链到/mongo/jstestfuzz); - 使用
uv(版本由 buildscripts/uv_version.txt 锁定)在/opt/venv中同步 MongoDB Python 依赖; - 软链 enterprise 模块到
/mongo/jstests/enterprise_tests。
镜像的获取与构建逻辑
无论是mongo_binaries还是workload,其构建都统一由DockerComposeImageBuilder驱动(见 docker_cluster_image_builder.py):
- 在 Evergreen 中:
master分支的二进制由编译任务产出,还需用db-contrib-tool setup-repro-env拉取last-continuous与last-lts二进制; - 本地开发:要求安装
db-contrib-tool(pip install db-contrib-tool或pipx install db-contrib-tool),用它下载 Ubuntu 22.04 的 MongoDB 二进制; - workload 镜像要求存在
mongo_enterprise_modules仓库(src/mongo/db/modules/enterprise); - 在 Evergreen 中构建时,依赖
evergreen/antithesis_clone_repos.sh预先克隆 QA、jstestfuzz 等仓库,因为 GitHub token 会在构建前提前生成并在一小时后过期,不能在构建过程中再执行克隆。
拓扑镜像:FROM scratch 的文件系统容器
"拓扑镜像"(对应base_images中的Dockerfile一节所描述的角色)把启动对应拓扑所需的全部文件组装成一个镜像:一个docker-compose.yml、一个logs目录、一个scripts目录和一个data目录。只要结构正确,你就能从该镜像复制出这些文件与目录,然后本地执行docker-compose up复现同样的拓扑。
该类镜像在构建时由 docker_cluster_image_builder.py 的_add_docker_compose_configuration_to_build_context动态生成,生成出的 Dockerfile 与文档中给出的示例一致:
FROM scratch COPY docker-compose.yml / ADD --chmod=0755 scripts /scripts ADD logs /logs ADD data /data ADD debug /debug(若配置了 Test Composer 目录,还会额外ADD --chmod=0755 test_composer /test_composer。)
为什么用FROM scratch?这些镜像仅仅作为存放拓扑所需全部文件的文件系统载体,不运行任何程序,因此使用FROM scratch最小化体积。镜像的实际运行者是被docker-compose.yml引用的mongo-binaries与workload镜像。所有拓扑镜像都在antithesis image build and push任务期间构建并上传到 Antithesis Docker registry;其中/data与/logs等目录由 evergreen/antithesis_image_build_and_push.sh 脚本创建。
docker-compose.yml:拓扑描述与故障注入控制
docker-compose.yml描述了如何利用mongo-binaries和workload镜像构造目标拓扑。以下是文档中给出的分片集群示例(与构建器生成的结构一致,见 docker_cluster_image_builder.py 中create_docker_compose_service的字段映射):
version: '3.0' services: configsvr1: container_name: configsvr1 hostname: configsvr1 image: mongo-binaries:evergreen-latest-master volumes: - ./logs/configsvr1:/var/log/mongodb/ - ./scripts:/scripts/ - ./data/configsvr1:/data/configdb/ command: /bin/bash /scripts/configsvr_init.sh networks: antithesis-net: ipv4_address: 10.20.20.6 # Set the an IPv4 with an address of 10.20.20.130 or higher # to be ignored by the fault injector # configsvr2: ... configsvr3: ... database1: ... container_name: database1 hostname: database1 image: mongo-binaries:evergreen-latest-master volumes: - ./logs/database1:/var/log/mongodb/ - ./scripts:/scripts/ - ./data/database1:/data/db/ command: /bin/bash /scripts/database_init.sh Shard1 networks: antithesis-net: ipv4_address: 10.20.20.3 # Set the an IPv4 with an address of 10.20.20.130 or higher # to be ignored by the fault injector # database2: ... database3: ... database4: ... database5: ... database6: ... mongos: container_name: mongos hostname: mongos image: mongo-binaries:evergreen-latest-master volumes: - ./logs/mongos:/var/log/mongodb/ - ./scripts:/scripts/ command: python3 /scripts/mongos_init.py depends_on: - "database1" - "database2" - "database3" - "database4" - "database5" - "database6" - "configsvr1" - "configsvr2" - "configsvr3" networks: antithesis-net: ipv4_address: 10.20.20.9 # The subnet provided here is an example # An alternative subnet can be used workload: container_name: workload hostname: workload image: workload:evergreen-latest-master volumes: - ./logs/workload:/var/log/resmoke/ - ./scripts:/scripts/ command: python3 /scripts/workload_init.py depends_on: - "mongos" networks: antithesis-net: ipv4_address: 10.20.20.130 # The subnet provided here is an example # An alternative subnet can be used networks: antithesis-net: driver: bridge ipam: config: - subnet: 10.20.20.0/24编写规范与约束
- 每个容器必须有
command:command运行一个初始化脚本,脚本必须放在以 volume 挂载的scripts目录中。写法必须是/bin/bash /scripts/[script_name].sh或python3 /scripts/[script_name].py。这是拓扑在 Antithesis 中正常启动的硬性要求。 - 日志路由:创建
mongod或mongos实例时,使用--logpath /var/log/mongodb/mongodb.log并把./logs/<容器名>挂载到/var/log/mongodb/(如示例中database1的做法)。这样一旦 Antithesis 检测到 bug,可以方便地取回日志。 - IPv4 地址与故障注入豁免:
ipv4_address设置为10.20.20.130 或更高的容器不会被网络模糊测试影响。例如workload容器通常不希望被网络模糊测试干扰,因此示例中分配了10.20.20.130。构建器源码中与此对应:next_available_fault_enabled_ip从 2 开始递增(受故障注入),next_available_fault_disabled_ip从 130 开始递增(不受故障注入)。 - 镜像 tag:所有镜像统一使用
evergreen-latest-mastertag(patch 场景则为evergreen-patch或自定义 tag),必要时由 evergreen/antithesis_image_build_and_push.sh 自动更新。 - 额外的 ulimits:构建器还会为每个服务加上
ulimits.memlock = -1。这是因为 s2n-tls(handoff 传输层使用)在启动时会调用mlock(),而 Antithesis 容器默认的RLIMIT_MEMLOCK很低,会导致s2n_config_new_minimal()失败、中止传输层初始化,放开 memlock 限制后 mongo{d,s} 才能正常启动。
scripts:初始化脚本的写法与存活要求
初始化脚本用于在容器启动时完成拓扑搭建。文档以sharded_cluster拓扑为例介绍了两种写法:利用utils.py中的工具方法编写 Python 脚本(如mongos_init.py),或使用简单的 shell 脚本(如database_init.py)。
当前仓库中,初始化脚本由 docker_cluster_image_builder.py 自动生成:
- 每个 mongo{d,s} 进程对应一个
scripts/<服务名>.sh,内容就是该进程的完整启动参数(process.args用shlex.quote拼接); scripts/workload.sh固定为tail -f /dev/null,保持 workload 容器存活;- 同时生成
scripts/run_resmoke.sh(封装 resmoke 运行命令)和scripts/print_connection_string.sh(打印 resmoke fixture 的 shell 连接串,Test Composer 脚本依赖它获取连接 URL)。
核心约束:初始化脚本不能结束,否则底层容器会退出。保持容器存活的方式有两种:
- Python 脚本:使用无限 while 循环;
- Shell 脚本:使用
tail -f /dev/null。
如何创建一个新的 Antithesis 测试拓扑
创建新拓扑需要谨慎,以确保高效利用有限的测试资源。步骤是:创建一个继承antithesis_task_template、并打上antithesistag 的新 Evergreen 任务,把指定的suite传给antithesis image build and push任务。可以参考现有的其他示例来起步。
完整的模板式任务配置示例(来自 buildscripts/antithesis/test_composer/README.md):
- <<: *antithesis_task_template name: antithesis_my_task commands: - func: "antithesis image build and push" vars: suite: concurrency_sharded_replication_with_balancer_and_config_transitions_and_add_remove_shard resmoke_args: >- --runAllFeatureFlagTests antithesis_test_composer_dir: basic_js_commandsantithesis image build and push任务内部的核心命令(见 antithesis_image_build_and_push.sh):
buildscripts/resmoke.py run --suite ${suite} ${resmoke_args} \ --dockerComposeTag $tag \ --dockerComposeBuildImages workload,config,mongo-binaries \ --dockerComposeBuildEnv evergreen ${extra_args}构建完成后会执行 sanity check:docker-compose up -d拉起拓扑,在 workload 容器内以--sanityCheck --externalSUT运行一次 resmoke(带 1 小时超时),收集日志后再把镜像推送到 Antithesis 仓库。其中mongo-binaries与workload两个共享镜像通过 S3 锁(buildscripts/s3_lock.py)保证并发推送互斥。
如何在 Antithesis 中测试你的套件
在 Evergreen patch 中传入参数schedule_antithesis_tests:
evergreen patch --param schedule_antithesis_tests=true当 Evergreen patch 中构建出 antithesis 镜像后,系统会向 Antithesis 发送一个 API 请求,让 Antithesis 运行你新建的镜像一小时。运行结束后,报告会通过邮件发送给你。
重要提示:这个参数会作用于你在 patch 中安排的每一个 antithesis 任务。一次 patch 中不要同时安排超过 1~2 个携带该参数的任务,否则会耗尽分配给项目的 Antithesis 测试额度。
从源码看,antithesis_image_build_and_push.sh 在 patch 且schedule_antithesis_tests=true时,会用jq组装参数(antithesis.config_image、antithesis.images、custom.duration设为.5小时、antithesis.report.recipients设为作者邮箱、antithesis.is_ephemeral设为true),然后调用curl -X POST请求 Antithesis 的 launch API 端点触发测试。
Antithesis 中的两种测试模式
Normal resmoke testing(默认模式)
Antithesis 会持续运行你的 resmoke 套件,每次从中随机取一个测试来执行。大多数使用 Python fixture 的 resmoke 套件都开箱即用地支持这种模式,其运行方式与 Evergreen 中的测试非常相似。如果任务上没有指定antithesis_test_composer_dir变量,Evergreen 中的 antithesis 任务默认就是这种模式。
Test Composer(模板驱动的自主测试)
Antithesis 提供了名为 Test Composer 的能力,用于针对集群运行"测试模板"。Test Composer 通过让你定义模板,引导 Antithesis 在多种系统状态下自主生成数千个测试用例,实现自主化测试。只要任务上指定了antithesis_test_composer_dir变量,Evergreen 任务就会自动使用 Test Composer。
Test Composer 采用一套命名约定(parallel_driver_*、singleton_driver_*、serial_driver_*、first_*、eventually_*、finally_*、anytime_*)来告诉 Antithesis 何时以及如何运行脚本,从而控制并行度、故障注入与命令顺序。仓库中内置了两个模板:
- basic_js_commands:针对单个
mongod的并行 JavaScript 负载(find/insert/update/delete/aggregate/事务/dbCheck 等),共享 js/commands.js 中的重试逻辑(处理瞬时网络错误、server selection 失败与可重试写错误); - random_resmoke:以随机种子(
--seed $(od -vAn -N4 -tu4 < /dev/urandom))加--shuffle --sanityCheck的方式运行现有 resmoke jstests。
关于命名规范、现有模板、最佳实践、本地开发方法以及 Evergreen 配置细节,请参阅 buildscripts/antithesis/test_composer/README.md。
附加资源
如果希望进一步利用 Antithesis,可以参考仓库内以下资源继续深入:
- 构建与推送全流程:evergreen/antithesis_image_build_and_push.sh
- 拓扑镜像与 docker-compose 的生成实现:buildscripts/resmokelib/testing/docker_cluster_image_builder.py
- 基础镜像定义:buildscripts/antithesis/base_images/mongo_binaries/Dockerfile 与 buildscripts/antithesis/base_images/workload/Dockerfile
- Test Composer 模板与指南:buildscripts/antithesis/test_composer/README.md
- 测试钩子中的 Antithesis 日志支持:buildscripts/resmokelib/testing/hooks/README.md
【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考