首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一次外网解析打出多次查询:TKE 上 ndots 与本地 DNS 缓存

一次外网解析打出多次查询:TKE 上 ndots 与本地 DNS 缓存

原创
作者头像
一只特立独行的兔先生
发布于 2026-11-12 18:22:03
发布于 2026-11-12 18:22:03
880
举报

业务调 api.example.com 变慢,应用 QPS 往往没同比例涨,CoreDNS 的查询量和 CPU 先满。Pod 的 /etc/resolv.conf 多半是对的:nameserver 指向 kube-dns,搜索域也在。慢的不是名字写错,是一次外部解析被拆成多次查询,NXDOMAIN 先打满 CoreDNS 和上游 VPCDNS。节点 resolv.conf 被 DHCP 盖掉,是另一类问题。

点号只决定先查谁

dnsPolicy 不写时 Kubernetes 用 ClusterFirst,不是 Default。kubelet 写入三段搜索域和 options ndots:5,nameserver 是 kube-dns 的 ClusterIP。文档示例写成 10.32.0.10,不要抄到线上。Default 继承节点 DNS,集群短名会失效,不能拿它躲开搜索域。

glibc 的 ndots 默认是 1,有点就先做绝对查询。设成 5 之后,点号少于 5 会把绝对查询延后。api.example.com 只有 2 个点,在 shop 里会先问 api.example.com.shop.svc.cluster.local、api.example.com.svc.cluster.local、api.example.com.cluster.local。这三条通常是 NXDOMAIN,最后才问 api.example.com。

resolv.conf 手册容易读成「点号不少于 ndots 就不再拼搜索域」。现行 glibc 的 __res_context_search 不是这样:点号达到 ndots,只是先发绝对查询;没有成功应答、又没有尾点,搜索域仍会继续。尾点才是硬边界,成败都只查绝对名。NXDOMAIN(HOST_NOT_FOUND)会换下一个搜索域。超时不一定把列表走完,次数以抓包为准,不要把「每次固定 4 个查询」当告警等式。

短名靠先搜索。redis 没有点,redis.otherns 只有 1 个点。ndots 改成 1,这两种写法会先被当成公网绝对名。

新域名让缓存和加副本都失效

getaddrinfo 一次名字尝试可以同时发 A 和 AAAA,glibc 2.9 起默认并行,报文数在名字次数上再乘类型。4 个名字都走完且两种类型都发,上限是 8,不是实测。

CoreDNS 的 cache 挡得住重复名字,挡不住每次都变的域名。爬虫、用户 URL、按租户拼的主机名,搜索域上的 NXDOMAIN 也进不了热缓存,会继续打到上游。TKE 文档写明节点和 CoreDNS 默认用 VPCDNS 做上游,大量不同外部域名时要注意它的 QPS 限制,配额以文档和控制台为准。

TKE 默认 CoreDNS 是 2 副本。装了 NodeLocal DNSCache 之后,文档给的副本上限是 10,估算式是 min(max(ceil(QPS/10000), ceil(节点数/8)), 10)。这个 QPS 是 CoreDNS 收到的查询,不是业务请求。用业务 QPS 代进去,副本数会偏小。

次数按 glibc 算。dig 默认不走搜索列表;Go、musl、Windows 的解析器不要套这组数字。

成功的外部名停在第一问

稳定外部依赖写成 api.example.com.,并确认客户端不剥尾点。有的 HTTP 库会自己去掉,配置改了抓包仍是多段搜索名。集群内继续用短名或完整集群域名。

尾点推不动时,把该工作负载的 ndots 降到 2,搜索域保留,dnsPolicy 仍用 ClusterFirst。api.example.com 会先查绝对名,查到记录就结束。这只覆盖外部名确实存在的调用。不存在、又没有尾点时,NXDOMAIN 之后搜索域仍会走完。失败名要停搜索,还是尾点,或把失败和成功分成两份短缓存,避免一次上游故障被冻成永久不存在。

redis 和 redis.otherns 在 ndots 为 2 时仍先搜索。options 和默认项合并,以 Pod 里最终的 resolv.conf 为准。没有本地缓存时不要把 timeout 改成 1,那是本地缓存做第一跳时的注入值。在现有工作负载上加上这一段即可:

代码语言:yaml
复制
dnsPolicy: ClusterFirst
dnsConfig:
  options:
    - name: ndots
      value: '2'

本地缓存接重复,注入仍是 ndots:3

重复名字用节点本地缓存接。TKE 的 NodeLocal DNSCache 以 DaemonSet 跑在节点上,地址是 169.254.20.10,未命中再问 kube-dns。它只是缓存代理,不能加插件。

iptables 模式的存量 Pod 和新建 Pod 都会无感转到本机缓存,resolv.conf 仍是 kube-dns 的 ClusterIP,不能据此判断没生效。这种模式下 Pod 自己的 ndots 还在,降到 2 可以和缓存同时成立。

