CoreDNS的使用

1,633 阅读5分钟

1. 概述

CoreDNS是一个Golang编写的DNS服务器,作为K8S使用的DNS组件,提供了Plugin机制,方便配置和使用,本文章主要介绍CoreDNS的基本使用,原理,Plugin的介绍

2. CoreDNS的部署

具体的安装参考: coredns.io/manual/toc/… 具体的安装方式支持如下的方式:

  1. 直接二进制安装,从github上下载,直接启动即可
  2. Docker安装,拉取镜像,采用docker run 的形式来运行
  3. K8S的安装方式(可以复制K8S集群安装CoreDNS的Deployment和关联的ConfigMap),之所以要分开主要是为了不影响K8S的内部DNS的解析,专门提供一个整个线上服务使用的DNS服务器

下面我们就采用最基本的二进制方式进行安装,主要朝向使用和原理分析,Plugin介绍方面发展

2.1 二进制安装方式

  1. github.com/coredns/cor… 按照自己的操作系统下载对应版本v1.10.1的CoreDNS的二进制包
  2. 下载下来之后进行解压
tar -zxvf coredns_1.10.1_linux_amd64.tgz
  1. 编写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
    }
}
  1. 启动coredns程序
#coredns是刚才解压出来的二进制程序 corefile是上面创建的文件
./coredns -conf=./corefile
  1. 可以看到如下的输出
xwwxkj.com:1053  #定义的corefile文件的zone配置
[INFO] plugin/reload: Running configuration SHA512 = 8da315c1557a068227dd232a31d7a93b10a6b486d54f5cf41ad773dd832236c8119471c9972b014b5e1c174b8bc08210119250cb5d5cfcc676d626630d1957bc
CoreDNS-1.10.1 #CoreDNS的版本号
linux/amd64, go1.20.4, #操作系统和Golang版本信息
  1. 执行dig命令验证下
dig @localhost -p 1053 tekton.cpaas.xwwxkj.com

#dig命令的简单使用:
dig @DNS服务器的IP -p DNS服务器的端口(默认53) 需要解析的域名

附图(CoreDNS的Plugin处理流程):

coredns-plugins.png

2.2 CoreDNS的查找流程

我们以如下的命令过程来叙述CoreDNS的查询流程

dig @localhost -p 1053 tekton.cpaas.xwwxkj.com
  1. dig发送UDP(或者TCP类型)类型的DNS查询数据包给localhost:1053的服务
  2. CoreDNS接收到DNS服务请求,并且解析出来查询的域名信息
  3. 按照配置的zone(xwwxkj.com),来进行匹配
  4. 执行zone(xwwxkj.com)执行对应的插件(hosts)
  5. 执行hosts插件进行查找对应域名的IP
  6. 返回查找结果

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插件查找过程

  1. 进入hosts插件的ServerDNS方法,假设查找的域名是:traefik.cpaas.wxchina.com
  2. 使用配置的zone和域名进行后缀匹配,返回查找到的zone信息
  3. 如果zone为空(意思就是没有查找到),就走下个plugin,如果zone找到了,那就继续查找
  4. 执行LookupStaticHostV4()方法。首先是查找CoreDNS从本地机器加载的hosts里面有没有
  5. 如果从本地加载的hosts文件没有,那就从hosts {} 配置里面查找有没有
  6. 如果正常/找不到 都会走下一个plugin

3.2 log插件介绍

3.2.1 log的基本配置

log的输出包含两个方面:

  1. 输出的日志格式
  2. 输出哪些类型的日志信息(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插件运行步骤

  1. 首先检查定义的Rule是否匹配当前的域名
  2. 接着判断配置的Class类型
  3. 根据上面的rule和Class类型来决定采用哪种log的format进行输出
  4. 执行下一个plugin