Cordns source code analysis: startup process

Original link: https://www.kawabangga.com/posts/4728

The startup process of Cordns is not very easy to read, because it is essentially a caddy server, which is developed based on caddy . That is to say, it injects its own logic into caddy, which is equivalent to using caddy as a framework. The actual startup process is actually the startup process of caddy. Coredns will not see a clear preparation to start, Start The code like server, and most of it is the registration logic to the caddy code. Similar to the relationship between Openresty and Nginx. This makes it difficult to read the source code at first, and it is necessary to figure out which things are Coredns and which interfaces are caddy. There are not many caddy documents, and the caddy used by Coredns is no longer official, but a version maintained by itself , which also increases the difficulty of reading. So I sorted out the startup process for this blog, hoping to clarify its logic.

Before starting to analyze the source code, the preparations that readers need to have are:

  1. The basic syntax of Golang, but I found that Golang is very simple, reading https://gobyexample.com/ is enough;
  2. Know the basic working principle of DNS, know the difference between DNS Resolver, DNS Root Server, DNS TLS Server , be able to distinguish the roles of Cloudflare 1.1.1.1 service and AWS Route53, know what kind of Server Coredns is (in fact, any one of Coredns can do);
  3. You need to read the documentation of Coredns first to know how to configure it;

After completing these, you can basically know how Coredns works. Before starting to enter the source code, you can think about it through the known documentation: if Coredns needs to complete the known functions, what needs to be done?

Guessed implementation module

Because it has been introduced in the previous blog, Coredns is actually based on a caddy server, so we can guess that Coredns must complete the following things:

  1. Need to be able to parse Corefile, which is the configuration file of Coredns;
  2. A Plugin Chain needs to be maintained, because the essence of the way Coredns works is one Plugin call;
  3. Plugin needs to be initialized;
  4. Coredns is based on caddy, so Coredns must have a place to tell caddy its own logic for processing requests, listening ports, etc.;

You may also think of other functions, so in the next source code reading, you can try to find these logics in the source code and see how Cordns is implemented.

