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    errorsfile /etc/coredns/zones/example.io.db
}example.net:53 {file /etc/coredns/zones/example.net.db
}.:53 {kuberneteserrorsloghealth
}

下图为配置的简略图

image.png

a. 从图中可看到插件执行顺序不是配置文件中的顺序,这是因为插件执行顺序是在源码目录中的plugin.cfg指定的,一旦编译后,顺序就固定了。
b. .根serverblock虽然指定了health,但是图中却没有,这是因为health插件不参与dns请求的处理。能处理dns请求的插件必须提供如下两个接口函数。

Handler interface {ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)Name() string
}

dns请求处理流程
收到dns请求后,首先根据域名匹配zone找到对应的dnsserver(最长匹配优先),如果没有匹配到,则使用默认的root dnsserver。
找到dnsserver后,就要按照插件顺序执行其中配置的插件,当然并不是配置的插件都会被执行,如果某个插件成功找到记录,则返回成功,否则根据插件是否配置了fallthrough等来决定是否执行下一个插件。

源码分析

plugin.cfg
源码目录下的plugin.cfg指定了插件执行顺序,如果想添加插件,可按格式添加到指定位置。

metadata:metadata
geoip:geoip
cancel:cancel
tls:tls
reload:reload
nsid:nsid
bufsize:bufsize
root:root
bind:bind
debug:debug
trace:trace
ready:ready
health:health
pprof:pprof
prometheus:metrics
errors:errors
log:log
dnstap:dnstap
local:local
dns64:dns64
acl:acl
any:any
chaos:chaos
loadbalance:loadbalance
cache:cache
rewrite:rewrite
header:header
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts
route53:route53
azure:azure
clouddns:clouddns
k8s_external:k8s_external
kubernetes:kubernetes
file:file
auto:auto
secondary:secondary
etcd:etcd
loop:loop
forward:forward
grpc:grpc
erratic:erratic
whoami:whoami
on:github.com/coredns/caddy/onevent
sign:sign

源码目录下的Makefile根据plugin.cfg生成了两个go文件:zplugin.go和zdirectives.go。

core/plugin/zplugin.go core/dnsserver/zdirectives.go: plugin.cfggo generate coredns.gogo get
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.go
import (_ "github.com/coredns/coredns/core/plugin" // Plug in CoreDNS."github.com/coredns/coredns/coremain"
)maincoremain.Run()

codedns.go 首先导入了包"github.com/coredns/coredns/core/plugin",此包内只有一个文件zplugin.go,此文件为自动生成的,主要导入了所有的插件,执行每个插件的init函数。

接着执行 run.go Run

//coredns/coremain/run.go
import (..."github.com/coredns/coredns/core/dnsserver"
)func Run()//解析参数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 {//执行 confLoadercdyfile, err := l.loader.Load(serverType)}instance, err := caddy.Start(corefile)// Twiddle your thumbsinstance.Wait()

此文件又引入了包"github.com/coredns/coredns/core/dnsserver",其init函数在 dnsserver/register.go 文件中,如下所示,主要是注册了serverType

const serverType = "dns"
// DefaultPort is the default port.
const DefaultPort = transport.Port
Port = "53"func init() {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.go
func Start(cdyfile Input) (*Instance, error)inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}err := startWithListenerFds(cdyfile, inst, nil)func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error {ValidateAndExecuteDirectives(cdyfile, inst, false)//stypeName 为 dnsstypeName := 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.func NewDispenser(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:newContextinst.context = stype.NewContext(inst)//coredns/core/dnsserver/register.go:InspectServerBlockssblocks, 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 {//获取插件的初始化函数 setupsetup, 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.OnStartupc.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.Pluginc.ListenHosts = c.firstConfigInBlock.ListenHostsc.Debug = c.firstConfigInBlock.Debugc.TLSConfig = c.firstConfigInBlock.TLSConfig}//将监听相同地址的config放在同一个group。一个config表示一个 dnsserver// we must map (group) each config to a bind addressgroups, 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 {return nil, 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 zones.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* alsosite.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) 函数中执行 pluginChainsite.pluginChain = stack}return s, nilservers = 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:Listenl, err := reuseport.Listen("tcp", s.Addr[len(transport.DNS+"://"):])return l, nil}if pc == nil {pc, err = s.ListenPacket()//core/dnsserver/server.go:ListenPacketp, 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) {go func() {defer func() {inst.wg.Done()stopWg.Done()}()errChan <- s.Serve(ln)}()go func() {defer func() {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       intend       booldshandler *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 pluginserrorAndMetricsFunc(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 querystate.Zone = zonevar (records   []dns.RRextra     []dns.RRtruncated boolerr       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 NXDOMAINfake := 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}if len(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 = truncatedm.Authoritative = truem.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.
func SRV(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:Servicesservices, 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.Hostserv.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获取indexidx := 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.html
https://blog.csdn.net/zhonglinzhang/article/details/99679323
https://www.codercto.com/a/89703.html

//NodeLocal DNSCache
https://www.cnblogs.com/sanduzxcvbnm/p/16013560.html
https://blog.csdn.net/xixihahalelehehe/article/details/118894971

也可参考:coredns源码分析 - 简书 (jianshu.com)

coredns源码分析相关推荐

  1. kubeadm源码分析(内含kubernetes离线包,三步安装)

    k8s离线安装包 三步安装,简单到难以置信 kubeadm源码分析 说句实在话,kubeadm的代码写的真心一般,质量不是很高. 几个关键点来先说一下kubeadm干的几个核心的事: kubeadm ...

  2. kubeadm源码分析(kubernetes离线安装包,三步安装)

    k8s离线安装包 三步安装,简单到难以置信 kubeadm源码分析 说句实在话,kubeadm的代码写的真心一般,质量不是很高. 几个关键点来先说一下kubeadm干的几个核心的事: kubeadm ...

  3. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  4. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  5. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  6. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  7. Yolov3Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...

  8. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  9. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

最新文章

  1. Linux 的数字权限意义
  2. SpringMVC---web.xml的配置
  3. 【Linux 内核 内存管理】Linux 内核内存布局 ③ ( Linux 内核 动态分配内存 系统接口函数 | 统计输出 vmalloc 分配的内存 )
  4. check $m2_home environment variable and mvn script match
  5. Hibernate常见问题集锦
  6. NVIDIA助力SiemensPLMSoftware“工业4.0创新实验室”,携手推进中国制造创新之路
  7. linux新手常用命令
  8. 详述一则数据库死锁故障的分析过程
  9. Jquery 操作checkbox
  10. python编程是干嘛的-编程小白必看!Python到底能做什么?
  11. 2018-2019-2 20175320实验五《网络编程与安全》实验报告
  12. BZOJ3675[APIO2014] 序列分割
  13. 组装电脑配件选择教程
  14. 手写HashMap排序
  15. 2022李宏毅第14讲---机器终身学习(Life Long Learning)
  16. 自己制作dns解析服务器,如何使用自己的DNS服务器解析域名,架设自己的DNS服务器...
  17. html动态图片怎么设背景,微信8.0状态背景视频怎么设置?状态视频动态背景图设置教程[多图]...
  18. 今天分享给你几个绘制Excel表格的技巧
  19. oracle截取6位,Oracle函数获取IDCARD中年龄
  20. [置顶]程序员常用网站收藏[定期更新]——csdn博客

热门文章

  1. vscode配置prettier
  2. HTML+CSS+JS简易实现校园表白墙,快去给心仪的人表白吧
  3. Python动态心形图形-快去表白吧
  4. TCP调试-16进制调试
  5. php中的点号,PHP中逗号与点号有什么区别
  6. Palindrome(最长公共子序列 + 滚动数组优化)
  7. Dropzone单文件上传、多文件上传、文件夹上传,springmvc接收,上传至Minio的一系列问题
  8. 运用计算机技术和语文教学,信息技术如何更好的应用于语文教学中
  9. 骨传导蓝牙耳机什么牌子好、骨传导蓝牙耳机排行榜
  10. 完美解决:找不到msvcr120.dll,需要重新安装服务