CoreDns 介绍和源码解析

5,930 阅读5分钟

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,
    }
  1. 每个{} 代表一个zone,格式是 “Zone:port{}”, 其中"."代表默认zone,
  2. {}内的每个名称代表插件的名称,只有配置的插件才会启用
    当解析域名时,会先匹配zone(都未匹配会执行默认zone),然后zone内的插件从上到下依次执行(这个顺序并不是配置文件内谁在前面的顺序,而是core/dnsserver/zdirectives.go内的顺序),匹配后返回处理(执行过的插件从下到上依次处理返回逻辑),不再执行下一个插件

插件

插件的文档说明请参考: coredns.io/plugins

性能

官方测试,2core 1g的资源消化下,单实例能够达到30k qps 根据官方测试报告,在2k pod下,qps预计不超过30k的环境,coredns 配置为2core,1g即可(我给估算大了一些,更稳定一些)

参考: github.com/coredns/dep…

源码解析

源码架构
CoreDns 基于Caddy框架编写,上图是大概的代码架构,简单说明如下(其实看图结合源码基本就能看懂了):

  1. 首先main方法在coredns.go内,因为这个文件导入了core/plugin.go,而core/plugin.go内导入了所有插件,所以会执行所有插件的init方法,每个插件的init方法功能都一样,就是把自己注册为caddy中名为"dns" 的server的plugin

  2. 然后main 方法内就一行,运行coremain/run.go内的Run方法,因为这个文件导入了core/dnsserver/register.go,因此会执行该文件的init方法,该方法通过caddy.RegisterServerType 注册了"dns"类型的sever的context,这里有两个参数,一个是Directives,是个string列表,包含了所有的插件名称,还有一个newContext,是初始化context的

  3. coremain/run.go内的Run方法内开始执行caddy.LoadCaddyfile 加载配置文件,然后执行caddy.start方法,这个方法内会进行一系列初始化,主要会处理1,2注册的server

    1. 根据第二步中的newContext初始化dnsContext
    2. 其次会执行caddy内部的ValidateAndExecuteDirectives方法,该方法的主要是根据第二步注册的Directives和配置文件 依次调用配置文件启用的每个插件的setup(),把启用的插件注册为dnsContext.Plugin
    3. 然后执行Context.InspectServerBlock()和Context.MakerServers(),即coremain/dnsserver/register.go内的dnsContext的两个方法,起作用分别是加载配置文件,注册pluginChain并返回该Server
    4. 调用Server的Lisen或ListenPacket方法,即core/dnsserver/server.go内的Server两个方法,启动TCP或者UDP监听
    5. 调core/dnsserver/server.go内Server或ServePacket方法注册处理TCP或者UDP请求的Handle,这两个方法都会注册ServeDNS方法为Handle
  4. 执行caddy.Wait()主进程进入等待,caddy开始处理请求,这里每个请求都会发送给core/dnsserver/server.go的ServeDNS处理,这里是dns的总逻辑

    1. 遍历 dns.NextLabel(dns),找到匹配的zone,都未匹配则尝试默认zone(".")
    2. 调用zone.pluginChain.ServeDNS()进行处理,pluginChain是一个链,链的顺序是core/dnsserver/zdirectives.go 定义的,最前的最先执行。
  5. 每个插件的ServeDNS()会进行如下处理

    1. 进行一些插件自定义处理
    2. 调用plugin.NextOrFailure()执行下个插件的处理(当然也可以不调用直接返回)
    3. 进行一些插件自定义处理

每个插件结构

如上分析,每个插件的结构基本会提供如下方法

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目录内,逻辑如下:

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

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

参考:

zhengyinyong.com/coredns-plu… ckchang.cn/2019/03/29/…