CoreDNS学习笔记
CoreDNS是一个基于Go的DNS服务器,非常灵活,支持插件链。CoreDNS内置了30+的插件,你还可以将一些外部插件也编译到CoreDNS的二进制文件中。
1 2 3 4 5 6 7 8 |
export GOPATH=${GOPATH-~/go} mkdir -p $GOPATH/src/github.com/coredns cd $GOPATH/src/github.com/coredns/ wget https://github.com/coredns/coredns/archive/v1.0.5.tar.gz tar xvf v1.0.5.tar.gz mv coredns-1.0.5 coredns cd coredns make CHECKS= godeps all |
CoreDNS的每个版本都发布了Docker镜像,在容器中启动CoreDNS的示例:
1 2 3 |
docker run --name coredns -h coredns --network local --ip 172.21.0.12 -p 127.0.0.1:53:53 -p 127.0.0.1:53:53/udp \ --volume /media/alex/Repository/Linkage/Docker/coredns/:/root/ \ --restart=always -d docker.gmem.cc/coredns/coredns:1.5.0 -conf /root/Corefile |
在加载了配置文件(默认为当前工作目录下的Corefile)后,CoreDNS就会运行一系列的DNS服务器。每个服务器由两个属性来识别:
- 其服务的区域(Zone),也就是DNS后缀
- 其监听的端口
当CoreDNS接收到一个DNS请求时,处理的步骤如下:
- 找到匹配端口的服务器
- 如果有多个服务器监听目标端口,则将请求转发给具有最匹配Zone(最长DNS后缀)的服务器
- 遍历匹配服务器的插件链,遍历的顺序就是配置文件中插件声明的顺序
- 每个插件都会检查DNS查询请求,判断自己是否应当处理它。某些插件支持基于查询的名称或者其它属性进行过滤匹配。插件会执行以下操作之一
- 处理请求 —— 为客户端提供响应
- 不处理请求
- 不处理请求,但是将请求传递给下一个插件(fallthrough)
- 处理请求,并为请求添加一个hint,后续插件会继续处理请求,但是当前插件有机会对最终的响应进行后续的操作
有一类特殊的插件,它们不去处理DNS请求,但是会影响CoreDNS的行为。例如bind插件决定CoreDNS绑定哪些网络接口。
每个插件由三个部分组成:
- Setup:解析配置文件,以及插件指令(Directives)
- Registration:在编译期间,注册到CoreDNS中
- Handler:处理查询的逻辑所在
选项 | 说明 |
-conf | 指定配置文件,如果不指定使用当前目录的Corefile |
-cpu | CPU用量限制,默认100% |
-pidfile | PID文件路径 |
-plugins | 列出可用的插件 |
-quiet | 安静模式 |
-version | 显示版本 |
-dns.port | 指定默认端口 |
以 #开头的行为注释。
你可以在Corefile的任何地方使用环境变量,格式 {$ENV_VAR}或者 {%ENV_VAR%}。在解析Corefile时,环境变量被自动替换为实际的值。
利用import插件,可以定义复用的配置片段:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 定义一个片段 (snip) { prometheus log errors } . { whoami # 导入片段 import snip } |
Corefile中可以定义多个服务器,每个服务器的配置格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 每个服务器以它权威负责(Authoritative)的Zone开头 # 多个Zone以空格分隔 # # 可以为每个Zone指定它支持的协议: # dns:// 原始DNS协议,默认值 # tls:// DNS over TLS # grpc:// DNS over gRPC # # 可以为Zone指定端口,默认53 [PROTOCOL]ZONE[:PORT] [ZONE1 ZONE2] { } # 示例,点号表示此服务器处理任何请求 . { } |
在每个服务器内部,可以定义0-N个插件组成的插件链:
1 2 3 4 5 6 7 8 9 10 |
ZONE { # 简单的插件,配置一些指令 PLUGIN [DIRECTIVE1] [DIRECTIVE2] # 复杂插件,具有更多的配置项,定义为插件块 PLUGIN { } } |
指定服务器监听的网络接口(IP地址): bind ADDRESS ...
示例:
1 2 3 4 5 6 7 8 |
. { bind 127.0.0.1 ::1 } . { bind 127.0.0.1 bind ::1 } |
允许服务器端进行的搜索后缀(search domain)补全。如果插件发现客户端查询的名称,匹配search path的第一个元素,则自动遍历search path中的domain链,并返回第一个非NXDOMAIN结果。如果出现失败,则返回原始查询的应答。
由于autopath的应答中的名称,和原始问题不匹配,因此他会在CoreDNS中添加一个CNAME,从原始名称指向应答中的名称。
格式:
1 2 3 4 |
# ZONE autopath权威负责的Zone # RESOLV-CONF 包含search domain的配置文件。或者指向其它插件,例如@kubernetes,这时从其它插件读取search domain # 配置文件中必须有 search domain1 domain2 ... 这样的行 autopath [ZONE...] RESOLV-CONF |
实现前端缓存,用于查询后端(Upstream、Database...)成本较高的场景。启用此插件后,除了Zone transfers / Metadata以外的记录会被缓存默认3600s。
格式:
1 2 3 4 5 6 7 8 9 |
# TTL 缓存有效期,单位秒,默认3600 # ZONES 哪些Zone支持缓存 cache [TTL] [ZONES...] { # 成功的DNS应答的缓存配置 success CAPACITY [TTL] [MINTTL] # Denial of existence应答的缓存配置 denial CAPACITY [TTL] [MINTTL] prefetch AMOUNT [[DURATION] [PERCENTAGE%]] } |
能够检测简单的forwarding循环并终止服务器。
能够从Panic中恢复,用于调试用途
启用错误日志记录,格式:
1 2 3 4 |
errors { # 在DURATION期间抓取匹配REGEXP的错误日志,聚合为单条日志 consolidate DURATION REGEXP } |
示例:
1 2 3 4 5 6 |
. { errors { consolidate 5m ".* i/o timeout$" consolidate 30s "^Failed to .+" } } |
从Etcd V3中读取Zone数据。Etcd中的数据必须编码为SkyDNS风格的消息 。格式:
1 2 3 4 5 6 7 8 |
etcd [ZONES...] { fallthrough [ZONES...] path PATH endpoint ENDPOINT... credentials USERNAME PASSWORD upstream [ADDRESS...] tls CERT KEY CACERT } |
将DNS请求转发给上游DNS服务器,支持DNS / TCP / DNS over TLS。该插件代替原先的proxy插件。
格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# FROM 匹配此后缀的DNS查询会被转发 # TO 上游服务器的端点,支持指定协议,例如tls://9.9.9.9 forward FROM TO... { # 空格分隔的,不进行转发的域名列表 except IGNORED_NAMES... # 强制基于TCP协议 force_tcp # 优先使用UDP prefer_udp # 多久后丢弃连接, expire DURATION # 判定为不健康需要的连续失败辞书 max_fails INTEGER # DNS over TLS配置 tls CERT KEY CA tls_servername NAME # 选取上游服务器的算法,默认random policy random|round_robin|sequential # 健康检查周期 health_check DURATION } |
直接转发,用于兜底的示例:
1 |
forward . 223.5.5.5 1.1.1.1 |
使用TLS的示例:
1 2 3 4 |
forward . tls://9.9.9.9 { tls_servername dns.quad9.net health_check 5s } |
强制TCP转发:
1 2 3 4 5 |
svc.k8s.gmem.cc { forward . 127.0.0.1:5353 { force_tcp } } |
从文件中读取上游DNS:
1 |
forward . /etc/resolv.conf |
如果CoreDNS运行在K8S中,则CoreDNS的Pod的/etc/resolv.conf内容的基础,取决于kubelet的--resolv-conf配置,默认值指向宿主机的/etc/resolv.conf文件。
首次转发,随机选取一个上游服务器,后续一直使用,直到它不健康了。
当出现一个错误 —— 任何DNS响应都不看作错误(REFUSED, NOTIMPL, SERVFAIL ... )—— 则CoreDNS启动健康检查循环(默认0.5s一次),直到上游服务器恢复健康。
如果max_fails设置为0则不进行健康检查,总是认为上游服务器是健康的。
CoreDNS不向不健康的上游服务器转发请求,如果所有上游服务器都不健康,则随机选取一个转发。
启用进程级别的监控检查。示例: health :8080,你可以访问:8080/health获取健康状态。
提供readiness探针。示例: ready localhost:8091
以/etc/hosts风格提供Zone数据,格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# FILE 从文件中读取Zone数据,默认从/etc/hosts读取 # ZONES 此插件的权威Zone hosts [FILE [ZONES...]] { # 内联的HOSTS条目 [INLINE] # 修改生成的DNS记录的DNS TTL ttl SECONDS # 禁止自动生成in-addr.arpa或ip6.arpa条目 no_reverse # 重新载入文件的间隔,例如 300ms 1.5h 2h45m reload DURATION # 对于匹配的ZONES,如果此插件没有记录,则由下一个插件处理 fallthrough [ZONES...] } |
示例:
1 2 3 4 |
hosts { 172.21.0.1 kdc-1 fallthrough } |
该插件有两个用途:
- 导入其它配置文件到主配置文件
- 导入配置片段
格式: import PATTERN
记录查询日志,格式:
1 2 3 4 5 6 7 |
# NAMES 匹配的DNS查询被记录 # FORMAT 日志格式, log [NAMES...] [FORMAT] log [NAMES...] [FORMAT] { class CLASSES... } |
指定哪些RCode会被记录
取值 | 说明 |
success |
记录成功的请求 |
denial | 记录NXDOMAIN的请求、NOERROR但是没有数据的(nodata,域名存在,但是请求的记录类型没有) |
error | 记录SERVFAIL、NOTIMP、REFUSED等等,任何提示远程服务器不愿意解析请求的应答都包含在内 |
all | 默认,记录所有请求 |
日志格式,默认格式为:
1 2 |
{remote}:{port} - {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration} |
详细请参考官方文档。
执行内部的消息重写。格式:
1 2 3 4 5 6 7 |
# FIELD 请求/应答的什么字段需要被重写 # type 请求的TYPE字段 # class 消息的类型 # name 请求中的DNS名称 # answer name 应答中的DNS名称 # ttl TTL值 rewrite [continue|stop] FIELD [FROM TO|FROM TTL] |
要重写DNS请求中的名称,使用格式:
1 |
rewrite [continue|stop] name [exact|prefix|suffix|substring|regex] STRING STRING |
示例:
1 2 3 |
rewrite name substring k8s.gmem.cc k8s.gmem.cc rewrite name regex (.*)\.gmem\.cc {1}.gmem.cc rewrite name suffix .gmem.cc. .gmem.cc. |
要重写DNS响应中的名称,参考:
1 2 3 4 |
rewrite stop { name regex (.*)\.gmem\.site {1}.gmem.cc answer name (.*)\.gmem\.site {1}.gmem.cc } |
提供一个模板,基于请求来动态的生成响应。格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# CLASS 查询分类,IN或ANY # TYPE 查询类型,A、PTR... ANY匹配所有类型 # ZONE 此模板的Zone template CLASS TYPE [ZONE...] { # 匹配请求DNS名称的正则式 match REGEX... # DNS应答 answer RR additional RR authority RR rcode CODE # 用于解析CNAMEs的上有集群 upstream fallthrough [ZONE...] } |
使用template插件可以实现泛域名解析,下面是一个例子:
1 2 3 4 5 |
template IN A mesh.gmem.cc { match .*\.mesh\.gmem\.cc answer "{{ .Name }} 60 IN A 10.0.11.11" fallthrough } |
当前版本有一个奇怪的行为: 上述配置导致hosts插件指定的10.0.11.1 mesh.gmem.cc条目失效,必须指定fallthrough才能避免此问题。
需要注意fallthrough不能包含集群域名后缀:
1 2 3 4 |
kubernetes k8s.gmem.cc{ # 注意这种配置 fallthrough k8s.gmem.cc } |
否则会出现DNS解析错误,在Pod里面解析chartmuseum.gmem.cc,会自动添加Search domain导致解析出错:
1 2 |
[ERROR] plugin/errors: 2 chartmuseum.gmem.cc.devops.svc.k8s.gmem.cc. A: plugin/proxy: no next plugin found [ERROR] plugin/errors: 2 chartmuseum.gmem.cc.svc.k8s.gmem.cc. AAAA: plugin/proxy: no next plugin found |
Leave a Reply