
RDS 携带无效 VHDS 配置时被半接受更新的修复分析【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读本文基于 Envoy 官方变更记录中一条 bug fix 条目changelogs/current/bug_fixes/rds__invalid-vhds-half-accepted-update.rst深入剖析一个 RDSRoute Discovery Service更新携带无效 VHDSVirtual Host Discovery Service配置时被半接受half-accepted的缺陷及其修复思路。文章以该变更记录为核心骨架结合仓库中 route_config_update_receiver_impl.cc、vhds.cc 及对应单元测试 vhds_test.cc还原缺陷成因、复现路径、修复原理与验证方法。读完本文你将理解 Envoy 中 RDS/VHDS 联合更新的状态机时序、config_dump 与 worker 数据面不一致的成因以及修复后先验证、后落账、再预热的顺序保证。背景RDS 与 VHDS 的分工与协同在 Envoy 的动态配置体系中路由配置由 RDS 负责下发而 VHDS 则是 RDS 的补充机制用于将 VirtualHost 的发现进一步拆分实现大规模虚拟主机的按需/增量下发。二者的关系体现在 RouteConfiguration 中RDS 下发的RouteConfiguration可以内嵌若干virtual_hosts若配置了vhds字段则额外的 VirtualHost 由 VHDS 通过 Delta xDS 协议增量下发VHDS 只支持DELTA_GRPC或ADS且 ADS 必须处于 Delta 模式这一约束在 vhds.cc 的VhdsSubscription::createVhdsSubscription中被显式校验。当一个 RDS 更新同时携带新的RouteConfiguration和新的 VHDS 配置时Envoy 必须保证路由配置的落账record、VHDS 订阅的创建create、预热warm up与发布publish严格按序进行任何一步失败都不能留下配置已记录但未生效的中间状态。缺陷现象配置被半接受变更记录原文描述了问题的两个可观察症状admin/config_dump端点报告的是被拒绝rejected的路由配置——即新配置虽然在验证阶段失败被拒绝但状态记录已经推进config_dump 读到了已记录的新配置worker 线程仍然在服务旧的路由配置——即数据面实际使用的路由表并未切换。这两者叠加意味着系统处于管理面说新配置已经来了、数据面却还在用旧配置的不一致状态既不符合更新要么全量成功、要么全量失败的原子性预期也会给运维排障带来严重误导——用户通过 config_dump 看到新配置已经记录却无法解释流量为何仍走旧路由。缺陷根因状态记录早于 VHDS 订阅创建从修复后的源码反推缺陷的根因在 RouteConfigUpdateReceiverImpl::onRdsUpdate 的处理顺序上。修复后的代码明确展示了正确的顺序约定先构建路由配置config_traits_.createConfig(...)将新RouteConfiguration编译为可执行的配置对象ConfigImpl若配置无效会抛出异常此时更新直接被拒绝任何状态都不动再创建 VHDS 订阅配置验证通过后若新配置带vhds字段调用createVhdsSubscription(...)创建订阅。这一步可能失败——例如 VHDS 配置源非法非DELTA_GRPC或非 Delta 模式的ADS、bootstrap 中未配置 ADS、ADS 的api_type不是DELTA_GRPC等校验逻辑见 vhds.cc此时同样直接拒绝更新最后才更新状态base_.updateState(...)将新配置写入内部状态随后base_.startWarming()启动预热。修复前步骤 3 中记录新路由配置的动作发生在步骤 2创建 VHDS 订阅之前。于是当步骤 2 失败时更新虽然被拒绝但新路由配置已经被记入状态config_dump的数据源正是这份状态而 worker 侧的预热/发布流程从未完成自然继续服务旧配置——这正是半接受的由来。值得强调的是修复后源码中 第 126-129 行注释 明确写道The new route configuration and VHDS subscription have been built without error, now we can update the state and start warming up.即只有新路由配置和 VHDS 订阅都无错构建后才能更新状态并开始预热这正是针对该缺陷的顺序保证。修复后的完整处理流程修复后的onRdsUpdate流程可拆解为以下几个关键阶段对应 route_config_update_receiver_impl.cc阶段一哈希去重uint64_t new_hash base_.getHash(rc); if (!base_.checkHash(new_hash)) { // 配置未变化无需构建、预热或发布 return absl::OkStatus(); }若新配置与当前已发布配置哈希相同直接跳过避免无谓的构建与预热。阶段二构建路由配置auto update_init_manager base_.warmer_.createInitManager(update_id); auto config config_traits_.createConfig( *new_route_config, factory_context_, *update_init_manager, false /* not validate unknown cluster */);注意 init manager 在此时是局部持有的注释明确说明init manager 一直保持局部直到确认新路由配置构建不抛异常从而保证被拒绝的更新不会打扰仍在预热的上一次更新。也就是说构建失败时之前仍在预热的更新会原封不动地继续。阶段三创建 VHDS 订阅if (has_vhds) { if (!base_.initialized_) { // 首次有效更新前创建订阅并挂到本次更新的 init manager 上 auto subscription_or_error createVhdsSubscription(*new_route_config, *update_init_manager); RETURN_IF_NOT_OK_REF(subscription_or_error.status()); new_vhds_subscription std::move(subscription_or_error.value()); } else if (vhds_configuration_changed || vhds_subscription_ nullptr) { // 已初始化但 VHDS 配置变化用 noop init manager 创建新订阅 // 避免阻塞主 init manager向后兼容旧行为 ... } // 否则VHDS 配置未变保留现有订阅 }这里有两个设计要点值得展开订阅失败即整体拒绝createVhdsSubscription返回absl::StatusOr任何校验失败都会让onRdsUpdate提前返回错误此时阶段四不会执行首次更新与后续更新的差异首次有效更新initialized_ false时新订阅挂到本次更新自己的 init manager 上参与预热后续 VHDS 配置变化的更新则使用 noop init manager保持VHDS 订阅未就绪也不阻塞新 RDS 更新的向后兼容语义第 106-119 行注释。阶段四落账 启动预热base_.updateState(std::move(new_route_config), new_hash, version_info, std::move(config), std::move(update_init_manager), std::move(update_id)); if (new_vhds_subscription ! nullptr) { vhds_subscription_ std::move(new_vhds_subscription); } else if (!has_vhds) { vhds_subscription_.reset(); } last_vhds_config_hash_ new_vhds_config_hash; rds_virtual_hosts_ std::move(rds_virtual_hosts); base_.startWarming();只有走到这一步状态才被推进、config_dump 才会反映新配置。此时新配置与 VHDS 订阅都已无错构建后续的预热失败属于运行期事件不再属于半接受范畴。为什么 VHDS 订阅创建可能失败源码级校验清单VhdsSubscription::createVhdsSubscriptionvhds.cc是失败高发点也是本缺陷中订阅创建失败的具体来源。它依次执行三类校验校验项失败条件错误信息传输协议config_source既不是ADS也不是api_type DELTA_GRPC的独立 gRPCvhds: only DELTA_GRPC or ADS (which uses Delta xDS) is supported as a config source.ADS 是否配置使用 ADS 但 bootstrap 的dynamic_resources.ads_config缺失vhds: ADS config source specified but no ADS configured in bootstrap.ADS 模式使用 ADS 但 bootstrap 中ads_config.api_type ! DELTA_GRPCvhds: ADS must use DELTA_GRPC api_type when used as VHDS config source.这三种失败路径在单元测试 vhds_test.cc 中均有覆盖VhdsInstantiationShouldFailWithoutDELTA_GRPCapi_type: GRPC的独立配置源onRdsUpdate返回非 OK 状态VhdsInstantiationShouldFailWithAdsButNoBootstrapConfigbootstrap 未配置 ADSVhdsInstantiationShouldFailWithAdsButWrongApiTypeADS 使用GRPC而非DELTA_GRPC断言错误信息vhds: ADS must use DELTA_GRPC api_type when used as VHDS config source.反向用例VhdsInstantiationShouldSucceedWithAdsAndDeltaGrpcbootstrap ADS 为DELTA_GRPC时创建成功。这些用例的共性断言是EXPECT_THAT(config_update_info-onRdsUpdate(route_config, 1), Not(IsOk()))——即更新必须整体失败。在修复之前这类失败的更新会在失败后留下新配置已记录的残余状态正是本 bug 的验证盲区。测试验证修复如何被固化除失败路径外vhds_test.cc 还验证了成功路径下 RDS/VHDS 的协同VhdsAddsVirtualHostsL241-L259VHDS 增量下发的 VirtualHost 被合并进路由配置virtual_hosts_size()从 0 变为 1RdsUpdatesVirtualHostsL262-L329RDS 更新自带 VirtualHost 时VHDS 下发的 VirtualHost 保持完好两者合并后总数正确2 个 RDS vhost 1 个 VHDS vhost 3。其底层合并逻辑在 route_config_update_receiver_impl.cc 的 rebuildRouteConfigVirtualHosts先清空再依次合并 RDS vhosts 与 VHDS vhostsVhdsUpdateWithoutChangesKeepsTheRouteConfigL333-L356无增删的 VHDS 更新不触发重建已发布配置对象指针保持不变避免无效预热。这些用例共同保证了成功的更新被正确合并发布失败的更新被整体拒绝且两种情况下都不存在半接受的中间状态。运维视角如何规避与排查虽然该缺陷已在源码中修复但从运维角度仍可从三个方面降低风险、提升排障效率配置先行校验在将携带vhds字段的 RouteConfiguration 推送到 xDS 管理面之前先在本地用envoy --mode validate或配置校验工具检查 VHDS 的config_source类型、ADS 的api_type是否为DELTA_GRPC从源头减少失败更新观察 config_dump 与流量的时序一致性排障时应同时对比 admin/config_dump中的路由配置与 worker 实际生效的路由可通过/routes?include_virtual_hoststrue等端点观察若二者不一致优先怀疑更新被部分接受关注订阅失败日志createVhdsSubscription的三种失败路径都有明确错误信息配合config_reload统计见 vhds.cc 中的 ALL_VHDS_STATS可以快速定位失败原因。小结本文围绕 Envoy 变更记录中RDS 更新携带无效 VHDS 配置被半接受的 bug fix完整还原了缺陷现象/config_dump报告被拒绝的新配置而 worker 仍服务旧配置管理面与数据面不一致缺陷根因新路由配置被记录updateState先于 VHDS 订阅创建订阅创建失败时更新被拒绝但状态已推进修复原理将onRdsUpdate的顺序调整为构建路由配置 → 创建 VHDS 订阅 → 落账并预热并配合局部 init manager 保证失败更新不干扰在途预热route_config_update_receiver_impl.cc验证方式vhds_test.cc 中Not(IsOk())断言确保无效配置整体拒绝成功路径用例确保 RDS/VHDS 合并正确。该修复的实质是为涉及多数据源协同的动态更新确立了一条不可动摇的原子性铁律状态推进必须以所有依赖配置编译、订阅创建的成功为前提。这一顺序保证不仅适用于 RDS/VHDS也是理解 Envoy 动态配置更新一致性的通用范式。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考