Qdrant E2E 测试指南:TLS 证书再生成与存储数据兼容性维护
【免费下载链接】qdrantQdrant - High-performance, massive-scale Vector Database and Vector Search Engine for the next generation of AI. Also available in the cloud https://cloud.qdrant.io/项目地址: https://gitcode.com/GitHub_Trending/qd/qdrant
本文面向 Qdrant 仓库贡献者与自建 CI 的维护者,系统讲解tests/e2e_tests/端到端测试中两类“需要手工维护外部依赖”的任务:一是本地根 CA / 服务端 / 客户端 mTLS 证书的重新签发;二是为存储后向兼容性测试重新生成参考存储目录与快照并发布到 GCP bucket。读完本文,你既能按步骤复现两类再生成流程,也能理解其背后的 Docker 集群编排、OpenSSL 配置与多版本兼容性验证机制。
一、端到端测试目录全景
在动手维护前,先明确tests/e2e_tests/的整体布局与本文聚焦的文件。该目录承载的是面向真实运行态的“端到端”测试(区别于tests/openapi等接口级测试),大量用例依赖 Docker 与镜像构建,核心入口定义如下:
- tests/e2e_tests/README.md:本文依据的主文档,包含 TLS 测试与数据兼容性测试的说明;
- tests/e2e_tests/test_data/:全部测试数据与辅助脚本所在目录;
- tests/e2e_tests/conftest.py:会话级 fixture(如
docker_client、qdrant_image、test_data_dir),其中qdrant_image默认会构建标签为qdrant/qdrant:e2e-tests的镜像,仅在镜像不存在或显式要求重建时才构建; - tests/e2e_tests/utils.py:公共工具函数,如
run_docker_compose(向 compose 环境注入QDRANT_IMAGE)。
主文档把维护工作划分为两大主题,本章节按主文档脉络逐层展开:
- TLS 测试(TLS test):如何重新签发证书以驱动双向 TLS(mTLS)集群测试;
- 数据兼容性测试(Data compatibility test):如何在格式演进后重新生成参考存储数据,保证当前代码仍能读懂上一个稳定版写出的磁盘格式。
二、TLS 测试:基于 mTLS 的集群安全验证
2.1 测试要验证什么
主文档中“TLS test”一节对应的是 tests/e2e_tests/test_tls.py。从该测试实现看,其核心断言覆盖三层能力:
- HTTP/HTTPS 双向 TLS:以
requests.Session携带 CA 校验 + 客户端证书,请求/telemetry、/cluster,断言集群内恰好 2 个 peer,且每个 peer 的 URI 均以https://开头并携带端口:6335(内部 p2p 通信端口); - gRPC mTLS:复用
fullstorydev/grpcurlDocker 镜像,挂载证书目录与 proto 目录(lib/api/src/grpc/proto),调用qdrant.Qdrant/HealthCheck验证node1.qdrant:6334的 gRPC 服务; - TLS 环境下的分片转移:在两节点集群中创建带
shard_number: 2的测试集合、写入 4 个点,再发起replicate_shard快照转移,最终校验远端副本出现。
2.2 集群如何以 TLS 拉起
该测试并不裸跑二进制,而是通过 tests/e2e_tests/test_data/tls-compose.yaml 编排双节点 docker-compose 集群。关键点包括:
- 镜像取自环境变量
${QDRANT_IMAGE:-qdrant/qdrant:dev},实际执行时由 conftest 注入构建好的e2e-tests镜像; - node1 以
--uri 'https://node1.qdrant:6335'启动,node2 额外带--bootstrap 'https://node1.qdrant:6335',表明节点间内网通信统一走 6335 端口的 HTTPS; - 两个容器均把宿主机的
./cert目录挂载为/qdrant/tls,把 tests/e2e_tests/test_data/tls_config.yaml 以只读方式挂载为/qdrant/config/tls_config.yaml。
2.3 TLS 配置参数全解
集群运行时读取的 tls_config.yaml 是一份“注解齐全”的最小配置,结合仓库配置结构逐项说明如下:
log_level: INFO service: # 对客户端(HTTP/gRPC API)通信启用 TLS enable_tls: true # 校验用户 HTTPS 客户端证书,是否要求客户端提供由 tls.ca_cert 中 CA 签发的证书 verify_https_client_certificate: true cluster: # 分布式部署模式开关 enabled: true # 集群内部节点间通信配置 p2p: # 节点间通信(p2p)启用 mTLS enable_tls: true # TLS 证书配置 tls: # 证书链文件(服务端证书) cert: ./tls/cert.pem # 私钥文件 key: ./tls/key.pem # 客户端证书校验用的 CA 证书 ca_cert: ./tls/cacert.pem值得注意的路径细节:由于 tls_config.yaml 运行在容器内的/qdrant/config/,而证书被挂载于/qdrant/tls/,因此配置中写的是./tls/cert.pem这类相对路径(相对于进程工作目录/qdrant),与宿主机上“test_data内平铺 cert 与 yaml”的布局正好对应。该文件同时开启service.enable_tls与cluster.p2p.enable_tls,使「对外 API」与「对内 p2p」均加密,这正是测试中既检查 6333/6334 客户端入口、又检查 6335 peer URI 的原因。
三、重新生成 TLS 证书
证书属于长期资产,默认有效期长达 3650 天(约 10 年),但一旦泄露、主机名规划调整或测试拓扑变化,就需要按主文档给出的两步流程重签。
3.1 第一步:运行生成脚本
在主文档指定的相对位置运行 tests/e2e_tests/test_data/gen.sh:
# 在仓库根目录(PROJECT_ROOT)执行 bash tests/e2e_tests/test_data/gen.sh脚本内部利用 OpenSSL 完成一个完整 PKI 基础设施的搭建,可分三阶段理解:
生成根 CA 自签证书:
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ -subj "/C=US/ST=State/L=City/O=Qdrant" \ -addext "keyUsage = critical, keyCertSign, cRLSign" \ -addext "basicConstraints = critical, CA:TRUE" \ -keyout cakey.pem -out cacert.pem产出
cakey.pem(CA 私钥)与cacert.pem(CA 证书,对应配置中的ca_cert)。注意basicConstraints = CA:TRUE与keyCertSign扩展,使它具备继续签发下级证书的资格。生成服务端/客户端私钥:
openssl genrsa -out key.pem 2048 chmod 644 key.pem单一
key.pem同时充当服务端与客户端密钥——该测试使用同一套证书做双向 TLS,证书内容须同时包含serverAuth与clientAuth用途(见 3.2)。用 CA 签发证书:先用 tests/e2e_tests/test_data/cert.cfg 生成 CSR,再以 CA 私钥签发
cert.pem。
3.2 证书主题与 SAN 配置
签发时的-config与-extfile均指向 cert.cfg,该文件同时定义了 DN 与扩展,尤其重要的是subjectAltName(SAN),它决定证书可被哪些主机名/地址信任:
[req] default_bits = 2048 default_md = sha256 prompt = no distinguished_name = req_distinguished_name req_extensions = v3_req [req_distinguished_name] CN = qdrant [v3_req] basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = node1.qdrant DNS.2 = node2.qdrant DNS.3 = localhost IP.1 = 127.0.0.1解读关键字段:
extendedKeyUsage = clientAuth, serverAuth:一套证书同时支持服务端认证与客户端认证,是 mTLS 得以成立的前提;[alt_names]:node1.qdrant、node2.qdrant恰好对应 compose 中两个容器的 hostname(也对应 test_tls.py 中{node_name}.qdrant的 gRPC 主机名拼接逻辑),localhost与127.0.0.1则保证从宿主机以 HTTPS 访问映射端口时证书校验可通过。
这也解释了一个易踩的坑:若修改了 compose 中节点 hostname,或把集群节点数从 2 扩展到 3,必须同步更新[alt_names]再重签证书,否则对端证书校验会因 SAN 不匹配而失败。
3.3 第二步:替换旧证书
脚本生成的cacert.pem、cert.pem、key.pem位于 tests/e2e_tests/test_data/cert,将它们覆盖到 tests/e2e_tests/test_data/cert/ 目录即可(注意脚本以仓库根为相对基准运行,实际会先在仓库根生成cacert.pem、key.pem、cakey.pem、cert.csr、cert.pem等临时产物,替换时应只拷贝三个目标 pem 文件,避免把中间产物混入版本库)。
替换后可运行 TLS 相关用例自检,例如:
# 在 tests/e2e_tests 目录下运行(需要 Docker 守护进程) python -m pytest test_tls.py -v四、数据兼容性测试:为“旧数据可读”兜底
4.1 动机与机制
主文档明确指出该测试的动机:为了快速发现存储兼容性回归,检查当前代码能否理解来自上一个稳定版的存储格式。向量数据库的存储演进(段结构、payload 索引、量化格式等)必须保证升级路径不被破坏,于是仓库采用“参考数据 + 版本矩阵”的策略:
- 不把大体积二进制归档进 git,而是推送到对象存储(README 中给出 GCP bucket:
qdrant-backward-compatibility); - CI 按版本矩阵下载对应归档,分别做「目录级 storage」与「快照级 snapshot」两类兼容验证。
版本矩阵与验证逻辑集中在 tests/e2e_tests/test_data_compatibility.py。当前矩阵包含最近的主线版本(如v1.16.0~v1.18.1),针对每个版本:
- 通过
https://storage.googleapis.com/qdrant-backward-compatibility/compatibility-{version}.tar下载归档; - 解出
storage.tar.bz2(storage 目录归档)与full-snapshot.snapshot.gz(快照归档); - Storage 子测试:将
storage目录以读写方式挂载为容器内/qdrant/storage,直接启动新版本 Qdrant; - Snapshot 子测试:把快照文件挂载为
/qdrant/snapshot.snapshot,以./qdrant --storage-snapshot /qdrant/snapshot.snapshot启动恢复流程; - 两种场景都先断言 12 个
EXPECTED_COLLECTIONS全部出现且状态为ok,再对每个集合执行稠密 / 稀疏 / 多向量搜索与覆盖各 payload 索引类型的 scroll 过滤查询,确认数据真正可读、可查。
也就是说,兼容性不是“能启动就算过”,而是要求旧格式下的数据在新版本里能完成真实检索。测试通过pytest-subtests在同一下载上并行跑两个子测试,并对每个用例打上xdist_group("compatibility")标记以便并行隔离。
4.2 兼容归档里装了什么
从生成脚本与消费方测试可还原归档内部结构:
compatibility-<version>.tar ├── storage.tar.bz2 # 仓库根 storage/ 目录的压缩归档 └── full-snapshot.snapshot.gz # 通过 snapshots API 生成的全量快照其中storage.tar.bz2解压后是标准的storage/目录树,测试正是把它整体挂进新版本容器的/qdrant/storage;快照则用于验证--storage-snapshot恢复路径(该启动参数在 test_data_compatibility.py 与 src/startup.rs 均有对应使用)。当前仓库test_data目录中保留了storage.tar.xz一份存量数据,而 CI 矩阵的归档均需从 GCP 获取,因此本地验证前需要外网可访问该 bucket。
五、重新生成存储兼容性数据
存储格式一旦演进(例如段文件、索引或量化元数据的持久化布局变化),就必须按主文档流程刷新参考数据。
5.1 执行生成脚本
主文档给出三个步骤,核心是运行 tests/e2e_tests/test_data/compatibility/gen_storage_compat_data.sh:
# 步骤 1:在仓库根目录执行 bash tests/e2e_tests/test_data/compatibility/gen_storage_compat_data.sh # 步骤 2:按提示输入生成数据的 Qdrant 版本号(例如 v1.18.1) # 步骤 3:把生成的 compatibility-<version>.tar 推送到 GCP bucket # (无凭证时需向维护者申请)脚本通过环境变量提供高级控制,缺省时走交互式询问:
| 环境变量 | 默认值 | 含义 |
|---|---|---|
USE_DOCKER | 1 | 为1时用qdrant/qdrant:$QDRANT_VERSION官方镜像生成数据;为0时本地cargo build后用./target/debug/qdrant |
QDRANT_VERSION | 空(交互询问) | 用于生成数据的版本号,最终写进归档文件名与提示信息 |
5.2 脚本流水线拆解
逐段解析脚本,能清晰看出“参考数据为什么可信”:
准备阶段:将
QDRANT_HOST固定为localhost:6333;容器模式下先用debian:12-slim清空./storage,再以--network=host启动qdrant/qdrant:$QDRANT_VERSION,并把宿主./storage映射进容器/qdrant/storage。脚本还注册了trap teardown EXIT,保证无论成功失败都会在退出时 kill 服务容器/进程。就绪探测:循环调用
curl ... /collections,最多等待约 30 秒直至服务可用,否则以退出码 2 失败——避免服务未就绪时就开始灌数据。灌数据:调用 tests/e2e_tests/test_data/compatibility/populate_db.py。该脚本是数据多样性的核心保证,见下一小节。
生成并下载快照:先
POST /snapshots创建全量快照并用jq解析出快照名,再GET /snapshots/$SNAPSHOT_NAME下载为full-snapshot.snapshot,随后gzip压缩。归档 storage 目录:
sudo chown -R $(whoami) ./storage规避权限问题后,以tar -cjvf把整个storage/打成storage.tar.bz2。打包外层归档:将
storage.tar.bz2与full-snapshot.snapshot.gz再封进compatibility-${QDRANT_VERSION}.tar,并提示上传到qdrant-backward-compatibilitybucket。
由此生成的每个版本归档,恰好与 test_data_compatibility.py 期望解出的两个内部产物一一对应,形成「生成方-消费方」的自洽闭环。
5.3 populate_db.py:参考数据为何“全面”
为使兼容性验证覆盖真实世界的存储形态,灌数据脚本刻意构造了高多样性的数据集,这是理解“为什么兼容归档体积大、但值得”的关键。核心策略包括:
- 向量维度多样化:稠密向量 256 维、多稠密向量 128 维(含
multivector_config)、稀疏向量 1000 维密度 0.1; - 三类向量同存:单稠密、多稠密(
multi-image)、稀疏(text)以及三者混合的点随机分布; - 点 ID 混用整数与 UUID:前一半点用整数 id,后一半用
uuid1; - payload 字段与索引类型全覆盖:
keyword_field、count_field、float_field、integer_field(含lookup/range索引)、boolean_field、geo_field、text_field(word 分词)、uuid_field、datetime_field,且各字段约半数点为单值、半数点为多值——这与测试端「按字段类型逐一执行 scroll 过滤」的断言表严格对应; - 多存储形态集合矩阵:入口处
main依次创建 12 个命名集合,覆盖内存向量、on_disk向量、memmap_threshold阈值触发、标量 int8 / 乘积 x64/x32/x16/x8 / 二进制量化、mmap 字段索引、uint8与float16向量类型等组合,且把indexing_threshold_kb压低以强制生成 HNSW 索引。
正因为集合矩阵与 payload 矩阵互相交叉,一个版本归档就能同时检验“向量存储/索引格式”“payload 索引格式”“量化配置持久化”三条兼容性主线——这与消费端EXPECTED_COLLECTIONS的 12 个集合名逐一对应。
六、何时需要重新生成?——触发条件与注意事项
结合前述机制,可以把主文档的“流程”落地为清晰的触发条件清单:
TLS 证书重签时机
- 私钥泄露或证书过期(默认 10 年,但 CI 模板与集群生命周期常短于证书周期,谨慎起见可随安全策略轮换);
- 集群拓扑变化:节点数量/主机名/域名调整时,必须同步更新 cert.cfg 的
[alt_names]后重签; - 重签后注意把三个
*.pem放入 tests/e2e_tests/test_data/cert/,确保 tls_config.yaml 引用的路径与挂载点依旧吻合。
兼容性数据再生成时机
- 任何会改变磁盘持久化格式的改动落地后,例如段文件结构、HNSW 索引布局、payload 索引序列化、量化(标量/乘积/二进制)持久化、稀疏向量索引等。一个务实的判断方法是:改动发生在 lib/segment、lib/collection、lib/quantization 等存储相关 crate,并在代码评审中出现过“旧版本是否还能读”的疑问时,就该刷新参考数据;
- 生成时务必回答正确的版本号:新归档通常应基于“上一个已发布稳定版”生成,使 CI 矩阵总能覆盖「旧格式 → 新代码」的方向;若向前追加多个版本,则相应扩展 test_data_compatibility.py 中的
VERSIONS列表; - 归档生成后需上传到
qdrant-backward-compatibilitybucket(需维护者授予的 GCS 凭证),CI 才能下载到新数据。
七、小结与自检清单
从仓库证据看,两套维护流程互为表里:TLS 测试依赖「受控 PKI」模拟真实加密部署,兼容性测试依赖「冻结的旧版本数据」守护存储演进安全。日常贡献可遵循如下检查单:
- 修改了集群拓扑/主机名 → 检查 cert.cfg SAN → 运行 gen.sh → 替换 cert 下三个 pem;
- 修改了持久化存储格式 → 用合适版本运行 gen_storage_compat_data.sh → 上传
compatibility-<version>.tar到 bucket → 必要时扩展VERSIONS与EXPECTED_COLLECTIONS; - 本地验证:TLS 场景运行 test_tls.py,兼容性场景运行 test_data_compatibility.py,并确保 Docker 环境与 GCP bucket 可达。
主文档给出的全部操作步骤均已在上文展开并补充了参数、原理与触发条件,据此即可独立完成 Qdrant 仓库中这两类“再生成”维护任务。
【免费下载链接】qdrantQdrant - High-performance, massive-scale Vector Database and Vector Search Engine for the next generation of AI. Also available in the cloud https://cloud.qdrant.io/项目地址: https://gitcode.com/GitHub_Trending/qd/qdrant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考