Kubernetes DNS架构演进之路(Kube-dns、CoreDNS)

(本文章是学习《kubernetes 网络权威指南》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)


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记录)。

etcd+kube2sky+SkyDNS
etcd+kube2sky+SkyDNS 是属于第一个版本的kube-dns架构,包含三个重要的组件,分别是:
etcd:存储所有DNS查找所需的数据;
kube2sky:观察kubernetes API Server 和Endpoint 的变化,然后同步状态到kube-dns自己的etcd;
SkyDNS:监听在53 端口,根据etcd中的数据对外提供DNS查询服务;
结构图如下
(由于图片大小限制上传不了,敬请谅解)

SkyDNS 配置etcd作为后端数据的存储,当kubernetes cluster 中国的DNS请求被SkyDNS接收时,SkyDNS会从etcd中读取数据,然后封装数据并返回,完成DNS请求响应。kube2sky 通过watch Service 和Endpoint 更新etcd中的数据。
假设 etcd 容器ID为 0fb60dxxx,下面我们来看看Service的Cluster IP在etcd中的数据存储。

docker exec -it 0fb60d  etcdctl get /skydns/local/kube/svc/default/mysql-service 
{"host":"10.254.162.44", "priority":10, "weught":10."ttl":30,"targetstrip":0}

如上所示,位于default namespace 的服务mysql-service 的cluster IP为 10.254.162.44.
SkyDNS 的基本配置信息也存在了etcd中,例如kube-dns IP地址、域名后缀等。如下所示:

docker exec -it ofb6od  etcdctl get /skydns/config
{"dns-addr":"10.254.10.2:53","ttl":30,"domain":"cluster.local"}

不难看出,SkyDNS是正式对外提供查询服务的组件,kube2sky是kubernetes到SkyDNS之间的桥梁,而etcd是存储kubernetes只涉及域名接卸部分的API对象。这个版本的kube-dns直接部署SkyDNS进程来提供域名解析服务。

kubedns+dnsmasq+exechealthz
新演进的kube-dns架构如图
(由于图片大小限制上传不了,敬请谅解)

kube-dns包含三个核心组件:

  • kubedns:从kubernetes API Server处观察Service和Endpoint的变化,并调用SkyDNS的golang库。在内存中维护DNS记录。kubedns作为dnsmasq的上游,在dnsmasq cache未命中的时候提供DNS数据。
  • dnsmasq:DNS配置工具,监听53端口,为集群提供DNS查询服务。dnsmasq提供DNS缓存,降低了kubedns的查询压力,并提升了DNS域名解析的整体性能;
  • exechealthz:健康检查,检查kubedns和dnsmasq的健康,对外提供/healthz HTTP 接口,以查询kube-dns的健康状况。

这个版本的kube-dns和之间的版本区别在于:

  • 从kubernetes API Server 那边观察得到的Service和Endpoint
    对象没有存在etcd中,而是缓存在内存中,既提高了查询性能,也省去了维护etcd存储的工作量;
  • 引入了dnsmasq容器,由它接收kubernetes集群中的DNS请求,目的是利用dnsmasq的cache模块,提高解析性能;
  • 没有直接部署SkyDNS,而是调用了SkyDNS的golang库,相当于之前的SkyDNS和kube2sky整合到一个进程中;
  • 增加了健康检查;

运行过程中,dnsmasq在内存中预留了一个缓冲区(默认是1G),保留最近使用的DNS查询记录,如果缓存中没有找到要查询的记录,dnsmasq会向kubedns中查询,同时把记录缓存起来。
注:dnsmasq是使用C++写的一个小程序,存在内存泄漏的“小毛病”
综上所述,不管是哪种架构,kube-dns的本质都是一个kubernetes API对象的监视器+SkyDNS;

2、上位的CoreDNS

CoreDNS作为CNCF中托管的一个域名发现项目,原生集成kubernetes,它的目标是成为云原生的DNS服务器和服务发现的参考解决方案。虽有,CoreDNS走的也是TraeFik的路子,姜维打击SkyDNS。
从kubernetes1.12开始,CoreDNS就成为kubernetes的默认DNS服务器,但是kubeadm默认安装CoreDNS的时间更早。在kubernetes 1.9版本中,使用kubeadm方式安装的集群可以通过以下明明直接安装CoreDNS。