IPVS 没有透明转发。tke-eni-ip-webhook 给新建 Pod 注入 DNSConfig,并把 dnsPolicy 改成 None:第一台 169.254.20.10,第二台是 kube-dns 的 ClusterIP(文档示例 10.23.1.234,换成自己的),ndots 为 3,attempts 为 2,timeout 为 1。api.example.com 只有 2 个点,小于 3,注入后仍搜索优先。缓存接得住重复名,接不住第一次出现的新域名。

glibc 只有超时或 SERVFAIL 才试下一台。缓存回了 NXDOMAIN,不会落到后面的 kube-dns。第一台无响应时 attempts 是 2,不要只按 1 秒估。

组件成功不等于 Pod 在用缓存。注入默认关:Pod 标签 localdns-injector 高于 Namespace 标签,高于 webhook 参数 localdns(默认 false)。非 hostNetwork 要 ClusterFirst,hostNetwork 要 ClusterFirstWithHostNet,否则不注入。kube-system、kube-public 不注入,存量 Pod 要重建。

代码语言:bash
复制
kubectl label namespace shop localdns-injector=enabled
kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}'
echo

超级节点、tke-direct-eni、Cilium Overlay 用不了 169.254.20.10;GlobalRouter 默认没有这个 webhook。新版本对超级节点会忽略该地址,旧版本可能直接解析失败。不要用集群级 localdns=true 一刀切。

IPVS 上既要缓存又要把 ndots 降到 2,就手写 DNSConfig,并在 Pod 模板上打 localdns-injector=disabled。标签打在 Deployment 的 metadata 上不会进 Pod。dnsPolicy: None 不合并默认搜索域,三段要写全,否则短名会断。文档有一处把字段写成 servers,Pod 的字段是 nameservers,写错会被 API 拒绝。10.0.0.10 换成上一条命令的 ClusterIP。

代码语言:yaml
复制
apiVersion: v1
kind: Pod
metadata:
  name: checkout
  namespace: shop
  labels:
    localdns-injector: disabled
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 169.254.20.10
      - 10.0.0.10
    searches:
      - shop.svc.cluster.local
      - svc.cluster.local
      - cluster.local
    options:
      - name: ndots
        value: '2'
      - name: timeout
        value: '1'
      - name: attempts
        value: '2'
  containers:
    - name: checkout
      image: busybox:1.36
      command: ['sleep', '3600']

跑起来后第一行应是 169.254.20.10,ndots 为 2。卸载前按回源查询量扩 CoreDNS,并确认 nameserver 里没有留下 169.254.20.10。

用业务进程对一次抓包

启动参数里没有 proxy-mode,就看 kube-proxy 配置文件,不要默认当成 iptables。10.0.0.12 换成 Pod IP。抓包在节点上做,解析在 Pod 里用 glibc 发,不要用 dig 代替。

代码语言:bash
复制
NS=shop
POD=ndots-check
kubectl -n kube-system get ds kube-proxy -o yaml | grep proxy-mode
kubectl -n $NS exec $POD -- cat /etc/resolv.conf
代码语言:bash
复制
tcpdump -n -i any -c 40 'udp port 53 and host 10.0.0.12'
代码语言:bash
复制
python3 - <<'PY'
import socket
print(socket.getaddrinfo('api.example.com', 443))
PY

先出现三条带 svc.cluster.local 的查询,再出现 api.example.com,放大还在。只有绝对名,说明成功路径已停在第一问。目的地址全是 kube-dns 的 ClusterIP、没有 169.254.20.10,本地缓存没接上,先查模式和注入,不要先扩副本。IPVS 注入成功时 ndots 应是 3,手写成功是 2。

上线前对一下

  • 外部名的点号、ndots、尾点对过。成功路径和 NXDOMAIN 路径分开看。
  • svc、svc.ns 在新 ndots 下仍先搜索,抽一条内部调用看过。
  • IPVS 上注入开关、dnsPolicy、已重建 Pod 一致;手写 ndots 为 2 的标签在 Pod 模板上。
  • 超级节点、独占网卡、Cilium Overlay、GlobalRouter 没有被集群级开关盖住。卸载前 CoreDNS 按回源查询量重算过副本。

CoreDNS 的 autopath 能在服务端走完搜索域,但 Pod IP 刚被另一个命名空间复用时可能用错搜索域;最终仍是 NXDOMAIN 时,客户端还是会自己搜完。NodeLocal 也不能加插件,它不是默认手段。双栈集群不要对 AAAA 回 NOERROR;非双栈且确认不要 IPv6 时,TKE 才建议这么做,这只减少类型。

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 点号只决定先查谁
  • 新域名让缓存和加副本都失效
  • 成功的外部名停在第一问
  • 本地缓存接重复,注入仍是 ndots:3
  • 用业务进程对一次抓包
  • 上线前对一下
  • 参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档