在Kubernetes 1.13版本以后(包含1.13),Kubernetes便指定了CoreDNS作为默认的服务发现机制(之前默认的版本是Kube-DNS),由于项目中也需要使用CoreDNS对Service进行DNS解析,所以在使用CoreDNS的过程,便深入的看下CoreDNS的源码,已对其内部的实现机制有所了解。

CoreDNS版本: CoreDNS-1.6.2

CoreDNS 的安装及使用

对于CoreDNS的安装及使用方式这里就不做详细的介绍了,具体详情请参考:

CoreDNS 源码分析

CoreDNS是一个基于caddy框架实现的,整个项目大量的使用了Caddy的插件功能,接下来直接分析源码,下面这张图(图片源自网络)就是CoreDNS整体框架启动流程的一个结构:


coredns

首先在CoreDNS的main.go文件中,会对plugin package进行import,用来将所有的plugin进行register。

1
2
3
4
5
package main
import (
// Plug in CoreDNS
_ "github.com/coredns/coredns/core/plugin"
)

接下来我们看下github.com/coredns/coredns/core/plugin/zplugin.go文件的内容(需要注意的是zplugin.go文件是Makefile通过plugin.cfg自动生成的, 而CoreDNS会基于plugin.cfg文件中的指令顺序,按顺序进行插件的执行)。

Makefile通过plugin.cfg生成的指令如下:

1
core/plugin/zplugin.go core/dnsserver/zdirectives.go: plugin.cfg GO111MODULE=on go generate coredns.go

zplugin.go文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// generated by directives_generate.go; DO NOT EDIT

package plugin

import (
// Include all plugins.
_ "github.com/caddyserver/caddy/onevent"
_ "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/cache"
_ "github.com/coredns/coredns/plugin/cancel"
_ "github.com/coredns/coredns/plugin/chaos"
_ "github.com/coredns/coredns/plugin/debug"
_ "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/federation"
_ "github.com/coredns/coredns/plugin/file"
_ "github.com/coredns/coredns/plugin/forward"
_ "github.com/coredns/coredns/plugin/grpc"
_ "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/log"
_ "github.com/coredns/coredns/plugin/loop"
_ "github.com/coredns/coredns/plugin/metadata"
_ "github.com/coredns/coredns/plugin/metrics"
_ "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/template"
_ "github.com/coredns/coredns/plugin/tls"
_ "github.com/coredns/coredns/plugin/trace"
_ "github.com/coredns/coredns/plugin/whoami"
)

在plugin package中的每一个plugin的init()都会在程序启动之前被执行,进行plugin的注册。让我们来看kuernetes plugin的注册过程:

1
2
3
4
5
6
func init() {
caddy.RegisterPlugin("kubernetes", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}

caddy.Plugin结构包含了被注册的plugin的基本信息,包括注册的服务类型(在CoreDNS中的服务类型是dns)和请求这个plugin时,这个plugin该做的Action。

1
2
3
4
5
6
7
8
9
10
11
// Plugin is a type which holds information about a plugin.
type Plugin struct {
// ServerType is the type of server this plugin is for.
// Can be empty if not applicable, or if the plugin
// can associate with any server type.
ServerType string

// Action is the plugin's setup function, if associated
// with a directive in the Caddyfile.
Action SetupFunc
}

caddy.RegisterPlugin方法的作用:

将指定plugin name(本示例:kubernetes)注册到服务类型为DNS的CoreDNS服务中。最终所有被注册plugin的存储结构为:

1
2
3
4
5
6
7
// plugins is a map of server type to map of plugin name to
// Plugin. These are the "general" plugins that may or may
// not be associated with a specific server type. If it's
// applicable to multiple server types or the server type is
// irrelevant, the key is empty string (""). But all plugins
// must have a name.
plugins = make(map[string]map[string]Plugin)

因此,通过上面的这个过程就把CoreDNS需要的所有的plugin都注册到了plugins = make(map[string]map[string]Plugin)结构中,用于后续的使用。

注册完所有的plugin之后,开始执行main方法,该方法很简洁,只会去调用一个coremain.Run()方法,在run.go文件的import package中会存在一个如下的package:

1
import ("github.com/coredns/coredns/core/dnsserver")

这个package会向CoreDNS注册一个叫做dns的服务类型,并关联与这个dns服务类型相关的一些操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func init() {
flag.StringVar(&Port, serverType+".port", 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\n}\n"),
ServerTypeName: serverType,
}
},
NewContext: newContext,
})
}