The entry program of Coredns is coredns.go , which has only two parts, the first part is import , and the second part is main .

 import ( _ "github.com/coredns/coredns/core/plugin" // Plug in CoreDNS. "github.com/coredns/coredns/coremain" )  func main() { coremain.Run() }

Golang’s import does not simply introduce the name of the package. If there is an init() in the package, this init() will be executed . So the import here actually does a lot of things.

register

When import a package, the imported package will also import other packages, and eventually, the init() of these packages will be executed.

The import link and the final executed init are as follows, some of which are not important, such as caddy.init() are ignored in the figure.

There are two important registration parts. The first part is to import all Plugins and execute init() in these Plugins. The import list of this Plugin is actually generated, as mentioned in the previous blog .

Because Golang is a statically compiled language, to modify the list of supported Plugins, you must recompile Coredns:

  1. Modify the plugin.cfg file
  2. Use the directives_generate.go in the warehouse to generate the zplugin.go source code in Chinese, then the import list is updated
  3. Recompile Coredns

What the init() in Plugin does is very simple, that is to call coredns.plugin.Register function, register the Plugin with caddy, and tell caddy two things:

  1. The ServerType supported by this Plugin is DNS
  2. The Action of Plugin, the initialization function. It is only registered here, and it has not been run.

The second part is the init function in register . The main action is to use the caddy interface caddy.RegisterServerType to register a new Server type.

When registering, tell caddy according to the interface of caddy:

  1. Directives: What are the Directives supported by the new ServerType;
  2. DefaultInput: When there is no configuration file input, what is the default configuration file, this option is not important;
  3. NewContext: This is the most important, how to generate Context corresponding to this ServerType, Context is the main entry for managing Config instances later;

There is also an init() in coremain , which mainly handles the command line parameters when Coredns starts, and then registers caddyFileLoader , which is a function to read (note that it has not been parsed) the configuration file.

At this point, the import phase is over. Most of the work done so far is to register the function with caddy and tell caddy what to do, but the function does not run.

Then go back to the second part of the coredns.go file: coremain.Run() .

start up

data structure

The general process of startup is to initialize various Instance, Context, and Config, and then start the Server. The data structures initialized in different stages are different. To understand this process, it is best to first understand the relationship between these data structures. The main data structures and their mutual application are shown in the figure below. Yellow ones represent data structures in caddy, and green ones represent data structures in Coredns.

Among them, caddy.Controller and caddy.Instance are the structures defined in caddy, mainly used by caddy server, and I don’t see many places in Coredns.

dnsContext implements caddy.context , internally saves the shutdown with Config , implements the InspectServerBlocks and MakeServers interfaces defined in caddy, and is a main data structure. There is only one global corresponding to caddy.Instanse , which is created by caddy.

Config is completely the internal structure of Coredns. It is the most important structure. It stores the Plugin list. When processing DNS requests, the Plugin is mainly called through Config . For each Corefile configuration file, ServerBlock and Zone will have A Config instance.

start process

The reason why the startup process of Coredns is complicated, one reason is that the real process is in caddy instead of in Coredns, and the other reason is the various logic that follows, which is essentially defined by Coredns, and then registered in caddy, caddy The code executed is actually written by Cordns.

So in order to explain the startup process, I first drew a picture. The job of the startup process is to initialize the various data structures described above. In the figure below, the above is the data structure, and the following is the execution flow of the code. In the figure below, the solid line represents the actual calling relationship, and the dashed line represents that this code initializes the data mechanism instance.

Starting from coremain.Run, the logic here is very simple. First, the registered caddyFileLoader mentioned above is executed. Then caddy.Start is called, and caddy is responsible for the main startup process.

Below we find a depth-first description of this startup process.

Caddy first creates an Instnace and then calls ValidateAndExecuteDirectives.

In ValidateAndExecuteDirectives, according to the ServerType string in the caddy file we loaded earlier, we get the DNS Server Type, which we registered in the init process mentioned above.

Then execute loadServerBlocks, which is a built-in function of caddy. The caddy file is parsed according to the Directives returned by ServerType.Directives we registered before. At this time, it is parsed as an ordinary caddy file without the parsing logic of coredns. At this time, the original caddy file is parsed into Tokens. It can also be seen that the configuration file of Coredns must be in the same format as Corefile and caddy, and follow the same grammar specification, because the parser is used in caddy (if the version of Coredns fork has not been modified).

The next step is to call the second important method registered by ServerType: NewContext creates a context, the actual type is dnsContext .

Then call context.InspectServerBlocks . This logic is also implemented in dnsContext . Mainly do two things, one is to check whether the Corefile is legal and whether there are duplicate definitions. Then create dnsserver.Config , Config is mainly associated with dnsContext, we get this Config mainly through Context. For example, in config.go, the method of getting the corresponding Config through the Controller is implemented :

 // GetConfig gets the Config that corresponds to c. // If none exist nil is returned. func GetConfig(c *caddy.Controller) *Config { ctx := c.Context().(*dnsContext) key := keyForConfig(c.ServerBlockIndex, c.ServerBlockKeyIndex) if cfg, ok := ctx.keysToConfigs[key]; ok { return cfg } // we should only get here during tests because directive // actions typically skip the server blocks where we make // the configs. ctx.saveConfig(key, &Config{ListenHosts: []string{""}}) return GetConfig(c) }

In fact, the Instance is found through the Controller, and then the Context ( c.Context() logic) on the Instance is found. Then use a Utils to find the corresponding Config from the Context. This implementation is also completely consistent with the above figure.

As mentioned above, each Key in each Server Block will have a corresponding Config, so how do we find the corresponding one with so many Configs?

In fact, it is the logic of keyForConfig . The context records the combination of Server Block Index + Server Block Key Index and the one-to-one correspondence with Config.

Going back to the logic of ValidateAndExecuteDirectives, after calling InspectServerBlocks, get the Action for each Directive and execute it. That is, initialize each Plugin.

After completion, the logic returns to caddy.Start , and the second important interface implemented in dnsContext will be called: MakeServers . Initialize the Server in Coredns.

The last step is to startServers , and the actual logic here goes back to the interface implemented by Coredns. It feels relatively clear, and there is nothing difficult to understand. It mainly implements two interfaces, one is Listen and the other is Serve .

Then you can start processing requests.

start processing the request

Finally, Server implements the Serve method of caddy.Server, which mainly matches the corresponding Config according to the zone in the DNS query request, and then saves the PluginChain in the Config , and completes the processing of the request by calling h.pluginChain.ServeDNS .

The code analysis of this article is here, and the startup process is complete. This article tries to paste as little code as possible, trying to clarify the startup process. The specific code should be found if you compare the pictures and explanations in this article, and the rest should not be difficult to understand. If you find errors or have questions, welcome to communicate in the comment area.

This article is based on the Coredns source code Commit: 3288b111b24fbe01094d5e380bbd2214427e00a4

The corresponding latest tag is: v1.8.6-35-gf35ab931

The post Cordns Source Code Analysis: The Startup Process first appeared on Kawabanga! .

This article is reprinted from: https://www.kawabangga.com/posts/4728
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment