首页
/ Kubernetes 集群内 NodeLocal DNSCache(nodelocaldns)Addon 深度解析:CoreDNS 每节点缓存的设计、清单模板与配置注入

Kubernetes 集群内 NodeLocal DNSCache(nodelocaldns)Addon 深度解析:CoreDNS 每节点缓存的设计、清单模板与配置注入

2026-09-06 18:41:28作者:凤尚柏Louis

本文以 Kubernetes 仓库中的 nodelocaldns 附加组件为对象,系统讲解 NodeLocal DNSCache 的架构思路、nodelocaldns.yaml 清单中每个对象的职责、__PILLAR__ 模板变量的注入机制,以及它在 NetworkPolicy 环境下的连通性配置与负缓存(negative caching)调优要点。读完本文,你将能读懂该插件的完整部署清单,理解它如何把集群 DNS 查询收敛到本机缓存,并掌握直接基于 nodelocaldns.yaml 定制部署的每一个开关与参数。

一、NodeLocal DNSCache 解决什么问题

在未启用 NodeLocal DNSCache 的集群中,Pod 内发起的 DNS 查询经由 kubelet 下发的 --cluster-dns(即 kube-dns 或 CoreDNS 的 ClusterIP)离开节点,再由 kube-proxy 的 iptables/ipvs 规则做 DNAT 转发到 DNS 后端 Pod。这条路径存在两个已知痛点:

  1. conntrack 竞争丢包:在高并发下,来自本机 Pod 的大量 DNS 查询经 DNAT 回路时,可能触发 conntrack 的冲突丢包,表现为间歇性 DNS 解析失败;
  2. 跨节点转发开销与延迟:每次查询都要在宿主机网络栈走一圈转发,且 kube-dns 后端 Pod 的扩缩容、驱逐会直接影响解析可用性。

NodeLocal DNSCache 的解法是:在每个节点上把一份 DNS 缓存放到离 Pod 最近的地方。本仓库的 cluster/addons/dns/nodelocaldns/ 目录正是为此提供的官方 addon 实现。按照仓库 README.md 的说明:它会在集群所有节点上运行一个 node-local-dns Pod,该 Pod 以 CoreDNS 作为 DNS 缓存,以 hostNetwork: true 方式运行,并创建一个承载 链路本地地址(默认 169.254.20.10/32)的专用 dummy 接口来接收 DNS 查询;缓存实例在缓存未命中时才回源连接到集群 DNS(clusterDNS)。

从功能演进看,该特性在 release 1.15 进入 Beta,在 1.18 正式 GA——README 中明确记录了这一过程,本文基于仓库当前版本中的清单文件展开,其中的镜像与配置均以仓库实际内容为准。

二、addon 的构成与整体工作流

目录内只有两个文件,结构非常精简:

文件 作用
cluster/addons/dns/nodelocaldns/nodelocaldns.yaml 描述该 addon 全部资源的 YAML 模板,内含待替换的 __PILLAR__ 占位变量
cluster/addons/dns/nodelocaldns/README.md 使用与设计说明文档

清单被设计为"模板 + 变量替换"的形态:当 YAML 被复制到 master 节点时,配置脚本会完成变量代换(详见本文第五节)。最终落地的资源相互配合形成如下链路:

Pod(应用) --查询--> node-local-dns(:53, 169.254.20.10 本机) 
                        ├── 命中缓存 → 直接返回
                        └── 未命中 → kube-dns-upstream Service → 集群内 CoreDNS/kube-dns

节点上缓存层的存在使得绝大多数查询在本机完成,只有 miss 才回到集群 DNS 后端,这正是该插件名称中 "Node-Local" 的含义。

三、逐对象拆解 nodelocaldns.yaml

清单 nodelocaldns.yaml 共声明 5 个资源(以 --- 分隔),下面逐个解读。

3.1 ServiceAccount:node-local-dns