caddy.ServerType包含一些服务类型相关的信息。包括:

  • 一个指令列表的函数声明,这个指令列表定义在github.com/coredns/coredns/core/dnsserver/zdirectives.go文件中,并且也是通过Makefile的方式自动生成。并按照指令类别的顺序依次执行。
  • 一个加载默认Corefile默认配置文件的函数声明(input中包含了配置文件的内容路径以及所属的服务类型等信息)
  • 一个用于服务实例启动的Context函数声明

具体的定义如下结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ServerType contains information about a server type.
type ServerType struct {
// Function that returns the list of directives, in
// execution order, that are valid for this server
// type. Directives should be one word if possible
// and lower-cased.
Directives func() []string

// DefaultInput returns a default config input if none
// is otherwise loaded. This is optional, but highly
// recommended, otherwise a blank Caddyfile will be
// used.
DefaultInput func() Input

// The function that produces a new server type context.
// This will be called when a new Caddyfile is being
// loaded, parsed, and executed independently of any
// startup phases before this one. It's a way to keep
// each set of server instances separate and to reduce
// the amount of global state you need.
NewContext func(inst *Instance) Context
}

当caddy.ServerType初始化完成之后,会通过caddy.RegisterServerType向CoreDNS进行服务类型为dns的服务注册,并将服务类型注册到如下结构:

1
2
// serverTypes is a map of registered server types.
serverTypes = make(map[string]ServerType)

上面介绍的这些都是CoreDNS服务在启动过程中的初始化工作。go on

Run()中会执行:

1
corefile, err := caddy.LoadCaddyfile(serverType)

该函数的主要作用就是加载Corefile配置文件,并将配置文件的内容解析到如下的结构中用于后续服务的使用:

1
2
3
4
5
6
7
8
9
10
type Input interface {
// Gets the Caddyfile contents
Body() []byte

// Gets the path to the origin file
Path() string

// The type of server this input is intended for
ServerType() string
}

加载完成Corefile之后,接下来就开始启动engine了 😋。

1
instance, err := caddy.Start(corefile)

Start函数会等所有需要的服务都LISTEN之后,才会返回,否者会一直block, 具体定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
if err != nil {
return inst, err
}
signalSuccessToParent()
if pidErr := writePidFile(); pidErr != nil {
log.Printf("[ERROR] Could not write pidfile: %v", pidErr)
}

// Execute instantiation events
EmitEvent(InstanceStartupEvent, inst)

return inst, nil
}

Start函数会返回一个Instance的结构,该结构中保存了一些服务的状态,用于服务的启动。

Instance结构的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
type Instance struct {
// serverType is the name of the instance's server type
serverType string

// caddyfileInput is the input configuration text used for this process
caddyfileInput Input

// wg is used to wait for all servers to shut down
wg *sync.WaitGroup

// context is the context created for this instance,
// used to coordinate the setting up of the server type
context Context

// servers is the list of servers with their listeners
servers []ServerListener

// these callbacks execute when certain events occur
OnFirstStartup []func() error // starting, not as part of a restart
OnStartup []func() error // starting, even as part of a restart
OnRestart []func() error // before restart commences
OnRestartFailed []func() error // if restart failed
OnShutdown []func() error // stopping, even as part of a restart
OnFinalShutdown []func() error // stopping, not as part of a restart

// storing values on an instance is preferable to
// global state because these will get garbage-
// collected after in-process reloads when the
// old instances are destroyed; use StorageMu
// to access this value safely
Storage map[interface{}]interface{}
StorageMu sync.RWMutex
}
  • serverType:表示该Instance被关联到具体哪一个服务类型,在我们这里是dns
  • caddyfileInput: 已经被加载的corefile文件的内容。
  • context: 启动服务使用的一些context内容。
  • servers: 该instance需要启动的服务列表(在我们这里主要是TCP和UDP服务)。

    其它的就是一个plugin注册到服务的启动回调函数(callbacks),当服务启动的时候,会去根据不同的plugin加载具体的启动callback函数。

好了,让我们继续回到Start函数的定义中, 在该函数中主要调用三个函数分别是:

  • startWithListenerFds: 用于解析Corefile配置文件及校验指令执行的是否正确,并启动CoreDNS服务。
  • signalSuccessToParent: 向父进程报告服务启动状态。
  • writePidFile: 将启动服务的进程ID(PID)写入到指定的文件。

