首页 / 博客 / 本文

DNSSEC 与私有 DNS 实战:签名链、服务发现与内网解析体系设计

🌐 DNS 进阶📅 2026-09-02⏱ 15 分钟

在《DNS 深度解析》里,我们把一次解析的递归、缓存与基本安全讲透了。这一篇往前走两步:一是DNSSEC 到底怎么签、怎么验,密钥怎么管——把「完整性」这件事彻底拆开;二是私有 DNS 在云原生里的新角色——它早就不只是「内网域名解析」,而是服务发现与流量治理的基础设施。

先厘清一个高频混淆。DNSSEC 保证的是「答案没被篡改」(数据完整性 + 源认证),不加密、不保密——查询和应答对旁路者仍是一清二楚的明文。想防窃听/劫持,要叠 DoT/DoH。三者的关系是:DNSSEC 防「假答案」,DoT/DoH 防「看和改」,各管一段,叠加才完整。

一、DNSSEC 的核心:一条从根下传的签名信任链

DNSSEC 用非对称密码学给资源记录签名。它的巧妙之处在于不要求每个权威服务器都可信,只需要信任「根」这一个锚点,然后沿着一级级的委托,把信任「下传」到你的域名。

记录类型作用通俗理解
RRSIG对一组 RRset 的数字签名「这批记录是我签的,签名如下」
DNSKEY该区域的公钥(分 KSK/ZSK)「验证我签名请用这把公钥」
DS子域 KSK 的摘要,存放在父域「我认可子域用的是这把钥匙」
NSEC / NSEC3证明「某记录不存在」防攻击者伪造 NXDOMAIN

验证的链条是这样走通的:解析器信任根的公钥 → 根用 DS 记录背书了 .com 的 KSK → .com 用 DS 背书了 example.com 的 KSK → example.com 的 KSK 签了 ZSK → ZSK 签了具体记录(RRSIG)。每一级的「DS 摘要」就是父域对子域公钥的信任背书,环环相扣,篡改任何一环都会导致验证失败。

KSK / ZSK 分离:为什么要两把钥匙

DNSSEC 里有两把钥匙,各司其职:

把两者分开,是为了让「日常换钥匙」不影响「信任链的锚点」:换 ZSK 是区域内部的事,换 KSK 才是需要父域配合的大事。这背后还有一个更隐蔽的风险点——DNSSEC 的密钥轮转如果做错(旧 KSK 撤销了、新 DS 还没生效),会导致整个域名被判定为验证失败(bogus),等于把域名「打挂」。所以轮转必须严格按「先加新、再过渡、最后撤旧」的顺序。

二、上手验证:dig 怎么读 DNSSEC

# 1. 验证一个已启用 DNSSEC 的域名,看 flags 里是否有 ad(authenticated data)
dig example.com A +dnssec
;; flags: qr rd ra ad;     # ad 位 = 这条链被验证通过

# 2. 看 DS 记录(父域对子域公钥的背书)
dig example.com DS

# 3. 看 DNSSEC 链的起点:根区的信任锚
dig . DNSKEY | head

排障时最有用的是 ad 位:解析器返回的应答 flags 里有 ad,说明它完成了 DNSSEC 验证;若验证失败,会返回 SERVFAIL 而不是一个「看似正常但未验证」的答案(这是 DNSSEC 的 fail-closed 设计——宁可不答,不给假答案)。一个常见坑:本地时间不准。RRSIG 有有效期,解析器本地时钟偏差过大,会把仍在有效期内的签名误判为过期,导致大面积 SERVFAIL——启用 DNSSEC 的递归器,NTP 校时是硬前提。

三、私有 DNS:从「内网解析」到「服务发现」

私有 DNS 的历史用法是「内网域名 + 本地权威」:公网域名向上游转发,内网域名本地应答。但到了云原生时代,私有 DNS 的角色发生了质变——它成了服务发现(Service Discovery)的核心机制。

1. 服务发现为什么离不开 DNS

在微服务/K8s 架构里,服务的 IP 是动态漂移的(Pod 重建、扩缩容、滚动发布),硬编码 IP 必死。于是大家约定:服务名 = 域名,DNS 查询 = 服务寻址。K8s 集群里的 svc.namespace.svc.cluster.local 就是这套逻辑的典型落地:

# K8s 内:Pod 之间用「服务名」互相调用,而不是 IP
curl http://api-svc.default.svc.cluster.local:8080/health
# 这个域名由 CoreDNS 解析,返回 Service 的 ClusterIP(虚拟 IP)
# Service 再通过 kube-proxy/iptables 把流量负载均衡到后端 Pod

这里 DNS 解决了两件事:寻址(把稳定的名字映射到漂移的 IP)和解耦(调用方不关心后端具体是谁、有几个、在哪)。配合 Service 的负载均衡,形成了「名字 → 虚拟 IP → 后端实例」的两层抽象。

2. CoreDNS:可插拔的 DNS 引擎

CoreDNS 之所以取代 Kube-DNS 成为 K8s 默认 DNS,在于它把 DNS 功能拆成了一个个可编排的插件(plugin),通过 Corefile 组合。几个关键插件:

.:53 {
    errors
    health
    kubernetes cluster.local in-addr.arpa ip6.arpa {   # 接 K8s API,动态提供服务发现
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf                          # 其余域名转发给上游
    cache 30
    loop
    reload
    loadbalance
}

这种「中间件式」架构让 CoreDNS 既能做服务发现,又能做内网权威、递归转发、负载均衡、缓存,甚至能对接 etcd(SkyDNS 风格)做自定义服务注册。理解了插件模型,就理解了为什么现代 DNS 方案都长这样。

3. 内网解析体系设计:分层与容错

一个稳健的内网 DNS 体系,通常会设计成多层:

层角色典型实现
递归层面向客户端,缓存 + 转发Unbound / dnsmasq / CoreDNS
权威层内网域名的最终答案BIND9 / 云厂商私有域
服务发现层动态服务名 → 实例CoreDNS(K8s)/ Consul DNS / etcd

设计上几条硬规则:

四、私有 DNS 的排障清单

# 1. 先定位是哪一层的问题
nslookup api-svc.default.svc.cluster.local     # 服务发现层返回了吗?
dig @内网权威 example.internal A               # 权威层对吗?
dig @递归器 example.internal A +trace          # 转发链路走得通吗?

# 2. CoreDNS 常见故障
kubectl logs -n kube-system -l k8s-app=kube-dns   # 看 CoreDNS 日志
# 高频关键词:loop(转发环)、i/o timeout(上游不可达)、NXDOMAIN(服务没注册/名字拼错)

# 3. 服务发现的经典坑:DNS 缓存 TTL 过长
# 实例已下线,但客户端还在拿旧 IP → 检查 TTL 是否过大、是否有多级缓存未失效

服务发现场景里,「改了没生效」最可能是 TTL 与各级缓存(客户端 OS 缓存、应用内 DNS 缓存、递归层缓存)叠加导致的。这也是为什么云原生里普遍倾向短 TTL + 客户端重连兜底(如 gRPC 的 load balancing + 连接探活),而不是单纯依赖 DNS 的更新速度。

总结。这一篇补上了 DNS 的两个进阶拼图:完整性——DNSSEC 用「根锚点 + DS 逐级背书 + RRSIG 签名」的信任链,保证答案没被篡改,但前提是管好 KSK/ZSK 的轮转和本地时钟;基础设施化——私有 DNS 从「内网解析」进化为「服务发现」,CoreDNS 的插件模型与「递归/权威/服务发现」分层设计,是现代内网解析体系的骨架。把「防假答案」和「让名字指向会漂移的服务」两件事都做扎实,DNS 才真正从「能用」走向「可靠」。
#DNS#DNSSEC#服务发现#Kubernetes#CoreDNS
广告