清单第 16–23 行创建了名为 node-local-dns 的 ServiceAccount,并打上 kubernetes.io/cluster-service: "true"addonmanager.kubernetes.io/mode: Reconcile 标签。其中 Reconcile 模式是 addon-manager 的声明式管理语义——它保证该清单以"调和"而非"仅创建一次"的方式被管理,任何被手工篡改的字段都会被 addon-manager 纠正回模板定义的状态。

3.2 Service:kube-dns-upstream(回源入口)

第 25–47 行定义了一个名为 kube-dns-upstream 的 Service,selector 指向 k8s-app: kube-dns 的 Pod,同时暴露 UDP 53 与 TCP 53 两个端口。它的作用是为缓存实例提供稳定的回源端点:即使集群 DNS 后端滚动更新或 Pod IP 变化,缓存也始终通过该 Service 的 ClusterIP 访问后端,无需感知后端变动。

3.3 ConfigMap:node-local-dns(CoreDNS Corefile)

第 48–102 行是插件的核心——一段内置在 ConfigMap 中的 CoreDNS Corefile 配置,定义了四个服务器块(zone),对应节点缓存需要处理的四类查询:

① 集群域名 zone__PILLAR__DNS__DOMAIN__:53,默认即 cluster.local):

__PILLAR__DNS__DOMAIN__:53 {
    errors
    cache {
            success 9984 30
            denial 9984 5
    }
    reload
    loop
    bind __PILLAR__LOCAL__DNS__ __PILLAR__DNS__SERVER__
    forward . __PILLAR__CLUSTER__DNS__ {
            force_tcp
    }
    prometheus :9253
    health __PILLAR__LOCAL__DNS__:8080
    }

要点解读:

  • cache 对成功应答(success)缓存 9984 条、TTL 30 秒;对否定应答(denial,即 NXDOMAIN 等)缓存 9984 条、TTL 仅 5 秒——这就是 README 强调的"负缓存 TTL 被压到最低 5 秒",细节见第七节;
  • bind 同时绑定在本地链路地址与集群 DNS Service IP 上,保证两类来源的查询都能命中;
  • forward . __PILLAR__CLUSTER__DNS__ 并追加 force_tcp,即集群内查询回源时强制走 TCP(规避大规模 UDP 查询在 conntrack 层面的丢包问题,也正是本项目要缓解的核心故障之一);
  • health 在本地链路地址的 8080 端口暴露健康检查端点,与 DaemonSet 的 livenessProbe 呼应。

② 反向解析 zonein-addr.arpa:53ip6.arpa:53,第 72–93 行):分别承接 IPv4、IPv6 的 PTR 反向查询,同样 cache 30 并强制 TCP 回源到集群 DNS。

③ 兜底外部 zone. :53,第 94–102 行):处理所有其他域名查询,forward 目标是 __PILLAR__UPSTREAM__SERVERS__——即从 kube-dns ConfigMap 推导出的上游服务器集合。

3.4 DaemonSet:node-local-dns(缓存实例本体)

第 104–191 行定义核心 DaemonSet,关键字段如下表:

字段/配置 设计意图
updateStrategy.rollingUpdate.maxUnavailable 10% 节点滚动升级时最多同时不可用 10%,保障升级期间 DNS 基本可用
priorityClassName system-node-critical 系统关键 Pod,优先级高于普通业务,避免被驱逐
hostNetwork true 直接占用宿主机网络,监听链路本地地址
dnsPolicy Default(注释:Don't use cluster DNS) 避免自身依赖集群 DNS 形成回环
tolerations CriticalAddonsOnlyNoExecuteNoSchedule 容忍关键插件污点与节点驱逐,确保在每个节点都常驻
容器镜像 registry.k8s.io/dns/k8s-dns-node-cache:1.26.8 node-cache 镜像(仓库当前版本)
资源 requests cpu 25m、memory 5Mi 极低开销的缓存代理
capabilities NET_ADMIN 用于管理 dummy 接口与 iptables 规则
暴露端口 53/UDP、53/TCP、9253/TCP DNS 服务与 Prometheus 指标

容器启动参数(第 146 行)揭示了它的三个关键输入:

args: [ "-localip", "__PILLAR__LOCAL__DNS__,__PILLAR__DNS__SERVER__",
        "-conf", "/etc/Corefile",
        "-upstreamsvc", "kube-dns-upstream" ]
  • -localip:同时监听链路本地 IP 与集群 DNS IP,二者都作为"本地监听面";
  • -conf:指向挂在的 Corefile;
  • -upstreamsvc:声明使用前面定义的 kube-dns-upstream Service 作为回源服务。

健康检查(第 161–167 行)通过 HTTP GET 访问 http://__PILLAR__LOCAL__DNS__:8080/healthinitialDelaySeconds: 60 给 CoreDNS 留足启动时间。挂载方面有三处:/run/xtables.lock(hostPath 类型 FileOrCreate,供并发修改 iptables 时互斥)、/etc/coredns(config-volume,取自 node-local-dns ConfigMap,将 Corefile 键映射为 Corefile.base)、/etc/kube-dns(挂载集群内 kube-dns ConfigMap,optional: true,用于推导自定义上游配置)。

3.5 Headless Service:node-local-dns(指标暴露)

第 192–211 行的 node-local-dns Service 是一个 clusterIP: None 的无头 Service,selector 匹配 k8s-app: node-local-dns。YAML 注释写得很直白:"a headless service ... instead of load-balancing it will return the IPs of our associated Pods",并带有 prometheus.io/port: "9253"prometheus.io/scrape: "true" 注解——它专门把每个节点上缓存实例的 9253 指标端口暴露给 Prometheus 抓取,同时避免负载均衡语义。

四、为什么必须"每节点一个缓存实例"与端口占用

NodeLocal DNSCache 的价值建立在本机性上:只有当 Pod 的查询在本机回环链路内被终结,才能彻底绕开宿主机 iptables DNAT 与 conntrack 竞争。因此 DaemonSet 是唯一正确的部署形态——"在集群所有节点上运行"。同时要注意 hostNetwork: true 意味着该 Pod 与宿主机共享网络命名空间,因此 53 端口在各节点上是被缓存实例独占的,这正是设计上把缓存监听地址放在 169.254.20.10 这类链路本地地址上的原因之一,避免与节点自身可能存在的其他 DNS 服务冲突。

从使用机制上推断,要让 Pod 的查询真正落到本机缓存,通常需要把节点的 kubelet DNS 配置指向该链路本地地址(即把 --cluster-dns 设为 169.254.20.10);本 addon 是整体部署流程(如 configure-helper.sh 所驱动的 kube-up 场景)中的一个组成部分,其模板变量亦由该脚本注入。

五、PILLAR 变量体系:模板如何被注入真实值

README 明确列出了 YAML 中的变量及其取值来源,汇总如下:

模板变量 含义 注入方
__PILLAR__DNS__SERVER__ kube-dns Service 的 ClusterIP 部署脚本(sed 代换)
__PILLAR__LOCAL__DNS__ 链路本地监听地址(默认 169.254.20.10) 部署脚本(sed 代换)
__PILLAR__DNS__DOMAIN__ 集群域名(默认 cluster.local) 部署脚本(sed 代换)
__PILLAR__CLUSTER__DNS__ 集群内查询的回源服务器 node-cache 镜像(≥1.15.6)启动时读取 kube-dns ConfigMap 推导
__PILLAR__UPSTREAM__SERVERS__ 外部查询的上游服务器 同上,由自定义上游配置推导

前三者由集群配置脚本在部署期代换。仓库中 cluster/gce/gci/configure-helper.shsetup-nodelocaldns-manifest 函数给出了最直接的实现证据:

function setup-nodelocaldns-manifest {
  setup-addon-manifests "addons" "0-dns/nodelocaldns"
  local -r localdns_file="${dst_dir}/0-dns/nodelocaldns/nodelocaldns.yaml"
  setup-addon-custom-yaml "addons" "0-dns/nodelocaldns" "nodelocaldns.yaml" "${CUSTOM_NODELOCAL_DNS_YAML:-}"
  # eventually all the __PILLAR__ stuff will be gone, but theyre still in nodelocaldns for backward compat.
  sed -i -e "s/__PILLAR__DNS__DOMAIN__/${DNS_DOMAIN}/g" "${localdns_file}"
  sed -i -e "s/__PILLAR__DNS__SERVER__/${DNS_SERVER_IP}/g" "${localdns_file}"
  sed -i -e "s/__PILLAR__LOCAL__DNS__/${LOCAL_DNS_IP}/g" "${localdns_file}"
}

可以看到:

  • 文件先被复制到 /etc/kubernetes/addons/0-dns/nodelocaldns/nodelocaldns.yaml,目录名冠以 0- 以保证 DNS 相关资源最先被 addon-manager 创建,防止其他插件抢占 DNS ClusterIP(见同脚本 start-kube-addons 中的注释);
  • 如果设置了环境变量 CUSTOM_NODELOCAL_DNS_YAML,则用用户自定义清单整体替换模板;
  • 三个 sed 分别完成域名、DNS Service IP、链路本地地址的代换;
  • 该函数仅在 ENABLE_CLUSTER_DNS=trueENABLE_NODELOCAL_DNS=true 时被调用(第 2859–2861 行),即 NodeLocal DNSCache 是集群 DNS 之上的可选增强层。

对应的默认值集中在集群配置文件中,例如 cluster/gce/config-default.sh

export ENABLE_NODELOCAL_DNS="${KUBE_ENABLE_NODELOCAL_DNS:-false}"
export LOCAL_DNS_IP="${KUBE_LOCAL_DNS_IP:-169.254.20.10}"

以及 cluster/gce/config-default.sh 中的 DNS_SERVER_IP="${KUBE_DNS_SERVER_IP:-10.0.0.10}"(测试集群配置在 config-test.sh 中有同款默认值)。也就是说:GCE 类集群默认不启用该插件,需显式开启 ENABLE_NODELOCAL_DNS;链路本地地址默认取 169.254.20.10

后两个变量(__PILLAR__CLUSTER__DNS____PILLAR__UPSTREAM__SERVERS__)则交给容器镜像侧处理:README 说明它们由 registry.k8s.io/k8s-dns-node-cache:1.15.6 或更新版本的镜像在启动时读取 kube-dns ConfigMap(对应 DaemonSet 中挂载 /etc/kube-dnsoptional: true 卷)来推导自定义上游服务器配置,从而使模板无需为每一类自定义上游而重新代换。换句话说,模板变量被刻意分成了"部署期由脚本填写的静态地址"与"运行期由镜像推导的动态上游"两组,各司其职。

六、链路本地地址的选择建议

README 对监听地址给出了明确约束与建议,值得原样保留并强调:

NodeLocal DNSCache 的本地监听 IP 可以是任何能保证不与集群既有 IP 冲突的地址。官方推荐使用本地作用域地址,例如 IPv4 的链路本地段 169.254.0.0/16,或 IPv6 的 Unique Local Address 段 fd00::/8

这条建议的工程含义是:169.254.0.0/16 是 RFC 3927 保留的链路本地段,常规集群内不会为业务 Pod/Service 分配该段地址,因此用其中任意地址(仓库默认取 169.254.20.10)承载缓存监听几乎零冲突风险。若你的集群确有地址规划上的特例(例如网络插件已占用该段),则应像上文 LOCAL_DNS_IP 变量一样显式改用一个可保证不冲突的地址,并让 kubelet 侧 DNS 配置指向同一地址。

七、NetworkPolicy 环境下的 DNS 连通性

这是一个容易踩坑的点,README 单独用一节说明:

由于 node-local-dns Pod 运行在 hostNetwork: true 下,基于 namespace selector 的 DNS 出口(egress)放行规则可能不够用——hostNetwork Pod 的流量不从普通 Pod 网络命名空间发出,Calico 文档中常见的"按命名空间选择器放行 DNS"的做法覆盖不到这类查询。

推荐的替代方案是针对回源目标 ClusterIP 的 ipBlock 规则,README 给出的可复制示例为:

spec:
  egress:
  - ports:
    - port: 53
      protocol: TCP
    - port: 53
      protocol: UDP
    to:
    - ipBlock:
        cidr: <well-known clusterIP for DNS>/32
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

即:对任意来源 Pod(podSelector: {})放行前往"集群 DNS 的知名 ClusterIP/32"的 53 端口 TCP/UDP 出口流量,同时显式声明 policyTypes 包含 Ingress 与 Egress。注意 NetworkPolicy 的 egress 是白名单语义,若策略只允许该 CIDR,还需保证缓存实例本地应答(链路本地地址、无跨节点转发)不受出口策略干扰——这正是把监听地址收敛到本机的好处。

八、Negative caching(否定应答缓存)的 5 秒权衡

README 记录了仓库对缓存配置的一次刻意收敛:denial(否定应答)缓存 TTL 被压缩到最低 5 秒。对应证据就在 nodelocaldns.yaml 的集群域名 zone 中:

cache {
        success 9984 30
        denial 9984 5
}

设计动机与权衡如下:

  • 为什么压到 5 秒:若否定应答(NXDOMAIN 等)被长期缓存,依赖 DNS 轮询做编排协调的业务会持续拿到过期的"不存在"结果,导致新创建的 Service、StatefulSet 域名迟迟无法被发现。缩短 denial TTL 能显著降低这类编排失败窗口。
  • 代价与调优方向:极端情况下(如针对不存在域名的恶意/高频查询)5 秒 TTL 可能放大回源压力。若确认性能受影响,可调高该 TTL 缓解,但 README 明确警告:依赖 DNS 轮询进行编排的操作可能因此失败,例如针对 StatefulSet 的 Operator。因此该值属于"默认保守、按业务场景谨慎上调"的敏感参数,不建议轻易改动。

对照反向解析 zone 中 cache 30 的配置可以看出:正向集群域名的否定应答使用了更短的独立 TTL,而反向 zone 未区分成功/否定应答,统一为 30 秒。

九、落地部署与验证清单

结合上文分析,在实际集群启用该插件的完整思路可归纳为:

  1. 开启开关:在集群引导配置中设置 ENABLE_NODELOCAL_DNS=true(GCE/kube-up 路径由 configure-helper.sh 消费;其他部署方式需将模板中的 __PILLAR__ 变量替换为实际值后交给集群 DNS 管理流程);
  2. 核对默认值LOCAL_DNS_IP=169.254.20.10DNS_SERVER_IP=10.0.0.10(kube-dns Service IP)、DNS_DOMAIN=cluster.local,如与实际不符,通过 KUBE_LOCAL_DNS_IP 等环境变量或自定义清单 CUSTOM_NODELOCAL_DNS_YAML 覆盖;
  3. 确认 kubelet DNS 指向链路本地地址,使节点上的 Pod 查询流入本机缓存面;
  4. 验证资源就绪node-local-dns DaemonSet 在每个节点达到预期副本数,kube-dns-upstream Service 正常选中后端;
  5. 功能自检:在任意节点用 dig @169.254.20.10 <service>.<namespace>.svc.<domain> 验证集群域名解析,用 dig @169.254.20.10 <外部域名> 验证外部查询回源链路,并观察 kube-dns 后端请求量是否显著下降;
  6. 指标巡检:通过无头 Service 暴露的 9253 端口抓取 CoreDNS 指标,检查缓存命中率与错误率;
  7. 策略核对:若集群启用 NetworkPolicy,按第七节示例为 DNS 出口补上针对集群 DNS ClusterIP 的 ipBlock 放行。

综上,NodeLocal DNSCache 是 Kubernetes 把"高可用、低延迟的 DNS"下沉到每个节点的标准实践:从本仓库的 addon 清单可见,它用一段精确的 CoreDNS 配置 + 一个 hostNetwork DaemonSet + 一组模板变量,就完成了缓存面、回源面、指标面与健康面的完整闭环。对集群管理员而言,理解 nodelocaldns.yaml 中每一个对象与 README.md 中的调优告诫,是安全启用与维护该插件的前提。

登录后查看全文
热门项目推荐
相关项目推荐