分享

沧海桑田:Kubernetes DNS架构演进之路

 黄爸爸好 2019-11-12
Kubernetes DNS服务目前有两个实现,分别是Kube-dns和CoreDNS。

1、Kube-dns 的工作原理

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 + SkyDNS
etcd + kube2sky + SkyDNS属于第一个版本的Kube-dns架构,包含三个重要的组件,分别是:
· etcd:存储所有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
新演进的Kube-dns架构如图4-14所示。
 

图4-14  新演进的 Kube-dns 架构

Kube-dns包含以下三个核心组件:

· 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的健康状况。

这个版本的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。

2、上位的CoreDNS

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.

CoreDNS有以下3个特点。

(1)插件化(Plugins)。基于Caddy服务器框架,CoreDNS实现了一个插件链的架构,将大量应用端的逻辑抽象成插件的形式(例如,Kubernetes的DNS服务发现、Prometheus监控等)暴露给使用者。CoreDNS以预配置的方式将不同的插件串成一条链,按序执行插件链上的逻辑。在编译层面,用户选择需要的插件编译到最终的可执行文件中,使得运行效率更高。CoreDNS采用Go语音编写,所以从代码层面来看,每个插件其实都只实现了CoreDNS定义的接口的组件而已。第三方开发者只要按照CoreDNS Plugin API编写自定义插件,就可以很方便地集成到CoreDNS中。

(2)配置简单化。引入表达力更强的DSL,即Corefile形式的配置文件(也是基于Caddy框架开发的)。

(3)一体化的解决方案。区别于Kube-dns“三合一”的架构,CoreDNS编译出来就是一个单独的可执行文件,内置了缓存、后端存储管理和健康检查等功能,无须第三方组件辅助实现其他功能,从而使部署更方便,内存管理更安全。

1. Corefile 知多少

Corefile是CoreDNS的配置文件(源于Caddy框架的配置文件Caddyfile),它定义了:
· DNS server以什么协议监听在哪个端口(可以同时定义多个server监听不同端口);
· DNS负责哪个zone的权威(authoritative)DNS解析;
· DNS server将加载哪些插件。
通常,一个典型的Corefile格式如下:






ZONE:[PORT] {[PLUGIN] ...}· ZONE:定义DNS server负责的zone,PORT是可选项,默认为53;· PLUGIN:定义DNS server要加载的插件,每个插件可以有多个参数。

例如:



{chaos CoreDNS-001}

上述配置文件表达的是:DNS server负责根域 . 的解析,其中插件是chaos且没有参数。

1)定义DNS server
一个最简单的DNS server配置文件如下:

.{}

即DNS server监听53端口并且不使用任何插件。如果此时定义其他DNS server,需要保证监听端口不冲突。如果是在原来DNS server的基础上增加zone,则要保证zone之间不冲突。例如:



. {}.:54 {}

如上所示,另一个 DNS server 监听在 54 端口并负责根域 . 的解析。
又如:







example.org {whoami}org {whoami}

这是同一个DNS server但是负责不同zone的解析,而且有不同的插件链。

2)定义Reverse Zone
跟其他DNS服务器类似,Corefile也可以定义Reverse Zone:



0.0.10.in-addr.arpa {whoami}

或者简化版本:



10.0.0.0/24 {whoami}

3)使用不同的通信协议
CoreDNS除了支持DNS协议,也支持TLS和gRPC,即DNS-over-TLS和DNS-overgRPC模式。例如:



tls://example.org:1443 {#...}

2. 插件工作模式
当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插件。

3. CoreDNS 请求处理工作流

下面将以一个实际的 Corefile为例,详解CoreDNS处理DNS请求的工作流。Corefile如下所示:

















coredns.io:5300 {file /etc/coredns/zones/coredns.io.db}example.io:53 {errorslogfile /etc/coredns/zones/example.io.db}example.net:53 {file /etc/coredns/zones/example.net.db}.:53 {errorsloghealthrewrite name foo.example.com foo.default.svc.cluster.local}

通过配置文件不难看出,我们定义了两个DNS server(尽管有4个配置块),分别监听5300和53端口。将以上Corefile翻译成处理逻辑图,如图4-15所示。
 

图4-15  CoreDNS请求处理流程

每个进入某个DNS server的请求将按照plugin.cfg的定义顺序执行其已经加载的插件。
需要注意的是,尽管在.:53配置了health插件,但是它并未在上面的逻辑图中出现,原因是该插件并未参与请求相关的逻辑(即并没有在插件链上),只是修改了DNS server的配置。通常,我们可以将插件分为两种:

· Normal插件:参与请求相关的逻辑,且插入插件链中;
· 其他插件:不参与请求相关的逻辑,也不出现在插件链中,只是用于修改DNS server的配置,例如 health、tls等插件。

4. 性能测试
下面介绍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。

1. 性能对比
下面从内存、CPU、QPS 和时延这4个维度对比Kube-dns和CoreDNS的性能和资源消耗。
内存和CPU消耗
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核。

QPS和时延

从表4-5所示的数据中可以看出:
· Kube-dns对外部域名解析(例如kubernetes.default.svc.cluster.local)的表现比CoreDNS要好10%左右。这可能是因为dnsmasq比CoreDNS的内置缓存做了更多的性能优化;

· CoreDNS对外部域名解析的性能提高了约3倍。部分原因可能是Kube-dns没有在缓存中存储DNS查询的负面响应(例如访问一个不存在的域名)。然而,即使Kube-dns在缓存中存储了负面响应也没有明显的区别,数据如表4-6所示。


2. 功能对比

为什么建议使用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记录这一特性,在多租户场景下格外有用。

4.3.4 小结
无论是Kube-dns还是CoreDNS,基本原理都是利用watch Kubernetes的Service和Pod,生成DNS记录,然后通过重新配置Kubelet的DNS选项让新启动的Pod使用Kube-dns或CoreDNS提供的Kubernetes集群内域名解析服务。

PS:本文经作者授权,转载于《Kubernetes网络权威指南》图书第4.3章节。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多