从上面的解释中即可知道startWithListenerFds是最为核心的函数调用了,接下来直接详细的分析startWithListenerFds函数。在该函数中首先会调用

1
loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body()))`

这个函数的主要作用是加载Corefile文件的内容,并解析该文件的内容到ServerBlock结构:

1
2
3
4
type ServerBlock struct {
Keys []string
Tokens map[string][]Token
}

其中的keys主要用于存储Corefile文件中的Zone相关的数据(比如: server的地址及端口号等),而Tokens用于存储该server下面的plugin的信息,其中Tokens的key是plugin name, []Token存储的是该plugin在Corefile中的位置,便于后续的解析操作。

之后会调用NewContext对instance的Context进行初始化。然后通过该Context实例分别去调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Context interface {
// Called after the Caddyfile is parsed into server
// blocks but before the directives are executed,
// this method gives you an opportunity to inspect
// the server blocks and prepare for the execution
// of directives. Return the server blocks (which
// you may modify, if desired) and an error, if any.
// The first argument is the name or path to the
// configuration file (Caddyfile).
//
// This function can be a no-op and simply return its
// input if there is nothing to do here.
InspectServerBlocks(string, []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error)

// This is what Caddy calls to make server instances.
// By this time, all directives have been executed and,
// presumably, the context has enough state to produce
// server instances for Caddy to start.
MakeServers() ([]Server, error)
}

ValidateAndExecuteDirectives函数中只调用了InspectServerBlocks,这个函数会在解析完Corefile文件之后,执行Corefile中的指令之前会被调用,用于进一步的对serverblock进行检查并进行进一步的参数完善。

等上面的准备工作准备完成之后,会调用executeDirectives去遍历所有的指令directives,如果serverblock中的指令在directives,则设置相应的Controller去执行相关plugin的action动作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func executeDirectives(inst *Instance, filename string, directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error {
// a directive for all server blocks before going to the next directive.
// This is important mainly due to the parsing callbacks (below).
for _, dir := range directives {
for i, sb := range sblocks {
for j, key := range sb.Keys {
// Execute directive if it is in the server block
if tokens, ok := sb.Tokens[dir]; ok {
controller := &Controller{
instance: inst,
Key: key,
Dispenser: caddyfile.NewDispenserTokens(filename, tokens),
OncePerServerBlock: func(f func() error) error {
var err error
once.Do(func() {
err = f()
})
return err
},
ServerBlockIndex: i,
ServerBlockKeyIndex: j,
ServerBlockKeys: sb.Keys,
ServerBlockStorage: storages[i][dir],
}

setup, err := DirectiveAction(inst.serverType, dir)
if err != nil {
return err
}

err = setup(controller)
if err != nil {
return err
}

storages[i][dir] = controller.ServerBlockStorage // persist for this server block
}
}
}
return nil
}

通过ValidateAndExecuteDirectives函数的进一步对Corefile解析,并通过解析过的各个plugin进行执行之后,Corefile文件中定义的相关的plugin插件基本都已处于准备好的状态。

接下来调用dnsContext的MakeServers方法,用于启动CoreDNS服务,接收外部的请求。下面是MakeServers的函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {

.......
// we must map (group) each config to a bind address
groups, err := groupConfigsByListenAddr(h.configs)
if err != nil {
return nil, err
}
// then we create a server for each group
var servers []caddy.Server
for addr, group := range groups {
// switch on addr
switch tr, _ := parse.Transport(addr); tr {
case transport.DNS:
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)

case transport.TLS:
s, err := NewServerTLS(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)

case transport.GRPC:
s, err := NewServergRPC(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)

case transport.HTTPS:
s, err := NewServerHTTPS(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
}

}

return servers, nil
}

在MakeServers函数中首先会调用:groupConfigsByListenAddr用于将不同server下的配置进行分组。并返回一个map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {

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
}

其中返回的map中,key表示的是服务的地址,value表示与该服务相关的配置信息。

之后遍历该map, 然后根据不同的transport去启动相应的服务,当前CoreDNS支持4种类型的transport:

1
2
3
4
5
6
const (
DNS = "dns"
TLS = "tls"
GRPC = "grpc"
HTTPS = "https"
)

无论是哪一种类型的transport最终都会去执行NewServer函数。NewServer会返回一些CoreDNS server:

1
2
3
4
5
6
7
8
9
10
11
12
13
ype Server struct {
Addr string // Address we listen on

server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
m sync.Mutex // protects the servers

zones map[string]*Config // zones keyed by their address
dnsWg sync.WaitGroup // used to wait on outstanding connections
graceTimeout time.Duration // the maximum duration of a graceful shutdown
trace trace.Trace // the trace plugin for the server
debug bool // disable recover()
classChaos bool // allow non-INET class queries
}

上面只是make了CoreDNS需要启动的Server结构,但是真正的服务进程到现在这个阶段还没有真正的启动。

接下来的操作是通过下面的这部分代码来对已经注册的各个plugin进行启动操作,也就是在每个plugin会在进行setup的时候,指定相关的回调函数(callback),也就是上面介绍Instance结构的时候,介绍的哪些回调函数(callback)。

1
2
3
4
5
6
for _, startupFunc := range inst.OnStartup {
err = startupFunc()
if err != nil {
return err
}
}

当所有的plugin需要的准备工作都处理完成之后,就调用startServers来启动CoreDNS服务,startServers函数的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error {
for _, s := range serverList {
if ln == nil {
ln, err = s.Listen()
if err != nil {
return fmt.Errorf("Listen: %v", err)
}
}
if pc == nil {
pc, err = s.ListenPacket()
if err != nil {
return fmt.Errorf("ListenPacket: %v", err)
}
}

inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
}

for _, s := range inst.servers {
inst.wg.Add(2)
stopWg.Add(2)
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)
}

// Log errors that may be returned from Serve() calls,
// these errors should only be occurring in the server loop.
go func() {
for {
select {
case err := <-errChan:
if err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
// this error is normal when closing the listener; see https://github.com/golang/go/issues/4373
log.Println(err)
}
}
case <-stopChan:
return
}
}
}()

go func() {
stopWg.Wait()
stopChan <- struct{}{}
}()

return nil
}

startServers函数中去遍历serverList去分别启动TCPUDP服务。用于接收外部的DNS请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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)
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)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()

return s.server[udp].ActivateAndServe()
}

最后,caddy.Wait()主进程进入等待状态,caddy开始处理接收到的请求,这里的每个请求都会发送给core/dnsserver/server.go的ServerDNS处理,这里是DNS的总逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// ServeDNS is the entry point for every request to the address that s
// 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.
// ......

// Wrap the response writer in a ScrubWriter so we automatically make the reply fit in the client's buffer.
w = request.NewScrubWriter(r, w)

for {
l := len(q[off:])
for i := 0; i < l; i++ {
b[i] = q[off+i]
// normalize the name for the lookup
if b[i] >= 'A' && b[i] <= 'Z' {
b[i] |= ('a' - 'A')
}
}

if h, ok := s.zones[string(b[:l])]; ok {
if r.Question[0].Qtype != dns.TypeDS {
if h.FilterFunc == nil {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
errorFunc(s.Addr, w, r, rcode)
}
return
}
// FilterFunc is set, call it to see if we should use this handler.
// This is given to full query name.
if h.FilterFunc(q) {
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 actually 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
}
}

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
}

// 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
}

}
  • 遍历dns.NextLabel(dns),找到匹配的Zone,如果都不匹配,则尝试默认的Zone(“.”)
  • 调动zone.pluginChain.ServerDNS()进行处理,pluginChain是一个链,链的顺顺序是core/dnsserver/zdirectives.go中定义的,按定义的顺序依次执行。(上面已经提到过了:))

注意:每一个插件都需要实现如果这个Interface:

1
2
3
4
type Handler interface {
ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
Name() string
}

其中ServeDNS是插件实现的核心逻辑,Name()是插件的名称。

总结

这样CoreDNS整体的一个架构流程就已经分析完了,说CoreDNS服务有什么新的亮点其实也没亮点,主要就依赖于caddy的插件化方案吧,但是现在有很多middleware的web server应该都能满足这个需求,哈哈。好了,本篇文章只是分析了CoreDNS的整体结构,但没有对其内部的各类插件进行细化的分析,例如: kubernetes, forward, health等。关于CoreDNS的内置插件,接下来可能会有篇文章来针对kubernetes插件进行分析:)