CoreDns 是用 GO 写的高性能,高扩展性的DNS服务。旨在解决Kube-dns目前的一些问题 CoreDns 内部采用插件机制,所有功能都是插件形式编写,用户也可以扩展自己的插件
如下,是Kubernetes 部署CoreDns时默认配置
.:53 {
errors # 启用错误记录
health # 启用运行状况检查端点,8080/health
kubernetes cluster.local in-addr.arpa ip6.arpa { # 处理k8s 域名解析
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153 # 启用Prometheus指标。9153/metrics
forward . /etc/resolv.conf # 通过resolv.conf内的nameservers解析
cache 30 # 启用缓存,所有内容限制为30秒的TTL
loop # 检测简单的转发循环并停止服务器
reload # reload允许自动重新加载已更改的Corefile
loadbalance # 负载均衡,默认round_robin,
}
- 每个{} 代表一个zone,格式是 “Zone:port{}”, 其中"."代表默认zone,
- {}内的每个名称代表插件的名称,只有配置的插件才会启用
当解析域名时,会先匹配zone(都未匹配会执行默认zone),然后zone内的插件从上到下依次执行(这个顺序并不是配置文件内谁在前面的顺序,而是core/dnsserver/zdirectives.go内的顺序),匹配后返回处理(执行过的插件从下到上依次处理返回逻辑),不再执行下一个插件
插件
插件的文档说明请参考: coredns.io/plugins
性能
官方测试,2core 1g的资源消化下,单实例能够达到30k qps 根据官方测试报告,在2k pod下,qps预计不超过30k的环境,coredns 配置为2core,1g即可(我给估算大了一些,更稳定一些)
源码解析

-
首先main方法在coredns.go内,因为这个文件导入了core/plugin.go,而core/plugin.go内导入了所有插件,所以会执行所有插件的init方法,每个插件的init方法功能都一样,就是把自己注册为caddy中名为"dns" 的server的plugin
-
然后main 方法内就一行,运行coremain/run.go内的Run方法,因为这个文件导入了core/dnsserver/register.go,因此会执行该文件的init方法,该方法通过caddy.RegisterServerType 注册了"dns"类型的sever的context,这里有两个参数,一个是Directives,是个string列表,包含了所有的插件名称,还有一个newContext,是初始化context的
-
coremain/run.go内的Run方法内开始执行caddy.LoadCaddyfile 加载配置文件,然后执行caddy.start方法,这个方法内会进行一系列初始化,主要会处理1,2注册的server
- 根据第二步中的newContext初始化dnsContext
- 其次会执行caddy内部的ValidateAndExecuteDirectives方法,该方法的主要是根据第二步注册的Directives和配置文件 依次调用配置文件启用的每个插件的setup(),把启用的插件注册为dnsContext.Plugin
- 然后执行Context.InspectServerBlock()和Context.MakerServers(),即coremain/dnsserver/register.go内的dnsContext的两个方法,起作用分别是加载配置文件,注册pluginChain并返回该Server
- 调用Server的Lisen或ListenPacket方法,即core/dnsserver/server.go内的Server两个方法,启动TCP或者UDP监听
- 调core/dnsserver/server.go内Server或ServePacket方法注册处理TCP或者UDP请求的Handle,这两个方法都会注册ServeDNS方法为Handle
-
执行caddy.Wait()主进程进入等待,caddy开始处理请求,这里每个请求都会发送给core/dnsserver/server.go的ServeDNS处理,这里是dns的总逻辑
- 遍历 dns.NextLabel(dns),找到匹配的zone,都未匹配则尝试默认zone(".")
- 调用zone.pluginChain.ServeDNS()进行处理,pluginChain是一个链,链的顺序是core/dnsserver/zdirectives.go 定义的,最前的最先执行。
-
每个插件的ServeDNS()会进行如下处理
- 进行一些插件自定义处理
- 调用plugin.NextOrFailure()执行下个插件的处理(当然也可以不调用直接返回)
- 进行一些插件自定义处理
每个插件结构
如上分析,每个插件的结构基本会提供如下方法
init() //模版代码,把自己注册为caddy中名为"dns",参数是setup方法
setup() //初始化一个实例,注册为dnsContext.Plugin
ServeDns() //每次请求时,会执行的代码,这里是插件的功能实现的地方
loop 检查插件
这个插件就是检查dns循环的,也就是我向CoreDns查一个域名,CoreDns代理到上级dns,但上级地址还是本CoreDns,又再次查询,如此反复,这会耗费大量cpu进行无效查询 因为如果部署中配置错误造成loop检查失败就会报错
plugin/loop: Loop (127.0.0.1:55953 -> :1053) detected for zone ".", see https://coredns.io/plugins/loop#troubleshooting. Query: "HINFO 4547991504243258144.3688648895315093531."
loop的实现源码在 plugin/loop目录内,逻辑如下:
- setup.go 内的setup方法内,通过OnStartup注册了一个钩子,当启动serve的时候就会启动一个协程,会尝试执行loop.exchange方法,如果成功,执行loop.setDisabled方法(效果是停止检查,即只检查一次)

- 如下图loop.go中的exchange逻辑就是向自己(地址设置参考上图,是本dnsserver的地址)执行一个新的dns查询,查询dns是l.qname,l.qname就是生产一个随机dns(<rand.Int()>.<rand.Int().)
- 因为第二步向自己发送了查询请求,CoreDns解析请求后,会执行loop.go中ServeDns方法,如果出现了循环,请求就会再次转发到CoreDns,这里就会执行多次,这里的逻辑就是
- 如果已经disabled(检查成功后会标记为disabled),就跳过处理
- 如果名称就是我们发送的l.qname,计数加1
- 判断如果计数大于2(也就是查询出现循环了),输出日志并退出程序

因此,可以看到主要原因就是域名解析出现了循环,我们需要检查下具体产生的原因,检查问题可以参考: Troubleshooting Loops In Kubernetes Clusters
另:如果是1.2.2 版本,提示失败,也可能是pod 内网络到上游dns不通,可以
kubectl run -i -t test-network --image=busybox --restart=Never ping 你的上游dns地址
参考: