理解OpenShift(2):网络之 DNS(域名服务)
理解OpenShift(1):网络之 Router 和 Route
理解OpenShift(5):从 Docker Volume 到 OpenShift Persistent Volume
** 本文基于 OpenShift 3.11,Kubernetes 1.11 进行测试 ***
OpenShift 集群中,至少有三个地方需要用到 DNS:
- 一是Pod 中的应用通过域名访问外网的时候,需要DNS来解析外网的域名
- 二是在集群内部(pod 中或者宿主机上)通过服务的域名来访问集群内服务的时候,这也是通常所说的服务发现功能,需要通过服务域名来先发现(获取其IP地址)再使用该服务
- 三是从集群外部通过域名访问部署在OpenShift pod 中的服务的时候,需要DNS来解析服务的外网域名
本文就从这三点出发,解释 OpenShift 是如何实现这三种DNS功能的。
1. OpenShift 中的DNS 相关组件及其配置
1.1 Pod 中的 DNS 配置
在Linux 系统上,当一个应用通过域名连接远端主机时,DNS 解析会通过系统调用来进行,比如 getaddrinfo()。
和任何Linux 操作系统一样,Pod 的 DNS 定义在 resolv.conf 文件中,其示例如下:
sh-4.2$ cat /etc/resolv.conf nameserver 172.22.122.9 search dev.svc.cluster.local svc.cluster.local cluster.local exampleos.com options ndots:5
其中,
- nameserver 字段是 pod 所在的宿主机的主网卡的IP 地址。也就是说 pod 中发起的所有DNS 查询请求都会被转发到运行在宿主机的 53 端口上的DNS服务器上。
- search 字段指定当解析一个非FQDN域名时被附加的搜索域(search domain)列表。其解释如下:
- options ndots:5
默认地,许多DNS 解析器如果发现被解析的域名中有任何的点(.)就把它当做一个 FQDN 来解析;如果域名中没有任何点,就把它当做 PQDN 来处理,并且会加上系统的默认domain name 和最后的点,来组成 FQDN。如果没有指定默认的 domain name (通过 domain 字段)或查询失败,则会将 search 字段的第一个值当做默认domain name,如果解析不成功,则依次往下试,直到有一个成功或者全部失败为止。
这个行为是通过 options ndots 来指定的,其默认值为1,这意味着只要被解析域名中有任何一个点(.),那么它就会被当做 FQDN,而不会附加任何 search domain,直接用来查询。OpenShift 环境中,这个值被设置为 5。这意味着,只要被解析域名中包含不超过五个点,该域名就会被当做PQDN,然后挨个使用 search domain,来组装成 FQDN 来做DNS查询。如果全部不成功过,则会尝试将它直接作为 FQDN 来解析。
1.2 Pod 所在宿主机上的 DNS 配置及服务
1.2.1 resolv.conf 文件
[root@node2 cloud-user]# cat /etc/resolv.conf # nameserver updated by /etc/NetworkManager/dispatcher.d/99-origin-dns.sh # Generated by NetworkManager search cluster.local exampleos.com nameserver 172.22.122.9
在部署环境时,会在每个节点上部署 /etc/NetworkManager/dispatcher.d/99-origin-dns.sh 文件。每当节点上的 NetworkManager 服务启动时,该文件会被运行。它的任务包括:
- 创建 dnsmasq 配置文件 :
- node-dnsmasq.conf (在我的 3.11 版本环境上没有创建该文件,见下文分析)
- origin-dns.conf
- origin-upstream-dns.conf
- 当 NetworkManager 服务启动时启动 dnsmasq 服务
- 设置宿主机的所有默认路由 IP 为 Dnsmasq 的侦听IP
- 修改 /etc/resolv.conf,设置搜索域,以及将宿主机的默认 IP 作为 nameserver
- 创建 /etc/origin/node/resolv.conf
也就是说,宿主机上的 DNS 请求也会转到本机上的 53 端口。
1.2.2 dnsmasq 及其配置
宿主机上的 53 端口上,dnsmasq 服务在route 默认路由的所有IP的53端口上侦听。其中一个负责接受并处理宿主机上所有pod 中以及宿主机上的所有DNS查询服务。
tcp 0 0 10.128.2.1:53 0.0.0.0:* LISTEN 906/dnsmasq
tcp 0 0 172.17.0.1:53 0.0.0.0:* LISTEN 906/dnsmasq
tcp 0 0 172.22.122.9:53 0.0.0.0:* LISTEN 906/dnsmasq
这些 IP 地址和默认路由IP 地址是符合的:
10.128.0.0 0.0.0.0 255.252.0.0 U 0 0 0 tun0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.22.122.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0 172.30.0.0 0.0.0.0 255.255.0.0 U 0 0 0 tun0
dnsmasq 服务的配置目录为 /etc/dnsmasq.d。其中有两个配置文件(具体含义请查阅有关文档):
[root@node2 dnsmasq.d]# cat origin-dns.conf no-resolv domain-needed no-negcache max-cache-ttl=1 enable-dbus dns-forward-max=10000 cache-size=10000 bind-dynamic min-port=1024 except-interface=lo # End of config
文件 origin-upstream-dns.conf 中定义了上游(upstream) DNS 名字服务器:
[root@node2 dnsmasq.d]# cat origin-upstream-dns.conf server=172.22.122.3 server=172.22.122.2 server=172.22.122.4
这些上游服务器的地址是从 DHCP 服务器中获取到的(我的OpenShift 环境搭建在OpenStack虚拟机中。前两个地址是OpenStack neutron 网络的 DNSmasq 地址,最后一个是单独搭建的 bind9 DNS 服务器地址)。
在早期版本中(我的OpenShift版本是 3.11),还有一个配置文件 node-dnsmasq.conf :
server=/in-addr.arpa/127.0.0.1 server=/cluster.local/127.0.0.1
这意味着所有以 cluster.local 和 in-addr.arpa 结尾的域名,都会被转到 127.0.0.1:53 上被解析。而其它的解析请求,会被转到在 origin-upstream-dns.conf 中定义的上游 DNS 服务器。
我的3.11版本环境中并没有生成该文件。从代码 https://github.com/openshift/origin/blob/master/pkg/dns/dnsmasq.go 看,OpenShift 中的 dnsmasq 在启动时会自动添加这两条记录:
而 dnsIP 和 dnsDomain 应该是在 /etc/origin/node/node-config.yaml 中的如下配置:
dnsBindAddress: 127.0.0.1:53 dnsDomain: cluster.local
Dec 3 14:10:57 dnsmasq[29595]: using nameserver 127.0.0.1#53 for domain in-addr.arpa Dec 3 14:10:57 dnsmasq[29595]: using nameserver 127.0.0.1#53 for domain cluster.local
从上面的分析可见,在 node 节点上的 dnsmasq,其实只是一个DNS 查询转发器(转到上游DNS 服务器或者本机上的 SkyDns)和结果缓存器,它本身并不保存域名的原始记录。
1.2.3 SkyDNS 及其配置
关于 SkyDNS:它是一个开源的构建在 etcd 之上的分布式服务宣告(announcement)和发现(discovery)服务。利用它,可以通过 DNS 查询来发现可用的服务。其开源社区的地址是 https://github.com/skynetservices/skydns。社区版本的 SkyDns 将记录保存在 etcd 中,在做查询时从etcd 获取数据并封装成 DNS 结果格式给客户端。
SkyDNS 的 server 部分支持被作为库文件使用,此时可以为其实现其它后端。在OpenShift 中并没有采用默认的 etcd 后端,而是基于 OpenShift API 服务实现了新的后端,其代码在https://github.com/openshift/origin/blob/master/pkg/dns/ 。SkyDns 调用 OpenShift API 服务来获取主机名、IP地址等信息,然后封装成标准 DNS 记录并返回给查询客户端。
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 17182/openshift
Node 节点上的 SkyDN 要么从cache 中直接回答 DNS 查询,要么调用 OpenShift API 服务来获取数据再返回。
1.3 Master 节点上的 DNS 服务
resolv.conf 文件同Node 节点上的:
[root@master1 cloud-user]# cat /etc/resolv.conf # nameserver updated by /etc/NetworkManager/dispatcher.d/99-origin-dns.sh # Generated by NetworkManager search cluster.local haihangyun.cn exampleos.com nameserver 172.22.122.5
dnsmasq 在多个IP 地址的 53 端口上侦听,为本机上的以及本机上Pod 中的DNS查询服务:
udp 0 0 10.128.0.1:53 0.0.0.0:* 866/dnsmasq udp 0 0 172.17.0.1:53 0.0.0.0:* 866/dnsmasq udp 0 0 172.22.122.5:53 0.0.0.0:* 866/dnsmasq
和 Node 节点不同,Master 节点上有两个SkyDns 进程。一个在 127.0.0.1:53 侦听,负责本机上的集群内服务的DNS查询,因为 Master 节点同时承担 node 节点的角色:
udp 0 0 127.0.0.1:53 0.0.0.0:* 11700/openshift
Dec 3 14:50:41 dnsmasq[10607]: using nameserver 127.0.0.1#53 for domain cluster.local Dec 3 14:50:41 dnsmasq[10607]: using nameserver 127.0.0.1#53 for domain in-addr.arpa
另一个是在所有网卡的 8053 端口上侦听,这是因为Master 还具有 master api 角色:
udp 0 0 0.0.0.0:8053 0.0.0.0:* 15096/openshift
对于这个 SkyDns 进程的作用尚不清楚,还需进一步研究。从已有资料上看看,所有节点上都需要安装 SkyDns,并组成一个分布式集群。因为 Master 节点上的 53 端口被另一个 SkyDns 进程占用,因此换到了端口8053。
2. DNS 查询流程
2.1 pod 内的应用通过域名访问外网服务器的DNS查询流程
流程示意图如最上面图中的 1 和 2.1 部分所示。
dnsmasq 日志:
Nov 21 11:03:44 dnsmasq[17788]: using nameserver 172.22.122.3#53 Nov 21 11:03:44 dnsmasq[17788]: using nameserver 172.22.122.2#53 Nov 21 11:03:44 dnsmasq[17788]: using nameserver 172.22.122.4#53 Nov 21 11:03:49 dnsmasq[17788]: query[A] www.sina.com from 172.22.122.13 Nov 21 11:03:49 dnsmasq[17788]: forwarded www.sina.com to 172.22.122.4 Nov 21 11:03:49 dnsmasq[17788]: forwarded www.sina.com to 172.22.122.2 Nov 21 11:03:49 dnsmasq[17788]: forwarded www.sina.com to 172.22.122.3 Nov 21 11:03:49 dnsmasq[17788]: reply spool.grid.sinaedge.com is 124.228.42.248
能看到 node 上的 dnsmasq 直接将查询请求转发给上游 DNS 名字服务器。因为存在多个名字服务器,所以是依次查询,直到成功为止。从日志看,其查询顺序和配置文件中的顺序是相反的。
2.2 Pod 内应用通过服务域名查找其IP 地址
流程示意图如上图中的 1 + 2.2 + 3 部分所示。
日志实例:
(1)从一个 pod 中 ping registry-console服务的域名 registry-console.default.svc.cluster.local。
(2)Node宿主机(IP 地址为 172.22.122.13)上的 dnsmasq 收到该查询。
(3)dnsmasq 将查询转到 127.0.0.1:53 上的 SkyDns 服务。
(4)SkyDNS 做查询。SkyDNS 能接收的域名格式:<prefix>.<service_name>.<namespace>.(svc|endpoints|pod).<base>,这意味着它支持查询服务(svc)、端点(endpoints)和 pod 的 DNS信息。
查询结果:
[root@node2 cloud-user]# nsenter -t 4216 -n dig mybank.dev.svc.cluster.local ;; QUESTION SECTION: ;mybank.dev.svc.cluster.local. IN A ;; ANSWER SECTION: mybank.dev.svc.cluster.local. 30 IN A 172.30.162.172 ;; Query time: 1 msec ;; SERVER: 172.22.122.9#53(172.22.122.9) ;; WHEN: Mon Dec 03 11:43:01 CST 2018 ;; MSG SIZE rcvd: 62
dnsmasq 日志:
Dec 3 14:19:44 dnsmasq[29595]: query[A] mybank.dev.svc.cluster.local from 10.128.2.128 Dec 3 14:19:44 dnsmasq[29595]: forwarded mybank.dev.svc.cluster.local to 127.0.0.1 Dec 3 14:19:44 dnsmasq[29595]: reply mybank.dev.svc.cluster.local is 172.30.162.172
(5)其它实验:查询服务的所有端点
查询结果:
[root@node2 cloud-user]# nsenter -t 4216 -n dig jenkins.dev.endpoints.cluster.local ;; QUESTION SECTION: ;jenkins.dev.endpoints.cluster.local. IN A ;; ANSWER SECTION: jenkins.dev.endpoints.cluster.local. 30 IN A 10.128.2.81 jenkins.dev.endpoints.cluster.local. 30 IN A 10.131.1.70
dnsmasq 日志:
Dec 3 14:20:48 dnsmasq[29595]: query[A] jenkins.dev.endpoints.cluster.local from 10.128.2.128 Dec 3 14:20:48 dnsmasq[29595]: forwarded jenkins.dev.endpoints.cluster.local to 127.0.0.1 Dec 3 14:20:48 dnsmasq[29595]: reply jenkins.dev.endpoints.cluster.local is 10.128.2.81 Dec 3 14:20:48 dnsmasq[29595]: reply jenkins.dev.endpoints.cluster.local is 10.131.1.70
(6)查询 pod
待查询的pod域名的格式为 <IP_with_dashes>.<namespace>.pod.<base>,SkyDns 会返回其IP 地址,但我没明白这么做的场景和价值,也许是确认pod是否存在?
查询结果:
[root@node2 cloud-user]# nsenter -t 4216 -n dig 172-30-162-172.dev.pod.cluster.local ;; QUESTION SECTION: ;172-30-162-172.dev.pod.cluster.local. IN A ;; ANSWER SECTION: 172-30-162-172.dev.pod.cluster.local. 30 IN A 172.30.162.172 ;; Query time: 1 msec ;; SERVER: 172.22.122.9#53(172.22.122.9) ;; WHEN: Mon Dec 03 13:32:05 CST 2018 ;; MSG SIZE rcvd: 70
dnsmasq 日志:
Dec 3 14:22:24 dnsmasq[29595]: query[A] 172-30-162-172.dev.pod.cluster.local from 10.128.2.128 Dec 3 14:22:24 dnsmasq[29595]: forwarded 172-30-162-172.dev.pod.cluster.local to 127.0.0.1 Dec 3 14:22:24 dnsmasq[29595]: reply 172-30-162-172.dev.pod.cluster.local is 172.30.162.172
(7)对比 FQDN 和 PQDN
这个 PQDN 被加上了搜索域名再进行查询,能返回正确的IP地址:
[root@node2 cloud-user]# nsenter -t 4216 -n ping mybank.dev.svc PING mybank.dev.svc.cluster.local (172.30.162.172) 56(84) bytes of data.
而这个 FQDN 被直接做DNS查询,结果查询失败,未能获取IP地址:
[root@node2 cloud-user]# nsenter -t 4216 -n ping mybank.dev.svc. ping: mybank.dev.svc.: Name or service not known
2.3 从外网通过服务域名访问pod 中运行的服务
可以看出,该过程中只涉及到外部DNS将服务的公共域名解析为 OpenShift Router 所在节点的公网地址,后面 HAProxy 作为代理,直接通过 IP 访问pod,并将结果返回客户端。
参考文档:
- https://pracucci.com/kubernetes-dns-resolution-ndots-options-and-why-it-may-affect-application-performances.html
- https://www.redhat.com/en/blog/red-hat-openshift-container-platform-dns-deep-dive-dns-changes-red-hat-openshift-container-platform-36
感谢您的阅读,欢迎关注我的微信公众号: