- 后端
- 云原生
- 运维
- 虚拟化
- 人工智能
- AI 应用
【免费下载链接】cloudpods
开源、云原生的多云管理及混合云融合平台
本指南以 Cloudpods 仓库 vendor 目录中 CoreDNS v1.10.1 的 forward 插件官方文档 为骨架,结合 插件源码 逐项剖析 DNS 转发的工作原理,并给出可在 Cloudpods region-dns 服务中直接使用的 Corefile 配置实战示例。读完本文,你将掌握 forward 插件的完整语法、UDP/TCP/DoT 三种上游协议的选择、带内健康检查机制、四种上游选择策略,以及并发限制、连接缓存等性能调优手段,并能独立编写多域名、多上游的转发配置。
一、forward 插件是什么
forward是 CoreDNS 中用于将 DNS 查询消息代理(proxy)到上游解析器的插件。它的核心设计思想是复用已经打开的到上游的 socket 连接,而不是为每个客户端请求都新建连接——这一点在插件源码的包注释中直接写明:"It caches an upstream net.Conn for some time, so if the same client returns the upstream's Conn will be precached",在基准测试中相比每次新建连接大约有 50% 的性能提升(connect.go)。
它同时支持三种传输协议:
- UDP:默认的明文 DNS 传输;
- TCP:通过
force_tcp或dns://指定; - DNS-over-TLS(DoT):通过
tls://前缀启用。
并且使用带内健康检查(in band health checking)来感知上游健康状态,而非依赖外部探活工具。
在 Cloudpods 中,该插件以 vendor 依赖形式随 CoreDNS 一起被编译进 region-dns 服务。cmd/region-dns/main.go 通过空白导入_ "github.com/coredns/coredns/plugin/forward"注册该插件,并在插件执行顺序(directives)表中将forward排在yunion(Cloudpods 自定义 DNS 插件)、hosts、file之后、whoami之前,意味着查询会先经过 Cloudpods 的yunion插件解析云平台内建的域名记录,未命中的请求再落入 forward 转发链(cmd/region-dns/main.go)。
二、基本语法与执行模型
2.1 最简形式
forward FROM TO...- FROM:需要被转发请求的基准域名。注意,使用 CIDR 记法展开为多个反向 zone 的域名并未被完整支持,只会使用第一个展开的 zone(源码 setup.go 中会打印
Unsupported CIDR notation警告)。 - TO...:转发目标端点,最多15 个上游(常量
const max = 15,见 setup.go)。TO 支持指定协议前缀:tls://9.9.9.9表示 DoT,dns://(或省略协议前缀)表示明文 DNS。
多个上游在首次使用时按policy(默认random)随机化。当某个健康的上游在交换过程中返回错误时,会依次尝试列表中的下一个上游。这一"故障转移"逻辑在 forward.go 的ServeDNS中实现:外层循环按 deadline(defaultTimeout = 5 * time.Second)遍历上游列表,跳过Down()判定为宕机的代理,直到收到有效响应。
2.2 完整扩展语法
forward FROM TO... { except IGNORED_NAMES... force_tcp prefer_udp expire DURATION max_fails INTEGER tls CERT KEY CA tls_servername NAME policy random|round_robin|sequential health_check DURATION [no_rec] [domain FQDN] max_concurrent MAX }所有块内选项的解析集中在 setup.go 的parseBlock函数,下面逐项说明。
三、核心参数详解
3.1 except —— 排除转发名单
IGNORED_NAMES是空格分隔的域名列表,匹配这些域名的请求不转发,而是继续走插件链中的下一个插件(pass through)。判断逻辑在 forward.go:
- 若查询名恰好等于
from域名,直接放行(转发); - 遍历
ignored列表,若任一 ignore 域名匹配查询名,则返回 false,交由下一插件处理。
3.2 force_tcp 与 prefer_udp —— 传输协议偏好
force_tcp:即使请求从 UDP 进来,也强制使用 TCP 转发到上游;prefer_udp:即使请求从 TCP 进来,也优先尝试 UDP;若响应设置了 TC(截断)标志,则自动改用 TCP 重试一次。
两者同时配置时,force_tcp优先。这个优先级在 connect.go 的Connect中体现:
case opts.forceTCP: // TCP flag has precedence over UDP flag proto = "tcp" case opts.preferUDP: proto = "udp" default: proto = state.Proto()而"TC 标志触发 TCP 重试"的逻辑在 forward.go:若响应被截断且非 forceTCP 且开启了 preferUDP,则把 opts.forceTCP 置为 true 后在同一连接循环内重试。
3.3 expire —— 连接缓存过期时间
expire DURATION:上游连接缓存的过期时间,默认10s(defaultExpire = 10 * time.Second,见 persistent.go)。连接缓存按 UDP、TCP、tcp-tls 三种类型分桶(persistent.go),由独立的connManagergoroutine 管理:取连接时若栈顶连接已超过 expire 则清空整个缓存桶并异步关闭连接;同时每秒由 ticker 触发cleanup清理过期连接(persistent.go)。归还连接(Yield)采用非阻塞方式,超时窗口为 25ms,避免繁忙时阻塞外层 goroutine。
3.4 max_fails —— 宕机判定阈值
max_fails INTEGER:判定上游"宕机"所需的连续健康检查失败次数,默认2。若设为 0,则上游永不被标记为宕机,也不做健康检查。判定逻辑在 proxy.go:
func (p *Proxy) Down(maxfails uint32) bool { if maxfails == 0 { return false } fails := atomic.LoadUint32(&p.fails) return fails > maxfails }注意是fails > maxfails,即失败次数严格大于阈值才判定宕机。
3.5 tls —— TLS 配置
tls CERT KEY CA定义 TLS 连接属性,支持 0~3 个参数:
| 参数形式 | 含义 |
|---|---|
tls(无参数) | 不做客户端认证,用系统 CA 验证服务器证书 |
tls CA | 不做客户端认证,用指定 CA 文件验证服务器证书 |
tls CERT KEY | 使用指定证书/密钥对做客户端认证,服务器证书用系统 CA 验证 |
tls CERT KEY CA | 使用指定证书/密钥对做客户端认证,服务器证书用指定 CA 文件验证 |
TLS 配置对整个转发代理是全局的——如果需要为不同上游配置不同的tls_servername,无法在同一个 forward 块内实现(后文示例给出绕行方案)。在 setup.go 中,解析完成后还会为 tls.Config 初始化ClientSessionCache(LRU,容量为上游数量),用于加速后续到同一 TLS 服务器的握手。
3.6 tls_servername —— TLS 服务器名称
tls_servername NAME设置 TLS 协商时的 ServerName(SNI)。例如转发到 9.9.9.9 必须设为dns.quad9.net。多个上游仍被允许,但必须使用同一个tls_servername——比如混用 9.9.9.9(Quad9)与 1.1.1.1(Cloudflare)是不行的。
文档特别强调:使用 TLS 转发却不设置tls_servername,会使得任何人都可以对你的转发连接实施中间人攻击,因此强烈建议在使用 TLS 转发时总是配置该值。从源码看,该值最终写入tlsConfig.ServerName(setup.go),而 DoT 拨号使用dns.DialTimeoutWithTLS("tcp", t.addr, t.tlsConfig, timeout)(connect.go),缺少 SNI 时对端将无法完成证书校验。
3.7 policy —— 上游选择策略
默认random,可选三种:
random:随机选择上游。实现上对 2 个上游做随机交换,超过 2 个用rn.Perm(len(p))生成全排列(policy.go);round_robin:轮询。通过原子递增计数器取模定位,并把命中的上游移到列表首位(policy.go);sequential:严格按配置顺序选择(policy.go)。
选择策略只影响"优先尝试哪个上游",一旦出错仍会按列表顺序故障转移。
3.8 health_check —— 健康检查行为
health_check DURATION [no_rec] [domain FQDN]<duration>:健康检查间隔,默认0.5s(hcInterval = 500 * time.Millisecond,见 proxy.go);no_rec:将健康检查 DNS 查询的 RecursionDesired 标志置为false(默认true);domain FQDN:设置健康检查使用的域名,默认是.。
健康检查的完整机制值得展开:
- 触发时机:当转发请求出错时触发一轮检查(forward.go 中
proxy.Healthcheck())。检查以循环方式运行,只要上游仍报告不健康,就每 0.5s 检查一次;一旦恢复健康即停止检查。 - 检查内容:向每个上游发送递归 DNS 查询(
. IN NS)。任何非网络错误的响应(如 REFUSED、NOTIMPL、SERVFAIL 等)都视为上游健康。这在 health.go 的send中实现:只要收到响应头(m.Response || m.Opcode == dns.OpcodeQuery),I/O 错误即被忽略,视为健康。 - 协议一致性:健康检查使用与 TO 指定的相同协议。DoT 上游的检查用
tcp-tls,force_tcp时非 TLS 上游检查改用tcp(setup.go)。健康检查客户端超时固定为读/写各 1s(health.go)。 - 健康检查关闭:
max_fails设为 0 时不执行任何检查,上游永远被视为健康。
另外有一个值得注意的兜底行为:当所有上游都被判定为宕机时,forward 假定健康检查机制本身已经失效(可能是误判),会改用random策略随机挑选一个上游强行连接(无论其是否健康),并递增HealthcheckBrokenCount指标(forward.go)。
3.9 max_concurrent —— 并发查询上限
max_concurrent MAX限制并发查询数量。超过 MAX 的新查询直接返回REFUSED,且该拒绝不计入健康检查失败。
实现上使用原子计数器(forward.go):atomic.AddInt64递增后若超过上限,返回dns.RcodeRefused并累加MaxConcurrentRejectCount指标。
选值建议:
- 下限:至少大于
上游查询速率 × 上游延迟(即带宽延迟积,否则会误伤正常流量); - 上限:每个并发查询大约占用2KB 内存,据此推算内存预算。
3.10 超时约定
每个端点的通信超时如下:
- 拨号超时:默认 30s,但会根据早期结果自动下调,最低到 1s;
- 读超时:固定 2s。
拨号超时的自适应算法在 connect.go:
limitTimeout:取当前均值,若小于最小值则返回最小值(1s);若小于最大值的一半则返回 2 倍均值;否则返回最大值(30s);averageTimeout:用移动平均(权重cumulativeAvgWeight = 4)把观测到的拨号耗时平滑进均值。
读超时固定 2s 的理由写在 persistent.go:有些解析器耗时较长,但通常(缓存)响应较快,2s 既留有余量又给故障转移保留了时间窗口。整个转发请求的外层 deadline 为 5s(defaultTimeout,见 forward.go)。
四、元数据与监控指标
4.1 Metadata
若同时启用了metadata插件,forward 会发布如下元数据:
forward/upstream:实际用于转发该请求的上游地址。
其赋值通过metadata.SetValueFunc(ctx, "forward/upstream", ...)惰性求值(forward.go),可在日志、ACL 等下游插件中读取。
4.2 Prometheus 指标
若通过prometheus插件开启监控,forward 导出以下指标(定义见 metrics.go):
| 指标 | 标签 | 含义 |
|---|---|---|
coredns_forward_requests_total | to | 每个上游的查询计数 |
coredns_forward_responses_total | to | 每个上游收到的响应计数 |
coredns_forward_request_duration_seconds | to, rcode, type | 每个上游/RCODE/类型的耗时直方图 |
coredns_forward_responses_total | to, rcode | 每个上游的 RCODE 分布 |
coredns_forward_healthcheck_failures_total | to | 每个上游的健康检查失败次数 |
coredns_forward_healthcheck_broken_total | 无 | 所有上游都不健康、随机喷洒(总是用 random 策略)的计数 |
coredns_forward_max_concurrent_rejects_total | 无 | 因并发达到上限而被拒绝的查询数 |
coredns_forward_conn_cache_hits_total | to, proto | 每个上游/协议的连接缓存命中次数 |
coredns_forward_conn_cache_misses_total | to, proto | 每个上游/协议的连接缓存未命中次数 |
其中to是配置中的上游服务器(TO),rcode是上游返回的 RCODE,proto是传输协议(udp、tcp、tcp-tls)。请求计数与 RCODE 计数在实际请求路径的Connect中同步累加(connect.go)。
此外,若同时加载了 dnstap 插件,forward 还会发送FORWARDER_QUERY与FORWARDER_RESPONSE两类 dnstap 消息,便于做全链路 DNS 流量审计(dnstap.go)。
五、实战配置示例
以下示例均为标准 Corefile 语法,可直接用于 CoreDNS / Cloudpods region-dns 的配置。
5.1 转发到本机另一端口的命名服务器
将example.org.内的所有请求代理到运行在 9005 端口的本地命名服务器:
example.org { forward . 127.0.0.1:9005 }5.2 多域名分区转发 + 缓存
lab.example.local.的请求转给 10.20.0.1;example.local.(不含lab.example.local.)转给 10.0.0.1;其余请求转给/etc/resolv.conf中定义的服务器,并缓存结果:
. { cache forward lab.example.local 10.20.0.1 forward example.local 10.0.0.1 forward . /etc/resolv.conf }关键约束:在同一个 server block 中配置多个 forward 插件时,它们按书写顺序依次评估请求。子域必须放在父域之前,否则子域请求会被父域的上游接管。本例中lab.example.local在example.local之前、example.local在.之前,正是遵循这一顺序。
从源码看,同一 server block 的多个 forward 会被构造成一条链:中间的 forward 的Next指向下一个 forward,最后一个才指向链中后续插件(setup.go)。
5.3 等价的分区写法(独立插件链)
上例几乎等价于下面这种三个独立 server block 的写法,差别仅在后者是三条独立插件链、存在 3 个独立的 cache 实例:
lab.example.local { cache forward . 10.20.0.1 } example.local { cache forward . 10.0.0.1 } . { cache forward . /etc/resolv.conf }5.4 三个解析器负载均衡(含 IPv6)
在三个解析器之间负载均衡,其中一个为 IPv6 地址:
. { forward . 10.0.0.10:53 10.0.0.11:1053 [2003::1]:53 }5.5 排除指定域名
转发除example.org之外的所有请求:
. { forward . 10.0.0.10:1234 { except example.org } }或者使用宿主 resolv.conf 的 nameserver 并排除example.org:
. { forward . /etc/resolv.conf { except example.org } }5.6 DNS-over-TLS(DoT)转发
转发所有请求到 9.9.9.9,使用 DoT 协议,并缓存答案最长 30 秒:
. { forward . tls://9.9.9.9 { tls_servername dns.quad9.net health_check 5s } cache 30 }注意tls_servername是必需的——9.9.9.9 无法在 TLS 协商中直接使用 IP 地址。将健康检查间隔调至 5s 是为了避免对上游服务造成过重的检查压力。
5.7 自定义健康检查域名
将健康检查域名改为example.org(默认是.):
. { forward . tls://9.9.9.9 { tls_servername dns.quad9.net health_check 5s domain example.org } cache 30 }5.8 同一提供商的多 DoT 上游
Cloudflare 双节点(1.1.1.1 与 1.0.0.1)共用同一个tls_servername:
. { forward . tls://1.1.1.1 tls://1.0.0.1 { tls_servername cloudflare-dns.com health_check 5s } cache 30 }5.9 不同 tls_servername 的 DoT 上游拆分
当多个 DoT 上游需要不同的tls_servername时,由于单个 forward 块的 TLS 配置是全局的,无法直接混配。可以用本地端口拆分的方案:外层 forward 把请求转发到本机两个端口,各端口再分别对接不同提供商:
. { forward . 127.0.0.1:5301 127.0.0.1:5302 } .:5301 { forward . 8.8.8.8 8.8.4.4 { tls_servername dns.google } } .:5302 { forward . 1.1.1.1 1.0.0.1 { tls_servername cloudflare-dns.com } }六、在 Cloudpods region-dns 中的落地
Cloudpods 的 DNS 服务入口在 cmd/region-dns/main.go,它编译进了一组精选的 CoreDNS 插件(acl、cache、forward、hosts、loadbalance、rewrite 等),并把 Cloudpods 自研的yunion插件插入到hosts之前、forward之前(directives 顺序)。因此 region-dns 的请求处理链大致为:
... cache → rewrite → acl → yunion(云平台域名/内网记录) → hosts → file → forward(转发到外网解析器) → whoami其中yunion插件的实现位于 pkg/dns/setup.go,负责解析云虚拟机 PlainName、CloudZoneFQDN、K8s Service 域名、数据库 DNS 记录等;未命中这些内置域的查询最终落到 forward 插件,转发到配置的外部上游。这意味着在 Cloudpods 环境里,forward 的 Corefile 配置主要承担"外网 DNS 出口"的角色,配合except可灵活控制哪些外部域名走转发、哪些交回插件链处理。
七、常见问题与最佳实践小结
- TLS 转发务必设置
tls_servername:否则无法完成对端证书校验,且存在被中间人攻击的风险(参考 §3.6)。 - 多 forward 的顺序是"子域优先":同一 server block 内必须把更具体的子域放在前面(参考 §5.2)。
max_fails: 0是双刃剑:它关闭了健康检查与宕机判定,适合上游完全由自身负载均衡兜底的场景,但失去故障转移感知。max_concurrent的取值:下限要覆盖查询速率 × 延迟,上限要考虑每个并发查询约 2KB 内存。- 全上游宕机时的随机喷洒是设计上的兜底,不要依赖它做高可用;监控
coredns_forward_healthcheck_broken_total可以及时发现该异常状态。 - 连接缓存与
expire:默认 10s 的缓存过期配合非阻塞归还(25ms 超时),在高 QPS 场景能显著降低新建连接的拨号开销。
参考文档与源码
- forward 插件官方 README:本文的语法与参数依据;
- forward.go:转发主流程、故障转移、并发限制与元数据发布;
- setup.go:Corefile 解析与参数校验、15 个上游上限;
- health.go:带内健康检查实现;
- policy.go:random / round_robin / sequential 三种策略;
- persistent.go:连接缓存桶、过期清理与超时常量;
- connect.go:拨号超时自适应与请求/响应收发;
- metrics.go:Prometheus 指标定义;
- cmd/region-dns/main.go:forward 在 Cloudpods region-dns 中的注册与插件顺序。
- 后端
- 云原生
- 运维
- 虚拟化
- 人工智能
- AI 应用
【免费下载链接】cloudpods
开源、云原生的多云管理及混合云融合平台
相关推荐
CoreDNS forward 插件完全指南:从基础转发到 DoT/DoH/DoQ 与带内健康检查
CoreDNS forward 插件完全指南:从基础转发到 DoT/DoH/DoQ 与带内健康检查 本指南以 CoreDNS 仓库中的 plugin/forwa
后端网络云原生CoreDNS 1.6.8 版本解读:forward 插件并发控制与健康检查增强
CoreDNS 1.6.8 版本解读:forward 插件并发控制与健康检查增强 本文基于 CoreDNS 官方发布说明 notes/coredns 1.6.8
后端网络云原生CloudPods 内置 CoreDNS loop 插件:转发环路检测原理与故障排查实战指南
CloudPods 内置 CoreDNS loop 插件:转发环路检测原理与故障排查实战指南 导读 本文聚焦 CloudPods 仓库中随 CoreDNS v1
后端云原生运维虚拟化人工智能AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考