CoreDNS和NodeLocalDNS配置详解

1、前言:

CoreDNS作为CNCF中托管的一个域名发现的项目,原生集成Kubernetes,它的目标是成为云原生的DNS服务器和服务发现的参考解决方案,目前CoreDNS已经从 CNCF 正式毕业,并正式成为 Kubernetes 的 DNS 服务器。它是 Golang 编写的一个插件式 DNS 服务器,从Kubernetes 1.13开始,CoreDNS就成了Kubernetes的默认DNS服务器,但 kubeadm默认安装CoreDNS的时间要更早。它有以下几个特性:

  • 插件化(Plugins)

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

  • 配置简单化

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

  • 一体化的解决方案

    区别于 kube-dns,CoreDNS 编译出来就是一个单独的二进制可执行文件,内置了 cache,backend storage,health check 等功能,无需第三方组件来辅助实现其他功能,从而使得部署更方便,内存管理更为安全。

其实从功能角度来看,CoreDNS 更像是一个通用 DNS 方案(类似于 BIND),然后通过插件模式来极大地扩展自身功能,从而可以适用于不同的场景(比如 Kubernetes)。正如官方博客所说:

1
CoreDNS is powered by plugins.

另外,在正式介绍CoreDNS和NodeocalDNS配置前先普及一下本文可能用到的一些概念:

1)in-addr.arpa (反向域名解析,从IP地址到域名的映射) 

    in-addr是inverse address, arpa 的标准名称对应为Address and Routing Parameter Area, 即“地址路由参数域”。

      由于在域名系统中,一个IP地址可以对应多个域名,因此从IP出发去找域名,理论上应该遍历整个域名树,但这在 Internet上是不现实的。为了完成逆向域名解析,系统提供一个特别域,该特别域称为逆向解析域in-addr.arpa。这样欲解析的IP地址就会被表达成一种像域名一样的可显示串形式,后缀以逆向解析域域名“in-addr.arpa”结尾。例如一个IP地址:218.30.103.170,其逆向域名表达方式为:170.103.30.218.in-addr.arpa。两种表达方式中IP地址部分顺序恰好相反,因为域名结构是自底向上(从子域到域),而IP地址结构是自顶向下(从网络到主机)的。实质上逆向域名解析是将IP地址表达成一个域名,以地址做为索引的域名空间,这样逆向解析的很大部分可以纳入正向解析中。

2、CoreDNS原理

watch Kubernetes的Service和Pod,生成DNS记录,然后通过重新配置Kubelet的DNS选项让新启动的Pod使用CoreDNS提供Kubernetes集群内域名解析服务。

3、Corefile 介绍

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

  • server 以什么协议监听在哪个端口(可以同时定义多个 server 监听不同端口)

  • server 负责哪个 zone 的权威(authoritative)DNS 解析

  • server 将加载哪些插件

常见地,一个典型的 Corefile 格式如下所示:

1
2
3
ZONE:[PORT] {
    [PLUGIN] ...
}
  • ZONE : 定义 server 负责的 zone,PORT 是可选项,默认为 53;

  • PLUGIN : 定义 server 所要加载的 plugin。每个 plugin 可以有多个参数;

比如:

1
2
3
. {
    whoami
}

上述配置文件表达的是:server 负责根域 . 的解析("."表示所有的域名都会匹配),其中 plugin 是 whoami 且没有参数。

1)定义 DNS server

一个最简单的配置文件可以为:

1
.{}

即 server 监听 53 端口并不使用插件。如果此时在定义其他 server,要保证监听端口不冲突;如果是在原来 server 增加 zone,则要保证 zone 之间不冲突,如:

1
2
3
# 多个server的话需要保证监听端口不冲突
.    {}
.:54 {}

另一个 server 运行于 54 端口并负责根域 . 的解析。

又如:

1
2
3
4
5
6
7
#同一server的话需要保证zone不冲突
example.org {
    whoami
}
org {
    whoami
}

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

2)定义 Reverse Zone

跟其他 DNS 服务器类似,Corefile 也可以定义 Reverse Zone(反向解析 IP 地址对应的域名):

1
2
3
0.0.10.in-addr.arpa {
    whoami
}

或者简化版本:

1
2
3
10.0.0.0/24 {
    whoami
}

可以通过 dig 进行反向查询:

1
$ dig -x 10.0.0.1

3)使用不同的通信协议

CoreDNS 除了支持 DNS 协议,也支持 TLS 和 gRPC,即 DNS-over-TLS 和 DNS-over-gRPC 模式:

1
2
3
tls://example.org:1443 {
#...
}

4、插件的工作模式

