业务调 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,那是本地缓存做第一跳时的注入值。在现有工作负载上加上这一段即可:
dnsPolicy: ClusterFirst
dnsConfig:
options:
- name: ndots
value: '2'重复名字用节点本地缓存接。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 要重建。
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。
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 代替。
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.conftcpdump -n -i any -c 40 'udp port 53 and host 10.0.0.12'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。
svc、svc.ns 在新 ndots 下仍先搜索,抽一条内部调用看过。CoreDNS 的 autopath 能在服务端走完搜索域,但 Pod IP 刚被另一个命名空间复用时可能用错搜索域;最终仍是 NXDOMAIN 时,客户端还是会自己搜完。NodeLocal 也不能加插件,它不是默认手段。双栈集群不要对 AAAA 回 NOERROR;非双栈且确认不要 IPv6 时,TKE 才建议这么做,这只减少类型。
__res_context_search:https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=resolv/res_query.c;hb=HEAD原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。