CoreDNS粗解

如果你厌倦了那些老古董的DNS服务 , 那么可以试试Coredns, 因为Caddy出色的插件设计, 所以Coredns的骨架基于caddy构建, 也就继承了良好的扩展性, 又因为Go语言是一门开发效率比较高的语言 , 所以开发一个自定义的插件是比较简单的事情 , 但是大多数使用都不需要自己编写插件 , 因为默认的插件以及外部的插件足够大多数场景了 。
本文主要分为四个部分

  • 源码阅读
  • 自定义插件编写
  • 一些非常有用的工具函数
源码阅读如果你不确定 , 那就阅读源代码吧 , 代码中存在准确无误的答案 。
这里假设启动命令为
./coredns并且当前工作目录有一个名称是Corefile的文本文件, 内容如下
. {forward . 8.8.8.8 1.1.1.1logerrorscache}首先看看coredns的函数入口
// coredns.gofunc main() { coremain.Run()}// coremainrun.gofunc Run()flag.StringVar(&conf, "conf", "", "Corefile to load (default ""+caddy.DefaultConfigFile+"")") // 注册一个加载配置文件函数, 如果指定了-conf参数, confLoader就能加载参数对应的配置文件 caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))// 如果不指定 , 自然也没关系, 那就在注册一个默认的配置文件加载函数 caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader)) if version {showVersion()os.Exit(0) } if plugins {fmt.Println(caddy.DescribePlugins())os.Exit(0) } // Get Corefile input corefile, err := caddy.LoadCaddyfile(serverType) // Start your engines instance, err := caddy.Start(corefile) // Twiddle your thumbs instance.Wait()}coredns的启动流程还是比较简洁的,, 可以看到coredns没有太多参数选项, 除了打印插件列表, 显示版本的命令参数之外 , 就是启动流程了 , 而启动流程概括起来也不负载 , 加载配置文件 , 基于配置文件启动 , 但是在在深入caddy.LoadCaddyfile和caddy.Start之前要先看看在此之前运行的init方法.
// corednsserverregister.gofunc init() { caddy.RegisterServerType(serverType, caddy.ServerType{Directives: func() []string { return Directives },DefaultInput: func() caddy.Input {// 如果配置文件找不到会加载配置了whoami, log插件的配置文件return caddy.CaddyfileInput{Filepath:"Corefile",Contents:[]byte(".:" + Port + " {nwhoaminlogn}n"),ServerTypeName: serverType,}},NewContext: newContext, })}因为caddy是一个http/s web服务器 , 而不是一个dns服务器 , 所以我们需要在调用caddy启动流程之前注入一个用于dns的ServerType , 后续创建的Server等对象都是基于此 , 并且这个ServerType设置了一个静态的配置文件".:" + Port + " {nwhoaminlogn}n", 也就是说在没有指定配置文件路径以及本地没有Corefile的情况下 , 还是能够启动一个默认的dns服务器 , 这个服务加载了whoami, log两个插件 。
加载配置文件在入口函数可以知道, 注入了以下两个配置文件加载函数
// 该函数比较简单, 就是将函数追加到caddyfileLoaders切片中caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))// 如果caddyfileLoaders切片中所有函数都没有加载到配置文件, 就会使用默认加载函数caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))前者的加载函数就是读取参数-conf指定的配置文件 。
后者就是读取本地的Corefile, 如果存在的话 。
confLoader的代码如下:
func confLoader(serverType string) (caddy.Input, error) { if conf == "" {return nil, nil } if conf == "stdin" {return caddy.CaddyfileFromPipe(os.Stdin, serverType) } contents, err := os.ReadFile(filepath.Clean(conf)) return caddy.CaddyfileInput{Contents:contents,Filepath:conf,ServerTypeName: serverType, }, nil}可以看到逻辑比较简单, 如果指定了-conf参数就通过参数值找到对应的配置文件并返回
因为我们没有指定-conf参数, 所以调用默认加载函数 。
LoadCaddyfile加载逻辑如下:
// vendorgithub.comcorednscaddycaddy.gofunc LoadCaddyfile(serverType string) (Input, error) { // 通过注册的配置文件加载函数, 默认加载函数加载配置文件 cdyfile, err := loadCaddyfileInput(serverType)// 函数找不到就用 if cdyfile == nil {cdyfile = DefaultInput(serverType) } return cdyfile, nil}// vendorgithub.comcorednscaddyplugins.gofunc loadCaddyfileInput(serverType string) (Input, error) { var loadedBy string var caddyfileToUse Input// 这里只注册一个从命令行参数加载的loader// caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))// 因为没有指定参数, 所以没哟配置文件会加载 for _, l := range caddyfileLoaders {cdyfile, err := l.loader.Load(serverType) }// 继而调用默认的加载函数 if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil {cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType)if cdyfile != nil {loaderUsed = defaultCaddyfileLoadercaddyfileToUse = cdyfile} } return caddyfileToUse, nil}


推荐阅读