1. 概述
CoreDNS是一个Golang编写的DNS服务器,作为K8S使用的DNS组件,提供了Plugin机制,方便配置和使用,本文章主要介绍CoreDNS的基本使用,原理,Plugin的介绍
2. CoreDNS的部署
具体的安装参考: coredns.io/manual/toc/… 具体的安装方式支持如下的方式:
- 直接二进制安装,从github上下载,直接启动即可
- Docker安装,拉取镜像,采用docker run 的形式来运行
- K8S的安装方式(可以复制K8S集群安装CoreDNS的Deployment和关联的ConfigMap),之所以要分开主要是为了不影响K8S的内部DNS的解析,专门提供一个整个线上服务使用的DNS服务器
下面我们就采用最基本的二进制方式进行安装,主要朝向使用和原理分析,Plugin介绍方面发展
2.1 二进制安装方式
- 从github.com/coredns/cor… 按照自己的操作系统下载对应版本v1.10.1的CoreDNS的二进制包
- 下载下来之后进行解压
tar -zxvf coredns_1.10.1_linux_amd64.tgz
- 编写corefile文件,保存文件名字为:corefile
# 这里我们使用了CoreDNS提供的hosts插件,后续我们会介绍,hosts插件类似于本地配置hosts文件
xwwxkj.com:1053 {
hosts {
10.20.121.41 tekton.cpaas.xwwxkj.com
10.20.121.42 hubble.cpaas.xwwxkj.com
}
}
- 启动coredns程序
#coredns是刚才解压出来的二进制程序 corefile是上面创建的文件
./coredns -conf=./corefile
- 可以看到如下的输出
xwwxkj.com:1053 #定义的corefile文件的zone配置
[INFO] plugin/reload: Running configuration SHA512 = 8da315c1557a068227dd232a31d7a93b10a6b486d54f5cf41ad773dd832236c8119471c9972b014b5e1c174b8bc08210119250cb5d5cfcc676d626630d1957bc
CoreDNS-1.10.1 #CoreDNS的版本号
linux/amd64, go1.20.4, #操作系统和Golang版本信息
- 执行dig命令验证下
dig @localhost -p 1053 tekton.cpaas.xwwxkj.com
#dig命令的简单使用:
dig @DNS服务器的IP -p DNS服务器的端口(默认53) 需要解析的域名
附图(CoreDNS的Plugin处理流程):
2.2 CoreDNS的查找流程
我们以如下的命令过程来叙述CoreDNS的查询流程
dig @localhost -p 1053 tekton.cpaas.xwwxkj.com
- dig发送UDP(或者TCP类型)类型的DNS查询数据包给localhost:1053的服务
- CoreDNS接收到DNS服务请求,并且解析出来查询的域名信息
- 按照配置的zone(xwwxkj.com),来进行匹配
- 执行zone(xwwxkj.com)执行对应的插件(hosts)
- 执行hosts插件进行查找对应域名的IP
- 返回查找结果
3. CoreDNS的插件介绍
备注:CoreDNS所有的插件都会实现如下的interface(Golang)
type Handler interface {
#处理DNS服务的逻辑,不一定是查找,可以是其他任何的功能,例如日志记录等等
ServerDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
Name() string
}
3.1 hosts插件介绍
3.1.1 hosts的基本配置
example.hosts example.org {
hosts {
10.0.0.1 example.org fallthrough #类似于主机的hosts配置
}
whoami
}
3.1.2 hosts插件源码
func (h Hosts) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
#1.根据DNS请求当中的域名去查找属于哪个zone
state := request.Request{W: w, Req: r}
qname := state.Name()
answers := []dns.RR{}
zone := plugin.Zones(h.Origins).Matches(qname)
#2.如果找不到zone,就直接走下个plugin
if zone == "" {
if state.QType() != dns.TypePTR {
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
}
}
#3.按照DNS的类型进行case选择(golang的case默认加了break)
switch state.QType() {
case dns.TypePTR:
names := h.LookupStaticAddr(dnsutil.ExtractAddressFromReverse(qname))
if len(names) == 0 {
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
}
answers = h.ptr(qname, h.options.ttl, names)
#4.Debug代码看到到了这儿,进行域名的查找
case dns.TypeA:
ips := h.LookupStaticHostV4(qname)
answers = a(qname, h.options.ttl, ips)
case dns.TypeAAAA:
ips := h.LookupStaticHostV6(qname)
answers = aaaa(qname, h.options.ttl, ips)
}
if len(answers) == 0 && !h.otherRecordsExist(qname) {
if h.Fall.Through(qname) {
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
}
return dns.RcodeServerFailure, nil
}
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative = true
m.Answer = answers
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
根据域名查找的逻辑
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
host = strings.ToLower(host)
ip1 := h.lookupStaticHost(h.hmap.name4, host) # 1.从加载的本地机器的hosts查找
ip2 := h.lookupStaticHost(h.inline.name4, host) # 2. 从配置的hosts {} 查找
return append(ip1, ip2...)
}
3.1.3 hosts插件查找过程
- 进入hosts插件的ServerDNS方法,假设查找的域名是:traefik.cpaas.wxchina.com
- 使用配置的zone和域名进行后缀匹配,返回查找到的zone信息
- 如果zone为空(意思就是没有查找到),就走下个plugin,如果zone找到了,那就继续查找
- 执行LookupStaticHostV4()方法。首先是查找CoreDNS从本地机器加载的hosts里面有没有
- 如果从本地加载的hosts文件没有,那就从hosts {} 配置里面查找有没有
- 如果正常/找不到 都会走下一个plugin
3.2 log插件介绍
3.2.1 log的基本配置
log的输出包含两个方面:
- 输出的日志格式
- 输出哪些类型的日志信息(denial/success/error),分别是:DNS请求拒绝/DNS请求成功/DNS请求失败
. {
log . "{proto} Request: {name} {type} {>id}" #配置输出的日志格式
#对xwwxkj.com结尾的DNS只打印denial和success情况下的日志信息
log xwwxkj.com {
class denial
class success
}
}
3.2.2 log插件源码
func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
name := state.Name()
#匹配规则
for _, rule := range l.Rules {
if !plugin.Name(rule.NameScope).Matches(name) {
continue
}
rrw := dnstest.NewRecorder(w)
rc, err := plugin.NextOrFailure(l.Name(), l.Next, ctx, rrw, r)
_, ok := rule.Class[response.All]
var ok1 bool
if !ok {
tpe, _ := response.Typify(rrw.Msg, time.Now().UTC())
class := response.Classify(tpe)
_, ok1 = rule.Class[class]
}
#按照定义的log规则输出日志信息,默认采用:127.0.0.1:37840 - 26502 "A IN traefik.cpaas.wxchina.com. udp 66 false 1232" NOERROR qr,aa,rd 84 0.002630761s
if ok || ok1 {
logstr := l.repl.Replace(ctx, state, rrw, rule.Format)
clog.Info(logstr)
}
return rc, err
}
return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
}
3.2.3 log插件运行步骤
- 首先检查定义的Rule是否匹配当前的域名
- 接着判断配置的Class类型
- 根据上面的rule和Class类型来决定采用哪种log的format进行输出
- 执行下一个plugin