Kubernetes DNS服务目前有两个实现,分别是Kube-dns和CoreDNS。 Kube-dns架构历经两个较大的变化。Kubernetes 1.3之前使用etcd + kube2sky + SkyDNS的架构,Kubernetes 1.3之后使用kubedns + dnsmasq + exechealthz的架构,这两种架构都利用了SkyDNS的能力。SkyDNS支持正向查找(A记录)、服务查找(SRV记录)和反向IP地址查找(PTR记录)。下面我们将分别详解Kube-dns 的这两种架构。1.1 etcd + kube2sky + SkyDNSetcd + kube2sky + SkyDNS属于第一个版本的Kube-dns架构,包含三个重要的组件,分别是:· kube2sky:观察Kubernetes API Server处Service和Endpoints的变化,然后同步状态到Kube-dns自己的etcd;· SkyDNS:监听在53端口,根据etcd中的数据对外提供DNS查询服务。etcd + kube2sky + SkyDNS版本的Kube-dns架构如图4-13所示。图4-13 etcd + kube2sky + SkyDNS版本的Kube-dns架构SkyDNS配置etcd作为后端数据储存,当Kubernetes cluster中的DNS请求被 SkyDNS接受时,SkyDNS从etcd中读取数据,然后封装数据并返回,完成DNS请求响应。Kube2Sky通过watch Service和Endpoints更新etcd中的数据。假设etcd容器ID为0fb60dcfb8b4,下面让我们一窥Service的Cluster IP在etcd中的数据存储:# docker exec -it 0fb60d etcdctl get /skydns/local/kube/svc/default/mysql-service {'host':'10.254.162.44','priority':10,'weight':10,'ttl':30,'targetstrip':0} 如上所示,位于default namespace的服务mysql-service的Cluster IP为 10.254.162.44。SkyDNS的基本配置信息也存在etcd中,例如Kube-dns IP地址、域名后缀等。如下所示:# docker exec -it 0fb60d etcdctl get /skydns/config {'dns-addr':'10.254.10.2:53','ttl':30,'domain':'cluster.local'} 不难看出,SkyDNS是真正对外提供DNS查询服务的组件,kube2sky是Kubernetes到SkyDNS之间的“桥梁”,而etcd则存储Kubernetes只涉及域名解析部分的API对象。这个版本的Kube-dns选择直接部署SkyDNS进程来提供域名解析服务。1.2 kubedns + dnsmasq + exechealthz· kubedns:从Kubernetes API Server处观察Service和Endpoints的变化并调用 SkyDNS的golang库,在内存中维护DNS记录。kubedns作为dnsmasq的上游在dnsmasq cache未命中时提供DNS数据;· dnsmasq:DNS配置工具,监听53端口,为集群提供DNS查询服务。dnsmasq 提供DNS缓存,降低了kubedns的查询压力,提升了DNS域名解析的整体性能;· exechealthz:健康检查,检查Kube-dns和dnsmasq的健康,对外提供/healthz HTTP接口以查询Kube-dns的健康状况。· 从Kubernetes API Server那边观察(watch)到的Service和Endpoints对象没有存在etcd,而是缓放在内存中,既提高了查询性能,也省去了维护etcd存储的工作量;· 引入了dnsmasq容器,由它接受Kubernetes集群中的DNS请求,目的就是利用dnsmasq的cache模块,提高解析性能;· 没有直接部署SkyDNS,而是调用了SkyDNS的golang 库,相当于之前的SkyDNS 和Kube2Sky整合到了一个进程中;运行过程中,dnsmasq在内存中预留一个缓冲区(默认是1GB),保存最近使用到的DNS查询记录。如果缓存中没有要查找的记录,dnsmasq会去kubeDNS 中查询,同时把结果缓存起来。需要注意的是,dnsmasq是一个C++写的小程序,有内存泄漏的“小毛病”。综上所述,无论是哪个版本的架构,Kube-dns的本质就是一个Kubernetes API对象的监视器+ SkyDNS。CoreDNS作为CNCF中托管的一个域名发现的项目,原生集成Kubernetes,它的目标是成为云原生的DNS服务器和服务发现的参考解决方案。所以,CoreDNS走的也是Traefik的路子,降维打击SkyDNS。从Kubernetes 1.12开始,CoreDNS就成了Kubernetes的默认DNS服务器,但 kubeadm默认安装CoreDNS的时间要更早。在Kuberentes 1.9版本中,使用 kubeadm方式安装的集群可以通过以下命令直接安装CoreDNS。# kubeadm init --feature-gates=CoreDNS=true 下面,我们将详细解释CoreDNS的架构设计和基本用法。从功能角度看,CoreDNS更像是一个通用的DNS方案,通过插件模式极大地扩展自身功能,从而适用于不同的场景。正如CoreDNS官方博客描述的那样:CoreDNS is powered by plugins.(1)插件化(Plugins)。基于Caddy服务器框架,CoreDNS实现了一个插件链的架构,将大量应用端的逻辑抽象成插件的形式(例如,Kubernetes的DNS服务发现、Prometheus监控等)暴露给使用者。CoreDNS以预配置的方式将不同的插件串成一条链,按序执行插件链上的逻辑。在编译层面,用户选择需要的插件编译到最终的可执行文件中,使得运行效率更高。CoreDNS采用Go语音编写,所以从代码层面来看,每个插件其实都只实现了CoreDNS定义的接口的组件而已。第三方开发者只要按照CoreDNS Plugin API编写自定义插件,就可以很方便地集成到CoreDNS中。(2)配置简单化。引入表达力更强的DSL,即Corefile形式的配置文件(也是基于Caddy框架开发的)。(3)一体化的解决方案。区别于Kube-dns“三合一”的架构,CoreDNS编译出来就是一个单独的可执行文件,内置了缓存、后端存储管理和健康检查等功能,无须第三方组件辅助实现其他功能,从而使部署更方便,内存管理更安全。Corefile是CoreDNS的配置文件(源于Caddy框架的配置文件Caddyfile),它定义了:· DNS server以什么协议监听在哪个端口(可以同时定义多个server监听不同端口);· DNS负责哪个zone的权威(authoritative)DNS解析;ZONE:[PORT] { [PLUGIN] ... } · ZONE:定义DNS server负责的zone,PORT是可选项,默认为53; · PLUGIN:定义DNS server要加载的插件,每个插件可以有多个参数。 上述配置文件表达的是:DNS server负责根域 . 的解析,其中插件是chaos且没有参数。即DNS server监听53端口并且不使用任何插件。如果此时定义其他DNS server,需要保证监听端口不冲突。如果是在原来DNS server的基础上增加zone,则要保证zone之间不冲突。例如:如上所示,另一个 DNS server 监听在 54 端口并负责根域 . 的解析。example.org { whoami } org { whoami } 这是同一个DNS server但是负责不同zone的解析,而且有不同的插件链。跟其他DNS服务器类似,Corefile也可以定义Reverse Zone:0.0.10.in-addr.arpa { whoami } CoreDNS除了支持DNS协议,也支持TLS和gRPC,即DNS-over-TLS和DNS-overgRPC模式。例如:tls://example.org:1443 { #... } 当CoreDNS启动后,它将根据配置文件启动不同的DNS server,每个DNS server都拥有自己的插件链。当新来一个DNS请求时,它将依次经历以下3步逻辑:(1)如果当前请求的DNS server有多个zone,则将采用贪心原则选择最匹配的 zone。(2)一旦找到匹配的DNS server,按照plugin.cfg定义的顺序遍历执行插件链上的插件。(3)每个插件将判断当前请求是否应该处理,将有以下几种可能的情况:· 请求被当前插件处理。插件将生成对应的响应并返回客户端,此时请求结束,下一个插件将不会被调用,如whoami插件;· 请求不被当前插件处理。直接调用下一个插件。如果最后一个插件执行错误,服务器返回SERVFAIL响应;· 请求被当前插件以Fallthrough的形式处理。如果请求在该插件处理过程中有可能跳转至下一个插件,该过程称为fallthrough,并以关键字fallthrough决定是否允许此项操作。例如,host插件尝试用/etc/hosts查询域名,如果没有查询到则会调用下一个插件;· 请求在处理过程携带hint。请求被插件处理,并在其响应中添加了某些提示信息(hint),继续交由下一个插件处理。这些额外的信息将组成对客户端的最终响应,例如metric插件。下面将以一个实际的 Corefile为例,详解CoreDNS处理DNS请求的工作流。Corefile如下所示:coredns.io:5300 { file /etc/coredns/zones/coredns.io.db } example.io:53 { errors log file /etc/coredns/zones/example.io.db } example.net:53 { file /etc/coredns/zones/example.net.db } .:53 { errors log health rewrite name foo.example.com foo.default.svc.cluster.local } 通过配置文件不难看出,我们定义了两个DNS server(尽管有4个配置块),分别监听5300和53端口。将以上Corefile翻译成处理逻辑图,如图4-15所示。每个进入某个DNS server的请求将按照plugin.cfg的定义顺序执行其已经加载的插件。需要注意的是,尽管在.:53配置了health插件,但是它并未在上面的逻辑图中出现,原因是该插件并未参与请求相关的逻辑(即并没有在插件链上),只是修改了DNS server的配置。通常,我们可以将插件分为两种:· Normal插件:参与请求相关的逻辑,且插入插件链中;· 其他插件:不参与请求相关的逻辑,也不出现在插件链中,只是用于修改DNS server的配置,例如 health、tls等插件。下面介绍CoreDNS的域名解析性能和资源消耗情况,如表4-4所示。值得一提的是,以上性能测试数据是在不带cache的情况下取得的,一般情况下要高于Kube-dns,具体的CoreDNS与Kube-dns的性能对比数据请看下文。 4.3.3 Kube-dns VS. CoreDNS虽然Kube-dns血统纯正,而且早早地进入Kubernetes 的“后宫”,也早有“名分”,但近来CoreDNS 却独得Kubernetes Network工作小组核心成员“圣宠”,它不仅早早地进入CNCF,就连其中一位创始人也被挖到谷歌的Kubernetes核心团队担任资深工程师(seniorstaff engineer)。与Kube-dns的三进程架构不同,CoreDNS就一个进程,运维起来更加简单。而且采用Go语言编写,内存安全,高性能。值得称道的是,CoreDNS采用的是“插件链”架构,每个插件挂载一个DNS功能,保证了功能的灵活、易扩展。尽管资历不深,却“集万千宠爱于一身”,自然是有绝技的。下面我们从性能和功能两个维度全面对比Kube-dns和CoreDNS。下面从内存、CPU、QPS 和时延这4个维度对比Kube-dns和CoreDNS的性能和资源消耗。CoreDNS和Kube-dns都维护集群中所有服务和端点的本地缓存。因此,随着服务和端点数量的增加,每个DNS Pod的内存要求也会增加。在默认设置下,CoreDNS应该比Kube-dns使用更少的内存,这是由于部署Kube-dns需要使用三个容器,而CoreDNS只要一个容器便足够。图4-16对比了随着集群内服务和Pod数量的增加,CoreDNS和Kube-dns消耗的内存。图4-16 CoreDNS和Kube-dns内存消耗对比 当Kube-dns和CoreDNS分别达到最大QPS负载时,观测CPU的使用量分别是 58Mi(Kube-dns)和5Mi(CoreDNS)。注:Mi是CPU核心数的度量单位,代表千分之一核,因此58Mi即0.058核,5Mi 即0.005核。· Kube-dns对外部域名解析(例如kubernetes.default.svc.cluster.local)的表现比CoreDNS要好10%左右。这可能是因为dnsmasq比CoreDNS的内置缓存做了更多的性能优化;· CoreDNS对外部域名解析的性能提高了约3倍。部分原因可能是Kube-dns没有在缓存中存储DNS查询的负面响应(例如访问一个不存在的域名)。然而,即使Kube-dns在缓存中存储了负面响应也没有明显的区别,数据如表4-6所示。为什么建议使用CoreDNS呢?Kubernetes官方已经将CoreDNS“扶正”,成为了默认模式,除了性能好,还有什么其他优势吗?Core DNS修复了Kube-dns的一些“令人讨厌”的问题:· dns#55 - Allow custom DNS entries for Kube-dns(允许Kube-dns自定义DNS记录);· dns#116 - Missing ‘A’ records for headless service with pods sharing hostname(当后端Pod共享主机名时,headless Service丢失A记录);· dns#131 - ExternalName not using stubDomains settings(stubDomains配置对 ExternalName Service不生效);· dns#167 - Enable round robin A/AAAA records(启用Kube-dns A/AAAA记录的轮询);· dns#190 - Kube-dns cannot run as non-root user(只有root用户才能运行 Kube-dns);· dns#232 - Use pod’s name instead of pod’s hostname in DNS SRV records(在DNS SRV记录中使用Pod名而不是Pod主机名)。· Zone transfers - list all records, or copy records to another server(CoreDNS允许将全部或部分的DNS记录复制到另一个CoreDNS服务器);· Namespace and label filtering - expose a limited set of services(基于namespace 和labels过滤一部分Service记录);· Adjustable TTL - adjust up/down default service record TTL(动态调整DNS记录的 TTL);· Negative Caching - By default caches negative responses(默认存储DNS查询的负面响应);其中,原生支持基于namespace和labels隔离及过滤Service和Pod的DNS记录这一特性,在多租户场景下格外有用。无论是Kube-dns还是CoreDNS,基本原理都是利用watch Kubernetes的Service和Pod,生成DNS记录,然后通过重新配置Kubelet的DNS选项让新启动的Pod使用Kube-dns或CoreDNS提供的Kubernetes集群内域名解析服务。PS:本文经作者授权,转载于《Kubernetes网络权威指南》图书第4.3章节。
|