Skip to content

DNS 应用场景

熟悉 kubernetes 的人都清楚,集群中存在一种这样的内置资源--statefulset,使用 yaml 创建statefulset 实例时,不知你有没有注意过如下的配置

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    ....
  serviceName: "nginx"

这里的 serviceName 即是 kubernetes service 类型的一种--headless service,仅创建 service 对象本身,但不为此 service 分配 clusterIP(CLUSTER-IP 为 None)

[root@master01 ~]# kubectl  get svc -n kube-system kubelet
NAME      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                        AGE
kubelet   ClusterIP   None         <none>        10250/TCP,10255/TCP,4194/TCP   32d

无论 Pod IP 是否变化,我们也能通过特定的 DNS 名称访问到对应的后端 rs,这是因为 kubernetes 集群按照特定的规则已经在集群中注册了对应的域名解析

pod_name.svc_name.namespace.svc.cluste.local

这里顺便再提一点 headless service 可能混淆的地方

  • ClusterIP 模式有 VIP
    • 以 service name 名称构建的域名解析后的 IP 即是 service VIP
  • ClusterIP 模式无 VIP
    • 以 service name 名称构建的域名解析后的 IP 是该 service 关联的所有 Pod 集合的 IP 列表,此时 kube-proxy 不提供负载均衡能力

无论在宿主机也好,容器内也罢,当我们想访问域名对应的服务时,应当遵循如下流程:

  • 通过 DNS server 找出域名对应的 IP
  • 与解析出来的 IP 进行三次握手后,再进行资源获取

那就顺便复习下 DNS 解析流程吧:

  • client 端查询缓存,如果本地缓存中存在,则直接返回 IP
  • 否则查询本地 hosts 文件中是否配置了域名的静态解析,如果仍未找出 IP 则继续下一步
  • 将 DNS 解析请求转向本地配置的 DNS 服务器(linux 中通过 /etc/resolv.conf 文件配置)
  • 本地 DNS 服务器若未找出 IP,则继续往根域查询
  • 一层层的查询,最终将 IP 返回给客户端

DNS 解析分为递归解析和迭代解析

  • 递归解析
    • 本地 DNS server 向根域服务器查询,根域返回域名所属顶级域服务器的地址
    • 本地 DNS server 再向 顶级域发起查询,顶级域名返回一级域名的地址
    • 本地 DNS server 再向一级域名服务器发起请求,依次类推,直到查询到最终的 IP
  • 迭代解析
    • 和递归解析差不多,只不过每次向根域、顶级域、一级域服务器发起请求的 客户端从本地 DNS server 变成 client 本身

Pod 的 DNS 策略

spec.containers.dnsPolicy 共有 4 种类型:

  • Default
    • 从 Pod 所在的 node 继承 DNS 服务器配置
  • ClusterFirst
    • Pod 内 DNS 服务器指定为 core-dns 地址
  • ClusterFirstWithHostNet
    • 和 hostNetwork 方式运行的 Pod 结合使用,如果以 hostNetwork 方式运行的 Pod,dnsPolicy 指定成 ClusterFirst,那么该 Pod 的 dnsPolicy 则会变成 Default 方式
  • None
    • Pod 忽略 kubernetes 环境中的 DNS 配置,Pod 则使用 dnsConfig 字段中提供的设置

如果 Pod.yaml 未指定 dnsPolicy,则会缺省配置为 ClusterFirst,不是 Default!

CoreDNS

当前作为 kubernetes 集群默认的 DNS 服务器,主要负责 kubernetes 集群内部域名解析

从如下的命令可以看出,CoreDNS 在集群中以2副本形式的 Pod 运行,并且拥有对应的 VIP 地址 10.96.0.10

[root@master01 ~]# kubectl  get pod -n kube-system -o wide |grep coredns
coredns-65dcc469f7-4tqxd                   1/1     Running   14 (7h30m ago)   173d   192.168.59.193   master02   <none>           <none>
coredns-65dcc469f7-jzd2t                   1/1     Running   14 (7h30m ago)   173d   192.168.59.254   master02   <none>           <none>
[root@master01 ~]#
[root@master01 ~]# kubectl  get service -n kube-system |grep dns
kube-dns       ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP,9153/TCP         173d
[root@master01 ~]#
[root@master01 ~]# kubectl  get ep -n kube-system kube-dns
NAME       ENDPOINTS                                                           AGE
kube-dns   192.168.59.193:53,192.168.59.254:53,192.168.59.193:53 + 3 more...   173d

问题一:先聊聊 CoreDNS 本身的配置文件

[root@master01 ~]# kubectl  get cm -n kube-system coredns -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 { # 代表任何服务均可通过 53 端口访问
        log # 记录日志的插件,coredns 默认记录域名解析过程中的日志,使用 log plugin 即可开启
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa { # 这里用于区分被解析域名是外部域名还是内部域名,比如以 cluster.local 结尾的域名就是内部域名,不会再执行到下面的 forward 配置
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf { # 转给 coredns 所在 node /etc/resolv.conf 上配置的 DNS 服务器解析
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2023-10-12T15:20:22Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "685077"
  uid: ea0fb1ff-4ccb-4747-bbb9-f838bb38b5da

问题二:Pod 内 /etc/resolv.conf 配置

nginx-test-69c8bd774c-4vb5q:~# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local # 和 ndots 有关,进行域名补全;比如被解析的域名是 aaa,那么则会依次将域名补全为 aaa.default.svc.cluster.local、aaa.svc.cluster.local 等
nameserver 10.96.0.10 # DNS 服务器,这里则是 CoreDNS 的 VIP
options ndots:5 # 5为默认值,当被解析的域名点小于5时,则进行域名补全,否则不进行 search domain 的补全

这里可能会对 ndots:5 不理解,我们再通过下面的例子进行详细说明:

  1. 域名中点数 < 5

如果被解析域名是 aaa.bbb,此时域名中只有一个点,则依次按照如下域名列表进行 DNS 解析,直到找出 IP 为止

aaa.bbb.default.svc.cluster.local -> aaa.bbb.svc.cluster.local -> aaa.bbb.cluster.local
  1. 域名中点数 >= 5

如果被解析域名是 aaa.bbb.ccc.ddd.eee.fff,此时域名中带有5个点,则只会对1个域名发起解析流程

aaa.bbb.ccc.ddd.eee.fff

Pod 内域名解析流程

内部域名解析

从 coredns 的配置文件可以看出,以 cluster.local 结尾的域名均是内部域名

我们通过抓包学习下 Pod 内解析内部域名,一共请求了多少次才最终返回正确的 IP

客户端请求:

nginx-test-7c676c8687-n87dr:~# nslookup nginx.svc.cluster.local 192.168.59.193
Server:		192.168.59.193
Address:	192.168.59.193#53

** server can't find nginx.svc.cluster.local: NXDOMAIN

抓包地点1: coredns 容器内

[root@master02 ~]# tcpdump -ni eth0 udp dst port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:16:22.389131 IP 192.168.59.192.55743 > 192.168.59.193.domain: 44433+ A? nginx.svc.cluster.local.default.svc.cluster.local. (67)
23:16:22.390064 IP 192.168.59.192.44490 > 192.168.59.193.domain: 18228+ A? nginx.svc.cluster.local.svc.cluster.local. (59)
23:16:22.390858 IP 192.168.59.192.38502 > 192.168.59.193.domain: 14253+ A? nginx.svc.cluster.local.cluster.local. (55)
23:16:22.391974 IP 192.168.59.192.53675 > 192.168.59.193.domain: 18250+ A? nginx.svc.cluster.local. (41)

抓包地点2: coredns 容器所在宿主机的外部网络网卡

[root@master02 ~]# tcpdump -i ens33 udp dst port 53  -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes

从上面的结果可以看出:

  • 我们对 nginx.svc.cluster.local 发起了域名解析
  • 由于域名点数小于5,所以进行的域名补全,一共向 coredns(192.168.59.193) 请求了 4个域名的 IP 地址
  • 域名后缀是 cluster.local,归类为内部域名,所以在 coredns 无法获取 IP 时,也并未转发至外部 DNS server 查询 IP,即宿主机未抓到任何 DNS 请求的包

外部域名解析

客户端请求:

nginx-test-7c676c8687-n87dr:~# nslookup baidu.com 192.168.59.193
Server:		192.168.59.193
Address:	192.168.59.193#53

Name:	baidu.com
Address: 198.18.0.10

抓包地点1: coredns 容器内

[root@master02 ~]# tcpdump -ni eth0 udp dst port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:21:31.350074 IP 192.168.59.192.39253 > 192.168.59.193.domain: 47988+ A? baidu.com.default.svc.cluster.local. (53)
23:21:31.350495 IP 192.168.59.192.60719 > 192.168.59.193.domain: 12040+ A? baidu.com.svc.cluster.local. (45)
23:21:31.350797 IP 192.168.59.192.48859 > 192.168.59.193.domain: 13476+ A? baidu.com.cluster.local. (41)
23:21:31.351074 IP 192.168.59.192.39780 > 192.168.59.193.domain: 45284+ A? baidu.com. (27)
23:21:31.351197 IP 192.168.59.193.38545 > 8.8.8.8.domain: 64169+ A? baidu.com. (27)
23:21:31.354600 IP 192.168.59.192.37797 > 192.168.59.193.domain: 12629+ AAAA? baidu.com. (27)
23:21:31.354783 IP 192.168.59.193.35047 > 114.114.114.114.domain: 9720+ AAAA? baidu.com. (27)

抓包地点2: coredns 容器所在宿主机的外部网络网卡

[root@master02 ~]# tcpdump -i ens33 udp dst port 53  -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
23:21:31.351244 IP 172.16.1.32.52454 > 8.8.8.8.domain: 64169+ A? baidu.com. (27)
23:21:31.354810 IP 172.16.1.32.35543 > 114.114.114.114.domain: 9720+ AAAA? baidu.com. (27)

解析流程优化

不知道你有没有注意,无论是内部域名解析,还是外部域名解析,均存在请求的域名点数小于5,所以也进行了 search 域的域名补全,额外产生了几次无效的 DNS 解析流程

那我们可能就在想了,有没有什么办法避免此种情况呢?

优化方式1: 使用绝对域名解析

在被解析域名的最后加上点,即可跳过 search domain 的补全,直接对目标域名发起解析流程。按照如上的方式自行抓包验证下

nginx-test-7c676c8687-n87dr:~# nslookup baidu.com. 192.168.59.193
Server:		192.168.59.193
Address:	192.168.59.193#53

Name:	baidu.com
Address: 198.18.0.10

优化方式2: 不同的 Pod 配置不同的 ndots

Pod.yaml 通过 dnsConfig 自定义 ndots 配置

spec:
  containers:
    ...
  dnsConfig:
    options:
      - name: ndots
        value: "1"

通过这样的配置后,创建出的 Pod ndots 就不再是默认值 5 了

总结

如果 dnsPolicy 为 ClusterFirst 模式下,域名点数小于5均会进行 search domain 补全。可根据实际的情况进行解析优化