Envoy 上游 DNS 解析架构详解:四种可插拔 DNS 解析器、配置参数与统计指标
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
Envoy 中大量组件都依赖 DNS 解析:STRICT_DNS / LOGICAL_DNS 集群、动态正向代理(dynamic forward proxy)系统、UDP DNS 过滤器等。本篇基于仓库架构文档 DNS Resolution 展开,结合 DNS 解析器接口、工厂选择逻辑 和 c-ares 解析器配置 proto,完整讲清 Envoy 的 DNS 解析是如何被抽象为可插拔扩展、四种内置解析器各自的行为差异、c-ares 解析器的全部配置参数及默认值,以及dns.cares/dns.apple/dns.hickory统计树下的可观测指标。读完本文,你可以准确选择解析器扩展、写出可复制的typed_dns_resolver_config,并依据统计数据诊断解析失败、超时与通道重初始化等问题。
一、谁在用 DNS 解析:Envoy 中的 DNS 消费方
根据架构文档,Envoy 中有多类组件会发起 DNS 解析:
- 集群类型:
STRICT_DNS(严格 DNS,必须解析成功才加入 host)与LOGICAL_DNS(逻辑 DNS,域名本身作为一个逻辑主机)两种上游集群类型; - 动态正向代理系统:由集群与 HTTP 过滤器组合而成,按请求目标域名动态建立上游连接,其 DNS 缓存由
typed_dns_resolver_config字段配置解析器,可参考 HTTP 过滤器文档; - UDP DNS 过滤器:作为 DNS 代理转发查询时也需要底层解析能力。
这些消费方在源码中的落点可以相互印证:集群侧在 cluster_factory_impl.cc 中根据集群类型构建解析器;动态正向代理的 DNS 缓存在 dns_cache_impl.cc 中创建;UDP 过滤器在 dns_filter.cc 中创建。所有消费方并不直接依赖某个具体解析库,而是依赖统一的Network::DnsResolverFactory接口。
二、可插拔抽象:DnsResolver 接口与 DnsResolverFactory
从源码结构看,整个 DNS 子系统的抽象核心是两个接口文件:
- DnsResolverFactory:继承自
Config::TypedFactory,每个解析器扩展注册一个工厂,其createDnsResolver(dispatcher, api, typed_dns_resolver_config)方法基于 typed 配置实例化具体解析器。dispatcher是本地工作线程的事件分发器,api用于访问系统资源。 - DnsResolver:异步解析器抽象,核心方法有:
resolve(dns_name, dns_lookup_family, callback):发起异步解析,返回ActiveDnsQuery*句柄,调用方可用于取消查询;resetNetworking():提示解析器重置网络状态,典型场景是网络切换(如 WiFi 切到蜂窝网络),具体行为由各解析器自行决定,可能涉及重建解析连接、重读解析目标等;- 回调类型
ResolveCb接收ResolutionStatus(Completed/Failure)、details字符串以及解析出的地址/TTL 列表。值得注意的实现细节:Completed并不代表“解析到地址”,文档注释明确说明 NODATA、SERVFAIL、NONAME 等都属于“查询完成但无结果”的语义,因此Completed才是更准确的表达。
与解析结果相关的几个数据结构同样定义在 dns.h:
AddrInfoResponse:A/AAAA 记录,包含地址与 TTL;SrvResponse:SRV 记录,包含 host、port、priority、weight;DnsLookupFamily枚举:V4Only、V6Only、Auto、V4Preferred、All,控制 IP 版本查询策略;ActiveDnsQuery::CancelReason:区分QueryAbandoned(调用方不再需要结果)与Timeout(调用方视角超时,解析器可能借此销毁现有连接以便后续查询更快得到答案)。
三、四种内置 DNS 解析器扩展
Envoy 通过可插拔扩展提供 DNS 解析,默认使用 c-ares 作为解析库。仓库内置 4 个解析器扩展,源码分别位于 source/extensions/network/dns_resolver/ 下的cares/、apple/、getaddrinfo/、hickory/子目录,配置 proto 位于 api/envoy/extensions/network/dns_resolver/:
| 解析器 | 扩展名 | 类型配置消息 | 适用平台/特点 |
|---|---|---|---|
| c-ares | envoy.network.dns_resolver.cares | CaresDnsResolverConfig | 默认实现,纯 C 异步 DNS 库 |
| Apple | envoy.network.dns_resolver.apple | AppleDnsResolverConfig | 仅 iOS/macOS,通过 Apple 专用 API 解析 |
| getaddrinfo | envoy.network.dns_resolver.getaddrinfo | GetAddrInfoDnsResolverConfig | 基于系统getaddrinfo,无解析器专属统计 |
| Hickory DNS | envoy.network.dns_resolver.hickory | HickoryDnsResolverConfig | 纯 Rust 解析器,支持 DoT/DoH/DNSSEC |
3.1 c-ares(默认)
c-ares 解析器实现位于 cares/dns_impl.h。从源码结构看,它维护一个ares_channel,所有调用与回调都假定发生在拥有创建用 dispatcher 的线程上;resetNetworking()的实现就是reinitializeChannel(),即重建 c-ares 通道。此外还包含以下机制,均可在头文件成员变量中找到对应物:
- UDP 通道周期刷新:
udp_channel_refresh_timer_配合max_udp_channel_duration定期重建通道,避免陈旧 socket 状态并改善 UDP 端口负载分布; - 双栈解析:
AddrInfoPendingResolution中dual_resolution_逻辑——当dns_lookup_family为V4Preferred或Auto时,首次解析失败后发起第二次解析;为All时则对两个 IP 族并发查询; - 不可路由地址族过滤:
filter_unroutable_families开启时,解析器会查询本机网络接口可用性,若没有 IPv4 接口就不再返回 IPv4 地址。
3.2 Apple(iOS/macOS)
Apple 解析器实现在 apple/apple_dns_impl.h,仅当在 Apple 系统上构建时才编译。它可通过 runtime 特性envoy.restart_features.use_apple_api_for_dns_lookups在 Apple 系统上替代默认解析路径(详见下文的工厂选择逻辑)。
3.3 getaddrinfo
实现位于 getaddrinfo/getaddrinfo.cc,直接委托系统的getaddrinfoAPI。架构文档特别指出:getaddrinfo 解析器目前不产生解析器专属统计,这是它与另外三种实现的可观测性差异,监控时需注意。
3.4 Hickory DNS(纯 Rust)
Hickory 是一个基于 Hickory DNS 库的纯 Rust 解析器,支持标准 DNS(UDP/TCP)、DNS-over-TLS(DoT)、DNS-over-HTTPS(DoH)与 DNSSEC 校验。从源码结构看(hickory/hickory_dns_impl.h),它通过 Envoy 的动态模块(dynamic modules)框架接入:HickoryDnsResolverConfig保存的是一组从动态模块解析出来的函数指针(on_dns_resolver_config_new_、on_dns_resolver_new_等),而不是直接调用 Rust 代码。解析器在其自己的 Tokio runtime 线程上异步运行,独立于 Envoy 的事件循环,因此 DNS 解析不会阻塞 dispatcher 线程。
四、解析器如何选择:工厂选择优先级
不同消费方(集群、DNS 缓存、UDP 过滤器)最终都通过 dns_factory_util.h 中的makeDnsResolverConfig()把各自异构的配置归一化为一个TypedExtensionConfig。从源码的分支顺序看,选择优先级如下:
typed_dns_resolver_config显式指定:配置中已存在 typed 解析器配置时,原样采用,优先级最高;- Apple 系统专用 API:
tryUseAppleApiForDnsLookups()检测平台与 runtime 特性envoy.restart_features.use_apple_api_for_dns_lookups,满足则生成AppleDnsResolverConfig; dns_resolution_config:配置中存在该字段时,将其resolvers与dns_resolver_options映射进CaresDnsResolverConfig;- 遗留字段兜底:
handleLegacyDnsResolverData()为向后兼容复制旧字段(如 bootstrap 与 DNS 缓存配置上的use_tcp_for_dns_lookups),并始终生成 c-ares typed 配置。
若完全未指定解析器配置,则调用createDefaultDnsResolverFactory():从源码注释看,默认行为是macOS 上用 Apple 解析器,其他平台一律 c-ares。这一机制解释了架构文档中“默认使用 c-ares,Apple 系统额外提供 Apple API 路径”的描述。
五、c-ares 解析器完整配置参数
CaresDnsResolverConfig定义在 cares_dns_resolver.proto,是内置 DNS typed 配置的典型示例。各字段说明如下:
| 字段 | 类型 | 说明 |
|---|---|---|
resolvers | repeated Address | 自定义 DNS 服务器地址列表;是否覆盖系统默认由use_resolvers_as_fallback决定 |
use_resolvers_as_fallback | bool | true时仅当 c-ares 无法从系统(如/etc/resolv.conf)获得 nameserver 才使用resolvers;否则resolvers覆盖系统默认。默认false |
filter_unroutable_families | bool | 查询本机接口可用性,过滤掉无对应网络接口地址族的解析结果(如无 IPv4 接口则不返回 IPv4 地址) |
dns_resolver_options | DnsResolverOptions | 见下节 |
udp_max_queries | UInt32Value | 限制基于 UDP 的 DNS 查询数量上限(当前仅 c-ares 解析器适用) |
query_timeout_seconds | UInt64Value | 每个 name server 首次响应一个查询的超时秒数,最小 1。注意:c-ares 库默认 2 秒,而Envoy 未设置时的默认值是 5 秒,这是为维持既有行为而做的调整 |
query_tries | UInt32Value | 放弃前的最大查询尝试次数,每次尝试可能使用不同 name server,最小 1。c-ares 库默认 3 次,Envoy 默认 4 次 |
rotate_nameservers | bool | 启用后对 name server 做轮转选择以均衡查询负载;禁用(默认)时按配置顺序尝试。该设置覆盖系统的 name server 轮转配置 |
edns0_max_payload_size | UInt32Value | 最大 EDNS0 UDP 载荷字节数,取值 512–4096。推荐值 1232(安全默认,避免分片)或 4096(最大允许);不设置时由 c-ares 内部默认(通常 1232) |
max_udp_channel_duration | Duration | 设置后 DNS 解析器会在该时长周期性地重新初始化 c-ares 通道,帮助摆脱陈旧 socket 状态、改善 UDP 端口负载分布;不设置则不做周期刷新 |
reinit_channel_on_timeout | bool | true时当查询以ARES_ETIMEOUT失败就重新初始化通道,可快速恢复偶发的 UDP socket 不可用;在网络抖动导致超时的环境中会增加通道重建频率,此时更建议改用max_udp_channel_duration做周期刷新。默认false |
qcache_max_ttl | UInt32Value | c-ares 内部 DNS 响应缓存的最大 TTL(秒),取值 ≥ 0;设为非零值时启用查询缓存并尊重响应 TTL(不超过该上限)。注意 c-ares 库默认缓存 1 小时,而Envoy 该字段默认 0,即完全禁用查询缓存 |
5.1 DnsResolverOptions
dns_resolver_options指向 resolver.proto 中的DnsResolverOptions,包含两个开关:
use_tcp_for_dns_lookups:所有 DNS 查询使用 TCP 而非默认 UDP;no_default_search_domain:不使用默认搜索域,仅按原样查询主机名或其别名。
同一 proto 中的DnsResolutionConfig(resolvers+dns_resolver_options)则对应上文选择优先级中的第 3 级,用于非 typed 的集群/DNS 缓存配置场景。
六、统计指标:dns.cares / dns.apple / dns.hickory
三种解析器各自输出独立的统计树,与源码中的统计宏定义一一对应(如 cares/dns_impl.h 的ALL_CARES_DNS_RESOLVER_STATS)。
6.1 c-ares(dns.cares统计树)
| 名称 | 类型 | 说明 |
|---|---|---|
resolve_total | Counter | DNS 查询总数 |
pending_resolutions | Gauge | 当前未完成的 DNS 查询数 |
not_found | Counter | 返回NXDOMAIN或NODATA的查询数 |
get_addr_failure | Counter | DNS 查询过程中的一般性失败数 |
timeouts | Counter | 超时的查询数 |
reinits | Counter | c-ares 通道重新初始化次数 |
其中reinits与timeouts组合特别有用:若reinit_channel_on_timeout开启,超时会触发通道重建,reinits的持续增长可以帮助判断 UDP socket 健康问题。
6.2 Apple(dns.apple统计树)
| 名称 | 类型 | 说明 |
|---|---|---|
connection_failure | Counter | 连接 DNS 服务器的失败尝试数 |
get_addr_failure | Counter | 调用 GetAddrInfo API 时的一般性失败数 |
network_failure | Counter | 因网络连通性导致的失败数 |
processing_failure | Counter | 处理来自 DNS 服务器数据时的失败数 |
socket_failure | Counter | 获取到 DNS 服务器的 socket 文件描述符的失败尝试数 |
timeout | Counter | 超时的查询数 |
6.3 Hickory(dns.hickory统计树)
| 名称 | 类型 | 说明 |
|---|---|---|
resolve_total | Counter | 已完成的 DNS 查询数 |
pending_resolutions | Gauge | 当前在途(in-flight)的 DNS 查询数 |
not_found | Counter | 返回NXDOMAIN或NODATA响应的查询数 |
get_addr_failure | Counter | DNS 查询过程中的一般性失败数 |
timeouts | Counter | 超时的查询数 |
6.4 getaddrinfo
getaddrinfo 解析器目前不产生解析器专属统计(架构文档 note 明确说明)。若部署中使用该解析器,需要依赖其他通用指标间接观察解析状况。
七、实践要点小结
- 不配置解析器时:macOS 上默认 Apple 解析器(可被 runtime 特性切换),其他平台默认 c-ares;需要精确控制时显式设置
typed_dns_resolver_config,它拥有最高优先级。 - 超时与重试:记住 Envoy 与 c-ares 库默认值不同(超时 5s/4 次尝试,而非库默认的 2s/3 次);
qcache_max_ttl默认 0 表示缓存禁用。 - UDP socket 健康:周期性通道刷新(
max_udp_channel_duration)与超时即重建(reinit_channel_on_timeout)二者取其一即可,前者适合长期稳定运行,后者适合偶发 socket 故障但会引入通道重建开销。 - 可观测性:按解析器选择对应的统计树(
dns.cares/dns.apple/dns.hickory);使用 getaddrinfo 时没有专属指标可用。 - 平台相关:Apple 解析器仅在 iOS/macOS 构建中可用;Hickory 通过动态模块框架以纯 Rust 运行在独立 Tokio 线程,适合需要 DoT/DoH/DNSSEC 且希望解析不占用事件循环线程的场景。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考