kubeadm init  --feature-getas=CoreDNS=true

下面我们将详细讲解CoreDNS的架构设计和基本用法。
从功能角度看,CoreDNS更像是一个通用的DNS方案,通过插件模式极大地扩展自身功能,从而适应不同的场景。
CoreDNS有以下三个特点:

  • 插件化(Plugins)。基于Caddy服务器框架,CoreDNS实现了一个插件链架构,将大量应用端的裸机抽象成插件形式(例如,kubernetes的DNS服务发现、Prometheus)监控等暴露给使用者。CoreDNS以预配置的方式将不同的插件串成一条链,按序执行插件上的逻辑。在编译层面,用户选择需要的插件编译到最终的可执行文件中,使得运行效率更高。CoreDNS采用Go语言编写,所有从代码层面看来,每个插件都只实现了CoreDNS定义的接口组件而已。第三方开发者只要按照CoreDNS
    Plugin API编写自定义插件,就可以很方便的集成到CoreDNS中。
  • 配置简单化。引入表达力更强的DSL,即Corefile形式的配置文件(也是基于caddy框架开放的)。
  • 一体化解决方案。区别在于kube-dns
    “三合一”的架构,COreDNS编译出来就是一个单独的可执行文件,内置了缓存、后端存储管理和健康检查等功能,无需第三方组件辅助实现其他功能,从而使部署更方便、内存管理更安全。

Corefile知多少

Corefile是CoreDNS的配置文件(源于Caddy框架的配置文件Caddyfile),它定义了:

  • DNS server以什么协议监听哪个端口(可以同时定义多个server监听不同端口);
  • DNS 负责哪个zone的权威DNS解析
  • DNS server加载那些插件

通常,一个典型的Corefile格式如下:

ZONR:{PORT} {
    [PLUGIN] ...
}
  • ZONE:定义 DNS server 负责的zone,PORT是可选项,默认为53;
  • PLUGIN:定义DNS serve要加载的插件,每个插件可以有多个参数 例如:
. {
    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 {
    whomi
}
org {
    whomi
}

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

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

0.0.10.in-addr.arpa {
    whoami
}

或者简化版本:

10.0.0.0/24 {
    whomi
}

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

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

插件工作模式

CoreDNS启动后,它将根据配置文件启动不同的DNS server,每个DNS server 都有自己的插件链。当新来一个DNS请求时,它将经历以下3步逻辑:
(1)如果当前请求的DNS server有多个zone,则将采用贪心原则选择最匹配的zone;
(2)一旦找到匹配的DNS server,按照plugin.cfg 定义的顺序,便利插件链上的插件;
(3)每个插件判断当前请求是否应该处理,将有以下几种可能的情况:

  • 请求被当前插件处理。插件将生成对应的响应,并返回给客户端,此时请求结束,下一个插件将不会被调用,如whoami插件。
  • 请求不被当前插件处理。直接调用下一个插件。如果最后一个插件执行错误,服务器返回SERVFALL响应。
  • 请求被当前插件以Fallthrough的形式处理。如果请求在该插件处理过程中有可能跳转至下一个插件,该过程称为fallthrough,并以关键字fallthrough决定是否允许此项操作。例如,host插件尝试用/etc/hosts查询域名,如果没有查询到则会调用下一个插件;
  • 请求在处理过程中携带hint:请求被插件处理,并在其响应中添加某些提示信息(hint),继续交由下一个插件处理。这些额外的信息将组成对客户端的最终响应,例如metric插件。

CoreDNS 请求处理工作流

下面将以一个实际的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 翻译成处理逻辑图,如图所示:
(由于图片大小限制上传不了,敬请谅解)

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

  • Normal插件:参与请求相关的逻辑,且插入插件链中
  • 其他插件:不参与请求相关的逻辑,也不出现在插件链中。只是用于修改DNS server的配置,例如 health、tls插件。
发布了13 篇原创文章 · 获赞 6 · 访问量 342

猜你喜欢

转载自blog.csdn.net/WuYuChen20/article/details/104517206