深入分析 OpenShift 内部 DNS
- OpenShift 中的DNS 相关组件及其配置
- 1.1 Pod 中的 DNS 配置
- 1.2 Pod 所在宿主机上的 DNS 配置及服务
- 1.2.1 resolv.conf 文件
- DNS 配置
- DNS 查询流程
- 为什么需要内部 DNS?
本文基于 OpenShift 3.11,Kubernetes 1.11 进行测试
OpenShift 集群中,至少有三个地方需要用到 DNS:
- 一是Pod 中的应用通过域名访问外网的时候,需要DNS来解析外网的域名
- 二是在集群内部(pod 中或者宿主机上)通过服务的域名来访问集群内服务的时候,这也是通常所说的服务发现功能,需要通过服务域名来先发现(获取其IP地址)再使用该服务
- 三是从集群外部通过域名访问部署在OpenShift pod 中的服务的时候,需要DNS来解析服务的外网域名
OpenShift 中的DNS 相关组件及其配置
1.1 Pod 中的 DNS 配置
在Linux 系统上,当一个应用通过域名连接远端主机时,DNS 解析会通过系统调用来进行,比如 getaddrinfo()。
和任何Linux 操作系统一样,Pod 的 DNS 定义在 resolv.conf 文件中,其示例如下:
sh-4.2$ cat /etc/resolv.conf
nameserver 172.16.60.83
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)列表。其解释如下:
域名(Domain Name)分为两种,一种是绝对域名(Absolute Domain Name,也称为 Fully-Qualified Domain Name,简称 FQDN),另一种是相对域名(Relative Domain Name,也称为 Partially Qualified Domain Name,简称PQDN)。FQDN 是完整域名,它能够唯一地在DNS名字空间中确定一个记录。比如最高级别的域名A包括子域名B它又包括子域名C,那么FQDN 是 C.B.A.,比如cs.widgetopia.edu.。 有时候我们也会使用PQDN,它是不完全的、模糊的。
FQDN 能被直接到 DNS 名字服务器中查询;而 PQDN 需要先转化为FQDN 再进行查询。其做法是将 PQDN 附加一个搜索域名(search domain)来生成一个 FQDN。在域名系统中,域名结尾是否是『.』被用来区分 FQDN 和 PQDN。比如 apple.com. 表示一个Apple公司的 FQDN,而 apple 则表示一个 PQDN,它的FQDN 可能是 apple.cs.widgetopia.edu.;apple.com 仍然是一个 PQDN,它的FQDN 可能是 apple.com.cs.widgetopia.edu.。
- 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 来解析。
因此,这某些场景中,pod 中的DNS 查询速度会降低应用的性能。解决方法主要有两种,要么直接使用 FQDN,要么减小 ndots 的值,具体请查看 Kubernetes 和 DNS 的有关文档。
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.16.60.83
在部署环境时,会在每个节点上部署 /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 端口。
DNS 配置
在部署 OpenShift 时我们会为所有的节点上都安装好 Dnsmasq(监听53端口)。
$ cat /etc/resolv.conf
# nameserver updated by /etc/NetworkManager/dispatcher.d/99-origin-dns.sh
search cluster.local paas.99cloud.home
nameserver 172.16.60.83
主机的 DNS 指向了本机上运行的 Dnsmasq。
$ cat /etc/dnsmasq.d/origin-upstream-dns.conf
server=172.16.60.90
Dnsmasq 的上游服务器指向了外部的 DNS 服务器(DNS 服务商或使用 CoreDNS 为集群提供域名解析服务)。
$ cat /etc/origin/node/resolv.conf
nameserver 172.16.60.90
而内部的 SkyDNS 也指向了外部的 DNS 服务器作为默认 DNS 域名服务器。
DNS 查询流程
OpenShift 内部使用 SkyDNS,数据存储在 etcd 中。
我们先看下主节点上所有监听了53端口的进程:
$ netstat -tunlp | grep 53
tcp 0 0 172.16.60.83:53 0.0.0.0:* LISTEN 33210/dnsmasq
tcp 0 0 172.17.0.1:53 0.0.0.0:* LISTEN 33210/dnsmasq
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 12277/openshift
tcp 0 0 10.128.0.1:53 0.0.0.0:* LISTEN 62167/dnsmasq
tcp 0 0 0.0.0.0:8053 0.0.0.0:* LISTEN 6392/openshift
127.0.0.1:53 就是真正的 OpenShift 内部 DNS。
Kubernetes 集群内部发布的 Service 对应的域名可以按照 service_name.name_space.svc 这个规则拼接起来。
$ curl -k https://apiserver.kube-service-catalog.svc/healthz
ok
$ oc get svc -n kube-service-catalog
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
apiserver ClusterIP 172.30.36.133 <none> 443/TCP 7h
controller-manager ClusterIP 172.30.189.41 <none> 443/TCP 7h
$ nslookup apiserver.kube-service-catalog.svc
Server: 172.16.60.83
Address: 172.16.60.83#53
Name: apiserver.kube-service-catalog.svc.cluster.local
Address: 172.30.36.133
$ nslookup apiserver.kube-service-catalog.svc 172.16.60.83
Server: 172.16.60.83
Address: 172.16.60.83#53
Name: apiserver.kube-service-catalog.svc.cluster.local
Address: 172.30.36.133
$ nslookup apiserver.kube-service-catalog.svc 127.0.0.1
Server: 127.0.0.1
Address: 127.0.0.1#53
Name: apiserver.kube-service-catalog.svc.cluster.local
Address: 172.30.36.133
对照着流程图,我们可以还原一个完整的 DNS 解析过程:
- 当访问 api server 服务健康状态的 API 时首先会查询 apiserver.kube-service-catalog.svc 这个(内部)域名对应的 IP 地址
- DNS 查询请求先来到主机已经配置好的 DNS 服务器,也就是 Dnsmasq
- Dnsmasq 随后会将查询请求向上递交给 SkyDNS,也就是集群内部的 DNS 服务器,这里能查到正确的 IP 地址
我们在管理 OpenShift 网络的 Pod 内部来查询一下 apiserver.kube-service-catalog.svc.cluster.local
$ oc get pods -n openshift-sdn
NAME READY STATUS RESTARTS AGE
ovs-7kpnv 1/1 Running 0 11h
ovs-7p75w 1/1 Running 0 11h
ovs-m6clw 1/1 Running 0 11h
sdn-7fz45 1/1 Running 0 11h
sdn-l9cq7 1/1 Running 0 11h
sdn-rmqvd 1/1 Running 0 11h
$ oc exec -it sdn-7fz45 -n openshift-sdn /bin/bash
[root@ocp-infra-1 origin]# dig apiserver.kube-service-catalog.svc.cluster.local
; <<>> DiG 9.9.4-RedHat-9.9.4-74.el7_6.1 <<>> apiserver.kube-service-catalog.svc.cluster.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39256
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;apiserver.kube-service-catalog.svc.cluster.local. IN A
;; ANSWER SECTION:
apiserver.kube-service-catalog.svc.cluster.local. 30 IN A 172.30.36.133
;; Query time: 1 msec
;; SERVER: 172.16.60.83#53(172.16.60.83)
;; WHEN: Wed Jul 03 16:17:59 UTC 2019
;; MSG SIZE rcvd: 82
为什么需要内部 DNS?
以往虚拟机中的应用程序之间通讯所使用的宿主机 IP 通常不会轻易变化。但是在容器环境中,容器启动时会被分配新的 IP 地址。编排容器的 Kubernetes 需要静态 IP,提供服务 IP 的 Service 对象就是为了实现这个目的而创建的。Service 对象的域名是不会变化的(拼接规则),借助这个内部的域名,可以实现服务之间的稳定通讯而不用管背后 Pod 本身的 IP 是否被改变。
由于 Pod 的 IP 会变化,如果我们在应用程序或者配置中指定了 IP 地址,Pod 被重新创建时应用程序并不会被主动通知 IP 已经改变。出于这些原因,我们通常使用域名来避免在应用程序中写死 IP 地址。OpenShift 的内部 DNS 使用动态 DNS,无论何时 Pod 被重建,DNS 都将使用新纪录进行更新。