当 CoreDNS 启动后,它将根据配置文件启动不同 server (假设配置文件中配置了多个DNS Server),每台 server 都拥有自己的插件链。当有 DNS 请求时,它将依次经历如下 3 步逻辑:

  (1)如果有当前请求的 server 有多个 zone,将采用贪心原则选择最匹配的 zone,例如当前server含有com和baidu.com这两个zone,当客户端请求当前server解析域名www.baidu.com的话,根据贪心原则会选择baidu.com这个zone;

  (2)一旦找到匹配的 server,按照 plugin.cfg定义的顺序执行插件链上的插件(目前可用插件列表);

  (3)每个插件将判断当前请求是否应该处理,将有以下几种可能: 

  • 请求被当前插件处理

    插件将生成对应的响应并回给客户端,此时请求结束,下一个插件将不会被调用,如 whoami 插件;

  • 请求不被当前插件处理

   直接调用下一个插件。如果最后一个插件执行错误,服务器返回SERVFAIL响应;

  • 请求被当前插件以 Fallthrough 形式处理

    如果请求在该插件处理过程中有可能将跳转至下一个插件,该过程称为 fallthrough,并以关键字 fallthrough 来决定是否允许此项操作,例如 host 插件,当查询域名未位于 /etc/hosts,则调用下一个插件;

  • 请求在处理过程被携带 Hint

    请求被插件处理,并在其响应中添加了某些信息(hint)后继续交由下一个插件处理。这些额外的信息将组成对客户端的最终响应,如 metric 插件;

5、 CoreDNS 如何处理 DNS 请求

如果 Corefile 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
coredns.io:5300 {
    file db.coredns.io
}
 
example.io:53 {
    log
    file db.example.io
    errors
}
 
example.net:53 {
    file db.example.net
}
 
.:53 {
    log
    errors
    cache
    kubernetes
    health
    proxy . 8.8.8.8
}

从配置文件来看,我们定义了两个 server(其中监听5300端口的server仅配置了coredns.io这一个zone,监听53端口的server配置了example.io、example.net和.三个zone),分别监听在 5300 和 53 端口。其逻辑图可如下所示:

每个进入到某个 server 的请求将按照 plugin.cfg 定义顺序执行其已经加载的插件。

从上图,我们需要注意的是尽管在 .:53 配置了 health 插件,但是它并为在上面的逻辑图中出现,原因是:该插件并未参与请求相关的逻辑(即并没有在插件链上),只是修改了 server 配置。更一般地,我们可以将插件分为两种:

  • Normal 插件:参与请求相关的逻辑,且插入到插件链中;

  • 其他插件:不参与请求相关的逻辑,也不出现在插件链中,只是用于修改 server 的配置,如 healthtls 等插件;

6、Kubernetes中CoreDNS配置详解

通过下面命令查看当前Kubernetes集群coredns配置

1
kubectl get configmaps -n=kube-system coredns -o yaml

结果如下(只粘贴了配置部分): 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        hosts {
            10.20.31.104 www.google.com
            fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
......  
 参数 说明
errors 打印/存储错误日志
health CoreDNS自身健康状态报告,默认监听端口8080,一般用来做健康检查。您可以通过http://localhost:8080/health获取健康状态(coredns Pod配置的存活探针使用此接口获取CoreDNS自身健康)。
ready CoreDNS插件状态报告,默认监听端口8181,一般用来做可读性检查。可以通过http://localhost:8181/ready获取可读状态。当所有插件都运行后,ready状态为200(coredns Pod配置的就绪探针使用此接口获取CoreDNS插件的就绪状况)。
kubernetes

kubernetes: CoreDNS Kubernetes插件,提供集群内服务解析能力。该插件实现了基于Kubernetes DNS的服务发现。

其中cluster.local in-addr.arpa ip6.arpa表示kubernetes插件会处理域名后缀为cluster.local的所有域名以及处理所有的in-addr.arpa中的反向dns查找和ip6.arpa形式域名,其中kubernetes集群域名后缀是在kubelet参数中配置的,默认值为cluster.local。

pods参数用于设置基于pods ip的A记录,例如default namespace下存在一个pod,ip为10.233.64.2,此Pod的A记录为10-233-64-2.default.pod.cluster.local ->10.233.64.2,pods参数有三个值,其中disabled表示关闭pod ip的A记录;insecure总是从请求中返回一个带有IP的A记录(不检查k8s)(例如:1-2-3-4.ns.pod.cluster.local. in A 1.2.3.4);verified表示如果在同一命名空间中,则存在匹配IP的pod。

fallthrough:指定特定区域查询失败,如果in-addr.arpa和ip6.arpa形式的域名在kubernetes插件没有匹配成功的话则进入下一个 plugin 继续。

ttl参数为响应客户端请求设置的自定义 TTL。
 
hosts

加载 /etc/hosts 文件里面的解析信息。如果一个域名在 hosts 文件中存在,则优先使用这个信息返回,在本例中一定要注意hosts插件的位置,如果放在了forward插件之后,那么hosts插件即没有匹配的机会;

prometeus
CoreDNS自身metrics数据接口。可以通过http://localhost:9153/metrics获取prometheus格式的监控数据。
forward 将域名查询请求转到预定义的DNS服务器。默认配置中,当域名不在Kubernetes域时,将请求转发到预定义的解析器(/etc/resolv.conf)中。默认使用宿主机的/etc/resolv.conf配置。forward . /etc/resolv.conf中的"."表示匹配所有域名。
cache 溯源得到的结果,缓存指定时间。类似 TTL 的概念;TTL以秒为单位。如果未指定,将使用最大 TTL,对于 NOERROR 响应为 3600,对于拒绝存在响应为 1800。将 TTL 设置为 30表示缓存记录长达 30 秒。
loop

环路检测,如果检测到环路,则停止CoreDNS。例如,forward . /etc/resolv.conf中配置的dns server地址和coredns地址一致,客户端请求解析in-addr.arpa类型域名,在kubernetes插件中解析in-addr.arpa类型域名失败,由于配置了fallthrough in-addr.arpa ip6.arpa,那么就会进入到forward插件继续解析,由于forward . /etc/resolv.conf中配置的dns server地址和coredns地址一致,那么又会传到dns server进行解析,这么就陷入了一个死循环,通过loop插件可以避免环路问题。

reload
允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,请等待两分钟以使更改生效,无需重启coredns对应的Pod。
loadbalance
循环DNS负载均衡器,可以在答案中随机A、AAAA、MX记录的顺序。

讲一下我自己的理解:

  1. 配置文件类似于 nginx 配置文件的格式;
  2. 最外面一级的大括号,对应‘服务’的概念。多个服务可以共用一个端口;
  3. 往里面一级的大括号,对应 plugins 的概念,每一个大括号都是一个 plugin。这里可以看出,plugins 是 CoreDNS 的一等公民;
  4. 服务之间顺序有无关联没有感觉,但 plugins 之间是严重顺序相关的。某些 plugin 必须用 fallthrough 关键字流向下一个 plugin;
  5. plugin 内部的配置选项是顺序无关的;
  6. 从 CoreDNS 的性能指标看,适合做大型服务。

7、Kubenetes中NodeLocalDNS配置详解

nodelocaldns介绍本文不在多余介绍,只针对nodelocaldns配置进行讲解,nodelocaldns配置几乎和CoreDNS配置一致。

通过下面命令查看当前Kubernetes集群coredns配置

1
kubectl get configmaps -n=kube-system nodelocaldns -o yaml

结果如下(只粘贴了配置部分): 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: v1
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
            success 9984 30
            denial 9984 5
        }
        reload
        loop
        bind 169.254.25.10
        forward . 10.233.0.3 {
            force_tcp
        }
        prometheus :9253
        health 169.254.25.10:9254
    }
    in-addr.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . 10.233.0.3 {
            force_tcp
        }
        prometheus :9253
    }
    ip6.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . 10.233.0.3 {
            force_tcp
        }
        prometheus :9253
    }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.25.10
        forward . /etc/resolv.conf
        prometheus :9253
    }
kind: ConfigMap
......

nodelocaldns只配置了一个server,监听默认的UDP 53端口,4个zone。域名后缀为cluster.local的所有域名以及in-addr.arpa和ip6.arpa形式域名走coredns进行域名解析,其他外部域名使用宿主机的/etc/resolv.conf文件配置的nameserver进行解析。这里着重解释下cluster.local这个zone里面cache插件的配置。

cache [TTL] [ZONES...]
● TTL最大 TTL,以秒为单位。如果未指定,将使用最大 TTL,对于 NOERROR 响应为 3600,对于拒绝存在响应为 1800。将 TTL 设置为 30表示缓存记录长达 30 秒。
● ZONES应该缓存的区域。如果为空,则使用配置块中的区域。DNS缓存时间。一条域名解析记录在DNS缓存中的存留时间最长为30s,缓存中的每个元素都根据其 TTL 进行缓存(以TTL为最大值)。缓存分为 256 个分片,每个分片默认最多可容纳 39 个项目 - 总大小为 256 * 39 = 9984 个项目。
● success,覆盖缓存成功响应的设置。CAPACITY表示我们在开始驱逐(随机)之前缓存的最大数据包数。TTL会覆盖缓存最大 TTL。 MINTTL覆盖缓存最小 TTL(默认 5),这对于限制对后端的查询很有用。
● denial,覆盖缓存拒绝存在响应的设置。CAPACITY表示我们在开始驱逐 (LRU) 之前缓存的最大数据包数。TTL会覆盖缓存最大 TTL。 MINTTL覆盖缓存最小 TTL(默认 5),这对于限制对后端的查询很有用。还有第三类 ( error) 但这些响应永远不会被缓存。(negative caching ttl:所查询的数据不存在时,这个否定的回答在其他服务器的缓存时间。

简单来说,success 9984 30表示对于成功的缓存最多缓存9984条域名解析记录,缓存时间为30s,denial 9984 5表示对于失败的缓存最多缓存9984条域名解析记录,缓存时间为5s。

8、总结

参考:https://my.oschina.net/u/4148359/blog/4868274

参考:https://mp.weixin.qq.com/s/vU6jMvNo3loXLltXF6cWVw

 
posted @ 2022-08-16 14:05  那就凑个整吧  阅读(2210)  评论(0编辑  收藏  举报