本手册正在编制中!欢迎提供帮助!如果您想帮助,请参阅 本教程的来源 。您可以通过发送拖拽请求或针对您希望在此获得解决的具体内容、任何缺失内容或仅是令人困惑的部分提交 问题 来提供帮助。
– CoreDNS 作者
目录
什么是 CoreDNS?
CoreDNS 是一种 DNS 服务器。它用 Go 语言编写。
CoreDNS 不同于其他 DNS 服务器,如(均为出色服务器)BIND、Knot、PowerDNS 和 Unbound(技术上是一个解析器,但值得一提),因为它非常灵活,且几乎所有功能都外包给插件。
插件可以独立存在或协同工作以执行“DNS 功能”。
那么,“DNS 功能”是什么?在 CoreDNS 中,我们将其定义为一个实现 CoreDNS 插件 API 的软件。实施的功能可以有很大差别。有些插件不会自己创建响应,例如 度量 或 缓存,但它们会添加功能。然后还有一些插件确实会生成响应。这些插件还可以执行任何操作:有些插件会与 Kubernetes 通信以提供服务发现,有些插件会从 文件 或 数据库 读取数据。
目前默认 CoreDNS 安装包含大约 30 个插件,但还有一大批 外部 插件,你可以将它们编译到 CoreDNS 中以扩展其功能。
CoreDNS 由插件提供支持。
编写新的 插件 应该相当容易,但这需要了解 Go 语言以及对 DNS 工作原理有所了解。CoreDNS 提取了许多 DNS 详细信息,因此你可以专注于编写需要的插件功能。
安装
CoreDNS 用 Go 语言编写,但除非你想开发插件或自己编译 CoreDNS,否则你可能不在乎。以下部分详细介绍如何获取 CoreDNS 二进制文件或从源安装。
二进制文件
对于每个 CoreDNS 版本,我们都会为各种操作系统提供 预编译二进制文件 。对于 Linux,我们还为 ARM、PowerPC 和其他架构提供交叉编译二进制文件。
Docker
我们也以 Docker 映像推送每个版本。你可以在 CoreDNS 组织的公共 Docker 中枢中找到它们。该 Docker 映像基本是scratch + CoreDNS + TLS 证书(用于 DoT、DoH 和 gRPC)。
源代码
要编译 CoreDNS,我们假设你已建立了正常运行的 Go 设置。如果没有配置该设置,请参见各种教程。CoreDNS 使用 Go 模块进行依赖关系管理。
编译事物的最新文档保存在coredns 源代码中。
测试
一旦有了coredns
二进制文件,你可以使用-plugins
标志列出所有已编译的插件。如果没有Corefile
(请参见配置),CoreDNS 将加载whoami插件,该插件将使用客户端的 IP 地址和端口号做出响应。因此,要进行测试,我们将启动 CoreDNS 以在端口 1053 上运行,然后使用dig
向其发送查询
$ ./coredns -dns.port=1053
.:1053
2018/02/20 10:40:44 [INFO] CoreDNS-1.0.5
2018/02/20 10:40:44 [INFO] linux/amd64, go1.10,
CoreDNS-1.0.5
linux/amd64, go1.10,
并且在一个不同的终端窗口中,dig
应当返回类似于这样的结果
$ dig @localhost -p 1053 a whoami.example.org
;; QUESTION SECTION:
;whoami.example.org. IN A
;; ADDITIONAL SECTION:
whoami.example.org. 0 IN AAAA ::1
_udp.whoami.example.org. 0 IN SRV 0 0 39368 .
下一个部分将展示如何启用更有意思的插件。下一部分
插件
一旦启动 CoreDNS 并且解析了配置,它将运行服务器。每个服务器都通过其所服务的区域和端口来定义。每个服务器都有自己的插件链。
当 CoreDNS 处理查询时,将执行以下步骤
-
如果配置了监听查询端口的多个服务器,它将检查哪一个为该查询提供了最具体的区域(最长后缀匹配)。例如,如果存在两个服务器,一个用于
example.org
,另一个用于a.example.org
,并且查询是www.a.example.org
,它将路由到第二个服务器。 -
一旦找到服务器,它将通过为该服务器配置的插件链进行路由。这始终按相同顺序发生。该(静态)排序在
plugin.cfg
中定义。 -
每个插件都将检查查询并确定是否应处理(一些插件允许你根据查询名称或其他属性进一步过滤)。现在可能发生一些事情
- 查询由该插件处理。
- 查询未由该插件处理。
- 查询由该插件处理,但在中途时决定仍要调用链中的下一个插件。在启用该关键词之后,我们将其称为贯通。
- 查询由该插件处理,添加“提示”,然后调用下一个插件。此提示提供了一种“查看”最终的响应并采取措施的方式。
处理查询意味着插件将使用回复向客户端做出响应。
请注意,插件可以根据自己的意愿从上述列表中进行偏离。但是,目前 CoreDNS 附带的所有插件都属于这四组中的其中一组。请注意,这篇博文也提供了有关查询路由的背景信息。
处理查询
插件处理查询。它查找(或生成,或插件作者决定此插件执行的任何操作)一个响应并将其发送回客户端。查询处理在此停止,不会调用下一个插件。whoami是一个以这种方式工作(简单)的插件。
不处理查询
如果插件决定不处理查询,它只会调用链中的下一个插件。如果链中的最后一个插件决定不处理查询,CoreDNS 会将 SERVFAIL 返回给客户端。
以 Fallthrough 处理查询
在这种情况中,插件会处理查询,但它从后端得到的答复(即,它可能得到了 NXDOMAIN)要求链中的其他插件也查看一下。如果提供了(并启用了)fallthrough,则会调用下一个插件。hosts插件的工作方式就是如此。首先,会尝试在主机表(/etc/hosts
)中进行查找,如果找到答案,则会将其返回。如果没有,它将fallthrough到下一个,希望其他插件可以向客户端返回某些内容。
以提示处理查询
此类插件将处理查询,并且始终调用下一个插件。但是,它提供了一个提示,允许它查看将写入到客户端的响应。prometheus插件就是这样一种插件。它对持续时间(以及其他内容)进行计时,但实际上并没有对 DNS 请求执行任何操作 - 它只会对其进行传递并在返回路径上检查一些元数据。
未注册插件
还有另一种特殊类别的插件,它们根本不处理任何 DNS 数据,但会以其他方式影响 CoreDNS 的行为。例如,控制 CoreDNS 应绑定到哪些接口的bind插件。以下插件属于此类别
- bind - 如前所述,控制要绑定的接口。
- root - 设置 CoreDNS 插件应在其中查找文件的根目录。
- health - 启用 HTTP 健康检查端点。
- ready - 为插件支持就绪状况报告。
插件的结构
插件由设置、注册和处理程序部分组成。
设置解析配置和插件的指令(这些应在插件的自述文件中记录)。
处理程序是处理查询并实施所有逻辑的代码。
注册部分将在 CoreDNS 中注册插件 - 这会在编译 CoreDNS 时发生。所有已注册的插件均可被服务器使用。在运行时于 CoreDNS 配置文件中(Corefile 中)对在各个服务器中配置哪个插件做出决策。
插件文档
各个插件均有自己的自述文件,详述了如何对其进行配置。此自述文件包括示例以及有待用户知晓的其他信息。各个自述文件均最终显示在 https://coredns.golang.ac.cn/plugins 中,我们还将它们编译成 手册页。
配置
可以在 CoreDNS 中配置多个内容。首先需要确定要将哪些插件编译到 CoreDNS 中。我们提供的二进制文件具有 plugin.cfg
中列出的所有插件,已编译其中。添加或删除 很容易,但需要重新编译 CoreDNS。
因此,大多数用户使用 Corefile 配置 CoreDNS。在 CoreDNS 启动并且未给出 -conf
标志的情况下,它将在当前目录中查找名为 Corefile
的文件。该文件由一个或多个 Server 块组成。各个 Server 块列出一种或多种插件。这些插件可以使用指令进一步进行配置。
Corefile 中插件的排列顺序并不会确定插件链的顺序。执行插件的顺序由 plugin.cfg
中的排列顺序决定。
Corefile 中的注释以 #
开始。行中的其余部分然后被视为注释。
环境变量
CoreDNS 支持在配置中进行环境变量替换。它们可以在 Corefile 中的任何位置使用。语法为 {$ENV_VAR}
(还可以使用更为类似于 Windows 的语法 {%ENV_VAR%}
)。CoreDNS 会在解析 Corefile 时替换变量的内容。
导入其他文件
参见 import 插件。此插件有点特别,在于可以在 Corefile 中的任何位置使用它。
可重用代码段
导入文件的特殊案例被称为代码段。通过使用特殊语法对块命名来定义代码段。此名称必须放置在括号中:(name)
。然后,可以通过import 插件将它包含在配置的其他部分中。
# define a snippet
(snip) {
prometheus
log
errors
}
. {
whoami
import snip
}
服务器块
各个服务器块以 Server 应管理的区域开始。在区域名称或区域名称列表(用空格分隔)之后,使用左大括号打开服务器块。使用右大括号关闭服务器块。以下服务器块指定了对根区域以下所有区域负责的服务器:.
;从根本上说,此服务器应处理每一个可能出现的查询。
. {
# Plugins defined here.
}
服务器块可以选择指定侦听的端口号。默认值为端口 53(DNS 的标准端口)。可以通过在冒号分隔的区域后列出端口来指定端口。此 Corefile 指示 CoreDNS 创建侦听端口 1053 的服务器
.:1053 {
# Plugins defined here.
}
注意:如果您为服务器明确定义了侦听端口,则不能使用
-dns.port
选项覆盖它。
使用已分配给服务器且在同一端口上运行的区域来指定服务器块出错。此 Corefile 将在启动时生成错误
.:1054 {
}
.:1054 {
}
将第二个端口号更改为 1055 会使这些服务器块变为两个不同的服务器。注意,如果您使用 bind 插件,则可以使同一区域侦听同一端口,前提是它们绑定到不同的接口或 IP 地址。此处使用的语法自 CoreDNS 1.8.4 起受到支持。
.:1054 {
bind lo
whoami
}
.:1054 {
bind eth0
whoami
}
将在启动时打印类似以下内容
.:1054 on ::1
.:1054 on 192.168.86.22
.:1054 on 127.0.0.1
指定协议
目前 CoreDNS 接受四种不同的协议:DNS、DNS over TLS (DoT)、DNS over HTTP/2 (DoH) 和 DNS over gRPC。您可以通过使用方案为区域名称打前缀来在服务器配置中指定服务器应接受的内容。
- 用于普通 DNS 的
dns://
(如果未指定方案,则为默认值)。 - 用于 DNS over TLS 的
tls://
,请参见 RFC 7858。 - 用于 DNS over HTTPS 的
https://
,请参见 RFC 8484。 - 用于 DNS over gRPC 的
grpc://
。
插件
每个服务器块指定应为此特定服务器链接的插件数量。在其最简单的形式中,您只需在服务器块中使用插件的名称即可添加插件
. {
chaos
}
chaos 插件使 CoreDNS 在 CH 类中回答查询 - 这对于识别服务器很有用。使用上述配置,当收到请求时,CoreDNS 将回答其版本
$ dig @localhost -p 1053 CH version.bind TXT
...
;; ANSWER SECTION:
version.bind. 0 CH TXT "CoreDNS-1.0.5"
...
大多数插件允许使用指令进行更多配置。在 chaos 插件的情况下,我们可以像在语法中所示的那样指定 VERSION
和 AUTHORS
语法
chaos [VERSION] [AUTHORS...]
- VERSION 是要返回的版本。如果没有设置,默认为
CoreDNS-<version>
。- AUTHORS 是要返回的作者。默认是 OWNER 文件中指定的所有人。
因此,这会向 chaos 插件添加一些指令,使 CoreDNS 将以 CoreDNS-001
作为其版本进行响应
. {
chaos CoreDNS-001 [email protected]
}
具有更多配置选项的其他插件具有插件块,它就像服务器块一样,用一个开始大括号和一个结束大括号括起来
. {
plugin {
# Plugin Block
}
}
我们可以结合所有这些,并使用以下 Corefile,它设置了在两个不同端口上提供服务的 4 个区域
coredns.io:5300 {
file db.coredns.io
}
example.io:53 {
log
errors
file db.example.io
}
example.net:53 {
file db.example.net
}
.:53 {
kubernetes
forward . 8.8.8.8
log
errors
cache
}
CoreDNS 解析后,将导致以下设置
外部插件
外部插件是未编译到默认 CoreDNS 中的插件。你可以轻松启用它们,但你需要自行编译 CoreDNS。
可能的错误
health 插件的文档中说:“此插件只需要启用一次”,这可能会让你认为这是一个有效的 Corefile
health
. {
whoami
}
但它不起作用,并导致一个有些难以理解的错误
"Corefile:3 - Error during parsing: Unknown directive '.'".
这里发生了什么?将 health
视为一个区域(和服务器块的开始)。解析器期望看到插件名(cache
、etcd
等),但在这种情况下,下一个标记是 .
,它不是插件。Corefile 应按如下方式构建
. {
whoami
health
}
health 插件文档中的那一行意味着一旦指定了 health,它对于整个 CoreDNS 流程来说都是全局的,即使你仅为一个服务器指定了它。
设置
在这里,你可以找到 CoreDNS 的一堆配置。所有设置都假设你不是 root 用户,因而无法在端口 53 上开始监听。我们将使用端口 1053,使用 -dns.port
标志。在每个设置中,使用的配置文件都是 CoreDNS 的默认文件,名为 Corefile
。这意味着我们不需要使用 -conf
标志指定配置文件。换句话说,我们使用 ./coredns -dns.port=1053 -conf Corefile
启动 CoreDNS,可简写为 ./coredns -dns.port=1053
。
所有 DNS 查询都将使用 dig
工具生成,这是调试 DNS 的黄金标准。我们在使用的是完整的命令行
$ dig -p 1053 @localhost +noall +answer <name> <type>
但我们在下面的设置中将其缩短,所以 dig www.example.org A
实际上是 dig -p 1053 @localhost +noall +answer www.example.org A
从文件中进行权威提供服务
此设置使用 file 插件。请注意,外部 redis 插件允许从 Redis 数据库提供权威服务。让我们使用 file 继续进行设置。
我们在创建的文件是 DNS 区域文件,它可以有任何名称(file 插件不在乎)。我们放入文件的数据适用于区域 example.org.
。
在你的当前目录中,创建一个文件,将其命名为 db.example.org
,并在其中放入以下内容
$ORIGIN example.org.
@ 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. (
2017042745 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ; minimum (1 hour)
)
3600 IN NS a.iana-servers.net.
3600 IN NS b.iana-servers.net.
www IN A 127.0.0.1
IN AAAA ::1
最后两行定义了一个名称 www.example.org.
,有两个地址,127.0.0.1 和 (IPv6) ::1。
接下来,创建此最小的 Corefile
,它处理对该域的查询并添加 log 插件以启用查询日志记录
example.org {
file db.example.org
log
}
启动 CoreDNS 并使用 dig
查询它
$ dig www.example.org AAAA
www.example.org. 3600 IN AAAA ::1
它起作用了。由于使用了 log 插件,我们还应该看到已记录的查询
[INFO] [::1]:44390 - 63751 "AAAA IN www.example.org. udp 45 false 4096" NOERROR qr,aa,rd,ra 121 0.000106009s
上述日志向我们显示了 CoreDNS 答复的地址 (::1
) 及其答复时间和日期。此外,它会记录查询类型、查询类、查询名称、使用的协议 (udp
)、传入请求的字节大小、DO 位状态和通告的 UDP 缓冲区大小。这是传入查询中的数据。NOERROR
发送响应代码,表示答复开始,随后是一组答复标志:qr、aa、rd、ra
、答复的字节大小 (121) 及获取答复所需的持续时间。
转发
可以使用 forward 将 CoreDNS 配置为将流量转发到递归解析器。在这里,我们将使用 forward,重点关注最基本的设置:转发到 Google Public DNS (8.8.8.8) 和 Quad9 DNS (9.9.9.9)。
除了带有所需配置的 Corefile 外,我们不必创建任何内容。在这种情况下,我们希望所有查询通过 CoreDNS 后转发到 8.8.8.8 或 9.9.9.9。
. {
forward . 8.8.8.8 9.9.9.9
log
}
请注意,forward 允许您微调将发送到上游的名称。在这里,我们选择了所有名称 (.
)。例如:forward example.com 8.8.8.8 9.9.9.9
仅会转发 example.com.
域内的名称。
开始使用 CoreDNS,并使用 dig
对其进行测试
$ dig www.example.org AAAA
www.example.org. 25837 IN AAAA 2606:2800:220:1:248:1893:25c8:194
将不同域转发到不同上游
您可能会遇到的常见情况是 example.org
查询需要发送至 8.8.8.8,而其余的应通过 /etc/resolv.conf
中的名称服务器解析。可以在 Corefile 中实现两种方式
- 在单个服务器块内多次声明 forward 插件
. {
forward example.org 8.8.8.8
forward . /etc/resolv.conf
log
}
- 使用多个服务器块,每个块用于您要路由的域之一
example.org {
forward . 8.8.8.8
log
}
. {
forward . /etc/resolv.conf
log
}
这会将域路由留给 CoreDNS,后者还会处理 DS 查询等特殊情况。将两个较小的服务器块替换为一个较大的服务器块并不会带来负面影响,只有一点需要注意,即您的 Corefile 会略长。片段和 import 等功能会提供帮助。
递归解析器
CoreDNS 没有一个本机(即以 Go 语言编写)递归解析器,但有一个(外部)插件与 libunbound 配合使用。要让此设置起作用,您首先必须重新编译 CoreDNS,并 启用 unbound 插件。这里有一个超级快速的入门 (您必须安装 CoreDNS 源代码)。
- 将
unbound:github.com/coredns/unbound
添加到plugin.cfg
中。 - 执行
go generate
,然后执行make
。
注意:unbound 插件需要编译 cgo,这也意味着 coredns 二进制文件现在与 libunbound 链接,而不是静态二进制文件。
假设此设置有效,那么您可以使用以下 Corefile 启用 unbound
. {
unbound
cache
log
}
我们已包含 cache,因为 unbound 的(内部)缓存已禁用,以便缓存指标能够正常运作。
编写插件
如本手册之前提到的那样,插件是让 CoreDNS 运行的要素。我们已在上一部分中看到了大量配置,但如何编写您自己的插件呢?
请查阅为 CoreDNS 编写插件,了解该主题的较早一篇帖子。CoreDNS 源代码中记录的plugin.md也提供了一些背景信息,并讨论了 README.md 的样式。
规范示例插件为示例插件。其GitHub 代码库展示了创建插件所需的极简代码(以及测试!)。
它具有
setup.go
和setup_test.go
,它们实现了从 Corefile 中解析配置。通常命名的setup
函数在 Corefile 解析器看到插件名称时被调用;在本例中,为“example”。example.go
(通常命名为<plugin_name>.go
),其中包含处理查询的逻辑和example_test.go
,其中包含一些单元测试,用于检查该插件是否工作。README.md
以 Unix 手册样式记录了如何配置此插件。- 一个 LICENSE 文件。对于在 CoreDNS 中加入,它需要具有类似 APL 的许可证。
该代码还具有大量注释;您可以随意分叉它,并基于它为您的插件打基础。
如何调用插件
当 CoreDNS 要使用插件时,它会调用 ServeDNS
方法。ServeDNS
有三个参数
- 一个
context.Context
; - 一个
dns.ResponseWriter
,它基本上是客户端连接; - 一个
*dns.Msg
,它是来自客户端的请求。
ServeDNS
返回两个值:一个(响应)代码和一个错误。当此服务器中使用错误时,会记录该错误。
代码告诉 CoreDNS 插件链是否已写入回复。在后一种情况下,CoreDNS 会处理该内容。对于该代码的值,我们重复使用 dns 包中的 DNS 返回代码 (rcodes)。
CoreDNS 处理
- SERVFAIL (dns.RcodeServerFailure)
- REFUSED (dns.RcodeRefused)
- FORMERR (dns.RcodeFormatError)
- NOTIMP (dns.RcodeNotImplemented)
为特殊内容,然后将不向客户端写入任何内容。在所有其他情况下,它假定已向客户端(通过插件)写入一些内容。
请参阅此帖子,了解如何使用插件编译 CoreDNS。
从插件进行日志记录
使用日志包为插件添加日志记录。使用以下内容对其进行初始化
var log = clog.NewWithPlugin("example")
现在,您可以log.Infof("...")
,将内容打印到标准输出,并使用级别[INFO] plugin/example
作为前缀。级别可以是:INFO
、WARNING
或 ERROR
。
一般情况下,返回错误时应将日志记录交给较高层。不过,如果有必要使用错误但仍会通知用户,则在插件中记录日志是可接受的。
指标
导出指标时,命名空间应为 plugin.Namespace
(=“coredns”),子系统应为插件的名称。插件的 README.md 也应包含一个指标部分,详细说明指标。如果插件支持就绪性报告,还应附带一个就绪性部分,对其进行详细说明。
文档
每个插件应有一个 README.md 解释插件的功能及配置方法。该文件应采用以下布局
- 标题:使用插件的名称
- 标题为:“Named”的子部分,内容为
<plugin name> - <one line description>.
,即 NAME DASH DESCRIPTION DOT。 - 标题为:“Description”的子部分,其中附有更长的描述以及插件支持的所有选项。
- 标题为:“Syntax”的子部分,其中详细介绍语法和支持的指令。
- 标题为:“Examples”的子部分。
- 标题为:“See Also”的可选子部分,该部分引用外部文档,如 IETF RFC。
- 标题为:“Bugs”的可选子部分,该部分列出尚未解决的问题。
当然也可以有更多部分。
样式
我们采用 Unix 手册页样式
- 正文中插件的名称应为斜体:
*plugin*
。 - 正文中所有采用大写字母的用户提供的参数均使用粗体:
**EXAMPLE**
。 - 可选文本采用区块引用:
[optional]
。 - 用三个点表示允许有多个选项:
arg...
。 - 直接使用的项目:
literal
。
域名示例
请务必在提供的任何示例和测试中使用 example.org
或 example.net
。这是为此目的创建的标准域名。如果您不这样做,您的虚拟域名就有可能被某人注册,并且实际上会提供 Web 内容(您可能喜欢或不喜欢)。
Fallthrough
在理想的世界中,以下内容将对插件适用:“要么您负责一个区域,要么不负责”。如果答案是“不负责”,则插件应调用链中的下一个插件。如果答案是“负责”,则插件应处理位于此区域和以下名称中的所有名称,即插件应处理整个域和所有子域,还可在此处查看如何使用启用 fallthrough
的查询过程。
. {
file example.org db.example
}
在此示例中,file 插件正在处理(包括)example.org
以下的所有名称。如果查询不是子域(或等于)example.org
,则会调用下一个插件。
不过,这个世界并不完美,有充分的理由“fallthrough”到下一中间件,这意味着一个插件仅负责区域中名称的子集。最早出现的第一个是reverse 插件,如今已被泛化的template 插件取代,后者可以综合各种响应。
template 插件的性质可能仅适用于指定的记录 TYPE,然后仅限于名称的子集。理想情况下,你应该将 template 分层为 file 或 auto 等其他插件前面。这意味着 template 可以处理一些特别的逆向情况,而所有其他请求都由支持插件处理。这正是 “fallthrough” 所做的。为了使事情更明确,我们已经选择实现这种行为的插件应该实现一个 fallthrough
关键字。
fallthrough
指令可选接受区域列表。只有针对那些区域中某个记录的查询才允许 fallthrough。
参加主仓库的资格
查看此文档描述要求。