想了解CoreDNS?这篇源码分析文章别错过
CoreDNS是使用go语言编写的快速灵活的DNS服务,采用链式插件模式,每个插件实现独立的功能,底层协议可以是tcp/udp,也可以是TLS,gRPC等。默认监听所有ip地址,可使用bind插件指定监听指定地址。
[SCHEME://]ZONE [[SCHEME://]ZONE]...[:PORT] { [PLUGIN]...}
SCHEME是可选的,默认值为dns://,也可以指定为tls://,grpc://或者https://。ZONE是可选的,指定了此dnsserver可以服务的域名前缀,如果不指定,则默认为root,表示可以接收所有的dns请求。PORT是选项的,指定了监听端口号,默认为53,如果这里指定了端口号,则不能通过参数-dns.port覆盖。
一块上面格式的配置表示一个dnsserver,称为serverblock,可以配置多个serverblock表示多个dnsserver。
下面通过一个例子说明,如下配置文件指定了4个serverblock,即4个dnsserver,第一个监听端口5300,后面三个监听同一个端口53,每个dnsserver指定了特定的插件
coredns.io:5300 { file /etc/coredns/zones/coredns.io.db}example.io:53 { log errors file /etc/coredns/zones/example.io.db}example.net:53 { file /etc/coredns/zones/example.net.db}.:53 { kubernetes errors log health}
下图为配置的简略图

a. 从图中可看到插件执行顺序不是配置文件中的顺序,这是因为插件执行顺序是在源码目录中的plugin.cfg指定的,一旦编译后,顺序就固定了
b. 根serverblock虽然指定了health,但是图中却没有,这是因为health插件不参与dns请求的处理。能处理dns请求的插件必须提供如下两个接口函数
Handler interface { ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) Name() string}
收到dns请求后,首先根据域名匹配zone找到对应的dnsserver(最长匹配优先),如果没有匹配到,则使用默认的root dnsserver。找到dnsserver后,就要按照插件顺序执行其中配置的插件,当然并不是配置的插件都会被执行,如果某个插件成功找到记录,则返回成功,否则根据插件是否配置了fallthrough等来决定是否执行下一个插件。
plugin.cfg源码目录下的plugin.cfg指定了插件执行顺序,如果想添加插件,可按格式添加到指定位置。
metadata:metadatageoip:geoipcancel:canceltls:tlsreload:reloadnsid:nsidbufsize:bufsizeroot:rootbind:binddebug:debugtrace:traceready:readyhealth:healthpprof:pprofprometheus:metricserrors:errorslog:logdnstap:dnstaplocal:localdns64:dns64acl:aclany:anychaos:chaosloadbalance:loadbalancecache:cacherewrite:rewriteheader:headerdnssec:dnssecautopath:autopathminimal:minimaltemplate:templatetransfer:transferhosts:hostsroute53:route53azure:azureclouddns:clouddnsk8s_external:k8s_externalkubernetes:kubernetesfile:fileauto:autosecondary:secondaryetcd:etcdloop:loopforward:forwardgrpc:grpcerratic:erraticwhoami:whoamion:github.com/coredns/caddy/oneventsign:sign
源码目录下的Makefile根据plugin.cfg生成了两个go文件:zplugin.go和zdirectives.go
core/plugin/zplugin.go会导入所有的插件,执行所有插件的init函数。import (// Include all plugins. _ "github.com/coredns/caddy/onevent" _ "github.com/coredns/coredns/plugin/acl" _ "github.com/coredns/coredns/plugin/any" _ "github.com/coredns/coredns/plugin/auto" _ "github.com/coredns/coredns/plugin/autopath" _ "github.com/coredns/coredns/plugin/azure" _ "github.com/coredns/coredns/plugin/bind" _ "github.com/coredns/coredns/plugin/bufsize" _ "github.com/coredns/coredns/plugin/cache" _ "github.com/coredns/coredns/plugin/cancel" _ "github.com/coredns/coredns/plugin/chaos" _ "github.com/coredns/coredns/plugin/clouddns" _ "github.com/coredns/coredns/plugin/debug" _ "github.com/coredns/coredns/plugin/dns64" _ "github.com/coredns/coredns/plugin/dnssec" _ "github.com/coredns/coredns/plugin/dnstap" _ "github.com/coredns/coredns/plugin/erratic" _ "github.com/coredns/coredns/plugin/errors" _ "github.com/coredns/coredns/plugin/etcd" _ "github.com/coredns/coredns/plugin/file" _ "github.com/coredns/coredns/plugin/forward" _ "github.com/coredns/coredns/plugin/geoip" _ "github.com/coredns/coredns/plugin/grpc" _ "github.com/coredns/coredns/plugin/header" _ "github.com/coredns/coredns/plugin/health" _ "github.com/coredns/coredns/plugin/hosts" _ "github.com/coredns/coredns/plugin/k8s_external" _ "github.com/coredns/coredns/plugin/kubernetes" _ "github.com/coredns/coredns/plugin/loadbalance" _ "github.com/coredns/coredns/plugin/local" _ "github.com/coredns/coredns/plugin/log" _ "github.com/coredns/coredns/plugin/loop" _ "github.com/coredns/coredns/plugin/metadata" _ "github.com/coredns/coredns/plugin/metrics" _ "github.com/coredns/coredns/plugin/minimal" _ "github.com/coredns/coredns/plugin/nsid" _ "github.com/coredns/coredns/plugin/pprof" _ "github.com/coredns/coredns/plugin/ready" _ "github.com/coredns/coredns/plugin/reload" _ "github.com/coredns/coredns/plugin/rewrite" _ "github.com/coredns/coredns/plugin/root" _ "github.com/coredns/coredns/plugin/route53" _ "github.com/coredns/coredns/plugin/secondary" _ "github.com/coredns/coredns/plugin/sign" _ "github.com/coredns/coredns/plugin/template" _ "github.com/coredns/coredns/plugin/tls" _ "github.com/coredns/coredns/plugin/trace" _ "github.com/coredns/coredns/plugin/transfer" _ "github.com/coredns/coredns/plugin/whoami")
core/dnsserver/zdirectives.go将所有插件名字放在一个数组中
var Directives = []string{"metadata","geoip","cancel","tls","reload","nsid","bufsize","root","bind","debug","trace","ready","health","pprof","prometheus","errors","log","dnstap","local","dns64","acl","any","chaos","loadbalance","cache","rewrite","header","dnssec","autopath","minimal","template","transfer","hosts","route53","azure","clouddns","k8s_external","kubernetes","file","auto","secondary","etcd","loop","forward","grpc","erratic","whoami","on","sign",}
codedns 主函数
//coredns/coredns.goimport ( _ "github.com/coredns/coredns/core/plugin"// Plug in CoreDNS."github.com/coredns/coredns/coremain")main coremain.Run()
codedns.go 首先导入了包”github.com/coredns/coredns/core/plugin”,此包内只有一个文件zplugin.go,此文件为自动生成的,主要导入了所有的插件,执行每个插件的init函数。
接着执行 run.go Run
//coredns/coremain/run.goimport ( ..."github.com/coredns/coredns/core/dnsserver")funcRun()//解析参数 flag.Parse()//如果指定了参数 version,则打印版本信息后退出if version { showVersion() os.Exit(0) }//如果指定了参数 plugins,则只打印插件信息后退出if plugins { fmt.Println(caddy.DescribePlugins()) os.Exit(0) }//解析配置文件 corefile, err := caddy.LoadCaddyfile(serverType) cdyfile, err := loadCaddyfileInput(serverType)for _, l := range caddyfileLoaders {//执行 confLoader cdyfile, err := l.loader.Load(serverType) } instance, err := caddy.Start(corefile)// Twiddle your thumbs instance.Wait()
此文件又引入了包”github.com/coredns/coredns/core/dnsserver”,其init函数在 dnsserver/register.go 文件中,如下所示,主要是注册了serverType
const serverType = "dns"// DefaultPort is the default port.const DefaultPort = transport.PortPort = "53"funcinit() { flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port") flag.StringVar(&Port, "p", DefaultPort, "Default port") caddy.RegisterServerType(serverType, caddy.ServerType{ Directives: func() []string { return Directives }, DefaultInput: func() caddy.Input {return caddy.CaddyfileInput{ Filepath: "Corefile", Contents: []byte(".:" + Port + " {\nwhoami\nlog\n}\n"), ServerTypeName: serverType, } }, NewContext: newContext, })}
剩下的就是解析参数,解析配置文件后,执行caddy.Start。这里就是根据配置文件中指定的serverblock,执行插件的setup进行初始化,创建对应的server,开始监听dns请求
//caddy/caddy.gofuncStart(cdyfile Input) (*Instance, error) inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})} err := startWithListenerFds(cdyfile, inst, nil)funcstartWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple)error { ValidateAndExecuteDirectives(cdyfile, inst, false)//stypeName 为 dns stypeName := cdyfile.ServerType()//stype 通过 RegisterServerType 注册,在 //coredns/core/dnsserver/register.go init时注册 stype, err := getServerType(stypeName) stype, ok := serverTypes[serverType]if ok {return stype, nil } ... inst.caddyfileInput = cdyfile//func loadServerBlocks(serverType, filename string, input io.Reader) ([]caddyfile.ServerBlock, error) sblocks, err := loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body())) validDirectives := ValidDirectives(serverType) serverBlocks, err := caddyfile.Parse(filename, input, validDirectives) p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}// NewDispenser returns a Dispenser, ready to use for parsing the given input.funcNewDispenser(filename string, input io.Reader) Dispenser { tokens, _ := allTokens(input) // ignoring error because nothing to do with itreturn Dispenser{ filename: filename, tokens: tokens, cursor: -1, } }return p.parseAll()//coredns/core/dnsserver/register.go:newContext inst.context = stype.NewContext(inst)//coredns/core/dnsserver/register.go:InspectServerBlocks sblocks, err = inst.context.InspectServerBlocks(cdyfile.Path(), sblocks)return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)//遍历执行插件注册的 setup 函数for _, dir := range directives {for i, sb := range sblocks {//获取插件的初始化函数 setup setup, err := DirectiveAction(inst.serverType, dir)if stypePlugins, ok := plugins[serverType]; ok {if plugin, ok := stypePlugins[dir]; ok {return plugin.Action, nil } }//执行插件注册的 setup 函数 setup(controller)//将 onStart 添加到数组 c.instance.OnStartup c.OnStartup(onStart)//每个插件的setup函数都会调用如下函数,注册插件handler//AddPlugin -> c.Plugin = append(c.Plugin, m) dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { l.Next = nextreturn l }) } } slist, err := inst.context.MakeServers() errValid := h.validateZonesAndListeningAddresses()for _, c := range h.configs { c.Plugin = c.firstConfigInBlock.Plugin c.ListenHosts = c.firstConfigInBlock.ListenHosts c.Debug = c.firstConfigInBlock.Debug c.TLSConfig = c.firstConfigInBlock.TLSConfig }//将监听相同地址的config放在同一个group。一个config表示一个 dnsserver// we must map (group) each config to a bind address groups, err := groupConfigsByListenAddr(h.configs) groups := make(map[string][]*Config)for _, conf := range configs {for _, h := range conf.ListenHosts { addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))if err != nil {returnnil, err } addrstr := conf.Transport + "://" + addr.String() groups[addrstr] = append(groups[addrstr], conf) } }return groups, nil//为同一个组的config创建一个server,多个插件共享一个底层server,上层通过//zone区分请求是到哪个dnsserver的// then we create a server for each groupvar servers []caddy.Serverfor addr, group := range groups {// switch on addrswitch tr, _ := parse.Transport(addr); tr {case transport.DNS: s, err := NewServer(addr, group) s := &Server{ Addr: addr, zones: make(map[string]*Config), graceTimeout: 5 * time.Second, }//site的类型是 Config,每个site表示一个dnsserverfor _, site := range group {// set the config per zone s.zones[site.Zone] = site//遍历每个dnsserver配置的插件//site.Plugin 为每个插件初始化setup时调用 dnsserver.GetConfig(c).AddPlugin 生成,//顺序是按照数组 Directives 从前向后// compile custom plugin for everythingvar stack plugin.Handler//从后向前逆序遍历 site.Pluginfor i := len(site.Plugin) - 1; i >= 0; i-- { stack = site.Plugin[i](stack)// register the *handler* also site.registerHandler(stack) c.registry[h.Name()] = hif s.trace == nil && stack.Name() == "trace" {// we have to stash away the plugin, not the// Tracer object, because the Tracer won't be initialized yetif t, ok := stack.(trace.Trace); ok { s.trace = t } }// Unblock CH class queries when any of these plugins are loaded.if _, ok := EnableChaos[stack.Name()]; ok { s.classChaos = true } }//pluginChain 为第一个插件的 handler,收到dns请求后,先执行第一个插件的 handler//在 ServeDNS(core/dnsserver/server.go) 函数中执行 pluginChain site.pluginChain = stack }return s, nil servers = append(servers, s) ... } }return servers, nilfor _, startupFunc := range inst.OnStartup {//比如 kubernetes 的 onStart 函数 err = startupFunc() } startServers(slist, inst, restartFds)for _, s := range serverList {if ln == nil { ln, err = s.Listen()//core/dnsserver/server.go:Listen l, err := reuseport.Listen("tcp", s.Addr[len(transport.DNS+"://"):])return l, nil }if pc == nil { pc, err = s.ListenPacket()//core/dnsserver/server.go:ListenPacket p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.DNS+"://"):])return p, nil } inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc}) }for _, s := range inst.servers {func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {gofunc() {deferfunc() { inst.wg.Done() stopWg.Done() }() errChan <- s.Serve(ln) }()gofunc() {deferfunc() { inst.wg.Done() stopWg.Done() }() errChan <- s.ServePacket(pc) }() }(s.server, s.listener, s.packet, inst) }}
tcp协议调用Serve,udp协议调用ServePacket
//core/dnsserver/server.go// Serve starts the server with an existing listener. It blocks until the server stops.// This implements caddy.TCPServer interface.func(s *Server) Serve(l net.Listener) error { s.m.Lock() s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { ctx := context.WithValue(context.Background(), Key{}, s) ctx = context.WithValue(ctx, LoopKey{}, 0) s.ServeDNS(ctx, w, r) })} s.m.Unlock()return s.server[tcp].ActivateAndServe()}// ServePacket starts the server with an existing packetconn. It blocks until the server stops.// This implements caddy.UDPServer interface.func(s *Server) ServePacket(p net.PacketConn) error { s.m.Lock() s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { ctx := context.WithValue(context.Background(), Key{}, s) ctx = context.WithValue(ctx, LoopKey{}, 0) s.ServeDNS(ctx, w, r) })} s.m.Unlock()return s.server[udp].ActivateAndServe()}
收到DNS请求后,调用ServeDNS,根据域名匹配dnsserver,如果没有匹配不到则使用根dnsserver,然后执行dnsserver中配置的插件
// ServeDNS is the entry point for every request to the address that// is bound to. It acts as a multiplexer for the requests zonename as// defined in the request so that the correct zone// (configuration and plugin stack) will handle the request.func(s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {// The default dns.Mux checks the question section size, but we have our// own mux here. Check if we have a question section. If not drop them here.if r == nil || len(r.Question) == 0 { errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure)return }// Wrap the response writer in a ScrubWriter so we automatically make the reply fit in the client's buffer. w = request.NewScrubWriter(r, w) q := strings.ToLower(r.Question[0].Name)var ( off int end bool dshandler *Config )//根据dns请求的域名作为zone(最长匹配优先),遍历 s.zones 进行匹配(每个zone表示一个dnsserver),//如果匹配到了,则设置 dshandler = hfor {if h, ok := s.zones[q[off:]]; ok {if h.pluginChain == nil { // zone defined, but has not got any plugins errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)return }if r.Question[0].Qtype != dns.TypeDS { rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) }return }// The type is DS, keep the handler, but keep on searching as maybe we are serving// the parent as well and the DS should be routed to it - this will probably *misroute* DS// queries to a possibly grand parent, but there is no way for us to know at this point// if there is an actual delegation from grandparent -> parent -> zone.// In all fairness: direct DS queries should not be needed. dshandler = h } off, end = dns.NextLabel(q, off)if end {break } }//匹配到zone,执行dnsserver的插件的 ServeDNS。//如果插件的 ServeDNS 直接返回了(比如k8s插件查找成功时),则只执行一个插件,//如果插件的 ServeDNS 调用plugin.NextOrFailure,则开始执行下一个插件 ServeDNS 了,//依次类推,直到有插件返回成功或者失败。if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil {// DS request, and we found a zone, use the handler for the query. rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) }return }//如果dnsserver没有匹配的zone,则最后尝试执行根zone,即配置文件中指定的"."// Wildcard match, if we have found nothing try the root zone as a last resort.if h, ok := s.zones["."]; ok && h.pluginChain != nil { rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) }return }// Still here? Error out with REFUSED. errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)}
以k8s插件为例
//k8s插件的 ServeDNS 函数// ServeDNS implements the plugin.Handler interface.func(k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := request.Request{W: w, Req: r} qname := state.QName() zone := plugin.Zones(k.Zones).Matches(qname)if zone == "" {return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r) } zone = qname[len(qname)-len(zone):] // maintain case of original query state.Zone = zonevar ( records []dns.RR extra []dns.RR truncated bool err error )switch state.QType() {case dns.TypeA: records, truncated, err = plugin.A(ctx, &k, zone, state, nil, plugin.Options{})case dns.TypeAAAA: records, truncated, err = plugin.AAAA(ctx, &k, zone, state, nil, plugin.Options{})case dns.TypeTXT: records, truncated, err = plugin.TXT(ctx, &k, zone, state, nil, plugin.Options{})case dns.TypeCNAME: records, err = plugin.CNAME(ctx, &k, zone, state, plugin.Options{})case dns.TypePTR: records, err = plugin.PTR(ctx, &k, zone, state, plugin.Options{})case dns.TypeMX: records, extra, err = plugin.MX(ctx, &k, zone, state, plugin.Options{})case dns.TypeSRV: records, extra, err = plugin.SRV(ctx, &k, zone, state, plugin.Options{})case dns.TypeSOA:if qname == zone { records, err = plugin.SOA(ctx, &k, zone, state, plugin.Options{}) }case dns.TypeAXFR, dns.TypeIXFR:return dns.RcodeRefused, nilcase dns.TypeNS:if state.Name() == zone { records, extra, err = plugin.NS(ctx, &k, zone, state, plugin.Options{})break }fallthroughdefault:// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN fake := state.NewWithQuestion(state.QName(), dns.TypeA) fake.Zone = state.Zone _, _, err = plugin.A(ctx, &k, zone, fake, nil, plugin.Options{}) }//没有查找到 dns 记录时,如果配置了fallthrough,则执行下一个插件,//否则返回错误信息if k.IsNameError(err) {if k.Fall.Through(state.Name()) {return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r) }if !k.APIConn.HasSynced() {// If we haven't synchronized with the kubernetes cluster, return server failurereturn plugin.BackendError(ctx, &k, zone, dns.RcodeServerFailure, state, nil/* err */, plugin.Options{}) }return plugin.BackendError(ctx, &k, zone, dns.RcodeNameError, state, nil/* err */, plugin.Options{}) }if err != nil {return dns.RcodeServerFailure, err }iflen(records) == 0 {return plugin.BackendError(ctx, &k, zone, dns.RcodeSuccess, state, nil, plugin.Options{}) }//查到dns记录,返回dns响应 m := new(dns.Msg) m.SetReply(r) m.Truncated = truncated m.Authoritative = true m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) w.WriteMsg(m)return dns.RcodeSuccess, nil}// SRV returns SRV records from the Backend.// If the Target is not a name but an IP address, a name is created on the fly.funcSRV(ctx context.Context, b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) {//比如对于 kubernetes 插件来说,b.Services 为 coredns/plugin/kubernetes/kubernetes.go:Services services, err := b.Services(ctx, state, false, opt) dup := make(map[item]struct{}) lookup := make(map[string]struct{})// Looping twice to get the right weight vs priority. This might break because we may drop duplicate SRV records latter on. w := make(map[int]int)for _, serv := range services { weight := 100if serv.Weight != 0 { weight = serv.Weight }if _, ok := w[serv.Priority]; !ok { w[serv.Priority] = weightcontinue } w[serv.Priority] += weight }for _, serv := range services {// Don't add the entry if the port is -1 (invalid). The kubernetes plugin uses port -1 when a service/endpoint// does not have any declared ports.if serv.Port == -1 {continue } w1 := 100.0 / float64(w[serv.Priority])if serv.Weight == 0 { w1 *= 100 } else { w1 *= float64(serv.Weight) } weight := uint16(math.Floor(w1))// weight should be at least 1if weight == 0 { weight = 1 } what, ip := serv.HostType()switch what {case dns.TypeCNAME: srv := serv.NewSRV(state.QName(), weight) records = append(records, srv)if _, ok := lookup[srv.Target]; ok {break } lookup[srv.Target] = struct{}{}if !dns.IsSubDomain(zone, srv.Target) { m1, e1 := b.Lookup(ctx, state, srv.Target, dns.TypeA)if e1 == nil { extra = append(extra, m1.Answer...) } m1, e1 = b.Lookup(ctx, state, srv.Target, dns.TypeAAAA)if e1 == nil {// If we have seen CNAME's we *assume* that they are already added.for _, a := range m1.Answer {if _, ok := a.(*dns.CNAME); !ok { extra = append(extra, a) } } }break }// Internal name, we should have some info on them, either v4 or v6// Clients expect a complete answer, because we are a recursor in their view. state1 := state.NewWithQuestion(srv.Target, dns.TypeA) addr, _, e1 := A(ctx, b, zone, state1, nil, opt)if e1 == nil { extra = append(extra, addr...) }// TODO(miek): AAAA as well here.case dns.TypeA, dns.TypeAAAA: addr := serv.Host serv.Host = msg.Domain(serv.Key) srv := serv.NewSRV(state.QName(), weight)if ok := isDuplicate(dup, srv.Target, "", srv.Port); !ok { records = append(records, srv) }if ok := isDuplicate(dup, srv.Target, addr, 0); !ok { extra = append(extra, newAddress(serv, srv.Target, ip, what)) } } }return records, extra, nil}func(k *Kubernetes) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (svcs []msg.Service, err error) { s, e := k.Records(ctx, state, false) r, e := parseRequest(state.Name(), state.Zone) services, err := k.findServices(r, state.Zone)//根据dns请求的service和namespace获取index idx := object.ServiceKey(r.service, r.namespace)//根据index从缓存获取 service 信息 serviceList = k.APIConn.SvcIndex(idx) endpointsListFunc = func() []*object.Endpoints { return k.APIConn.EpIndex(idx) } zonePath := msg.Path(zone, coredns)for _, svc := range serviceList { }return services, err
参考//如何写coredns插件http://dockone.io/article/9620
//coredns源码分析https://wenku.baidu.com/view/34cabc1e346baf1ffc4ffe4733687e21af45ff7c.htmlhttps://blog.csdn.net/zhonglinzhang/article/details/99679323https://www.codercto.com/a/89703.html
//NodeLocal DNSCachehttps://www.cnblogs.com/sanduzxcvbnm/p/16013560.htmlhttps://blog.csdn.net/xixihahalelehehe/article/details/118894971
夜雨聆风
