Docker Source Analysis (v): Creation of Docker server

Source: Internet
Author: User
Tags vars

Introduction to 1.Docker Server

Docker server is an important part of Docker daemon in the Docker architecture. The main function of Docker server is to accept requests sent by the user through the Docker client and follow the appropriate routing rules for routing distribution.

At the same time, Docker server has excellent user-friendliness, and the support of multiple communication protocols greatly reduces the Docker user's threshold for Docker use. In addition, the Docker server design implements a detailed and clear API interface for Docker users to choose from. In terms of communication security, Docker Server can provide secure Transport layer protocol (TLS) to ensure encrypted transmission of data. In concurrent processing, Docker Daemon uses goroutine in Golang, which greatly improves the concurrency processing capability of the server.

This article is the fifth--docker server creation for the Docker Source Analysis series.

2. Docker Server source code Analysis content arrangement

This article will analyze the creation of Docker server from the source point of view, the main contents of the analysis are as follows:

(1) The creation and execution process of the job "Serveapi", representing the creation of the Docker server;

(2) In-depth analysis of the execution process of the job "SERVEAPI";

(3) Docker Server creates a listener and serves the process analysis of the API.

3.Docker Server creation Process

"Docker source Analysis (iii): Docker daemon Launch" mainly analyzes the launch of Docker Daemon, and at the end of the Maindaemon () run, the job of creating and running the name "SERVEAPI" is implemented. The role of this link is to let Docker daemon provide API access services. Essentially, this is the creation and operation of Docker server in the Docker architecture.

From a process perspective, the Docker server is created and run, representing the entire life cycle of the "SERVEAPI" job: Creating a job instance job, configuring the job environment variable, and finally executing the job. This chapter is divided into three sections to analyze these three different stages.

3.1 Create a job named "Serveapi"

Job is the most basic task execution unit within the engine in the Docker architecture, so the task of creating Docker server is no exception and needs to be represented as an executable job. In other words, to create a Docker Server, you must create a corresponding job. The specific job creation form is located in the./docker/docker/daemon.go, as follows:

Job: = Eng. Job ("Serveapi", flhosts ...)

The above code uses engine instance eng to create an instance of the job type job,job named "Serveapi", and initializes Job.args with the value of Flhost. The role of Flhost is to configure the protocol that the Docker server listens to and the address of the listener.

It should be noted that "Docker source Analysis (iii): Docker daemon Launch" Maindaemon () in the implementation process, in the loading Builtins link has already registered to the Eng object key "Serveapi" handler, And the value of the handler is API.SERVEAPI. Therefore, when you run a job named "Serveapi", the handler of the job, the API, is executed. Serveapi.

3.2 Configuring the Job environment variable

When the job instance job is created, Docker daemon configures the environment parameters for the job. During the job implementation, there are two ways to configure the parameters for the job: first, when the job instance is created, the Args property of the job is initialized directly with the specified parameters, and second, when the job is created, the specified environment variable is added to the job. The following code implements the configuration environment variables for the created job:

Job. Setenvbool ("Logging", true) job. Setenvbool ("Enablecors", *flenablecors) job. Setenv ("Version", dockerversion. VERSION) job. Setenv ("Socketgroup", *flsocketgroup) job. Setenvbool ("Tls", *fltls) job. Setenvbool ("Tlsverify", *fltlsverify) job. Setenv ("Tlsca", *flca) job. Setenv ("Tlscert", *flcert) job. Setenv ("Tlskey", *flkey) job. Setenvbool ("Bufferrequests", True)

For the above configuration, the following table summarizes the environment variables:

Environment variable Name

Flag parameter

Default value

Action Value

Logging

True

Using log output

Enablecors

Flenablecors

False

To provide cors headers in the remote API

Version

Show Docker version number

Socketgroup

Flsocketgroup

"Docker"

Assigning user group names to UNIX domain sockets in daemon mode

Tls

Fltls

False

Using the TLS secure transport protocol

Tlsverify

Fltlsverify

False

Using TLS and verifying the remote client

Tlsca

Flca

Specify the CA file path

Tlscert

Flcert

TLS certificate file path

Tlskey

FlKey

TLS key file path

Bufferrequest

True

Caching Docker client requests

3.3 Running the job

Configure the job's environment variables, then execute the job's run function, the implementation code is as follows:

If err: = job. Run (); Err! = Nil {log. Fatal (ERR)}

In the Eng object has registered the key is "Serveapi" handler, so when running the job, the value of this handler is executed, the corresponding handler value is API.SERVEAPI. At this point, the life cycle of the job named "Serveapi" is complete. The following is an in-depth analysis of job Handler,api. SERVEAPI implementation of details.

4.SERVEAPI Operating Process

This chapter provides an in-depth analysis of the part of Docker server's API service, which analyzes the architecture design and implementation of Docker server from a source point of view.

As a server for listening requests and processing requests, Docker server first clarifies how many kinds of communication protocols it needs to serve, and in the framework of the C/s mode of Docker, there are three types of protocols available: TCP protocol, Unix socket form, and FD form. The Docker server then creates different server-side instances, depending on the protocol. Finally, in different service-side instances, the corresponding routing module, the listening module, and the handler of processing the request are created to form a complete server.

The "Serveapi" job will execute the API at run time. The SERVEAPI function. The function of Serveapi is to cycle through all of the current supported communication protocols for Docker Daemon and to create a goroutine for each protocol, with a server side configured to serve HTTP requests within the Goroutine. The SERVEAPI code implementation is located in the./docker/api/server/server.go#l1339:

First, judge the job. If the length of the Args is 0, because the Job.args is initialized by flhosts, the length of the Job.args is 0, indicating that there is no protocol and address for the Docker server to listen, the parameter is incorrect, and the error message is returned. The code is as follows:

If Len (job. Args) = = 0 {return job. Errorf ("Usage:%s proto://addr [proto://addr ...]", job. Name)}

Second, define two variables, Protoaddrs represents the content of flhosts, and Cherror defines the error type channel pipeline with the same length as Protoaddrs, and the role of Cherror is described later in this article. It also defines the Activationlock, which is a channel used to synchronize the two job executions of "Serveapi" and "acceptconnections". In the implementation of the SERVEAPI runtime servefd and Listenandserve, because Activationlock is not content in this channel, and when running the "acceptionconnections" job, First notifies the INIT process that Docker daemon has started and shuts down Activationlock, and Serveapi continues to execute. It is because of the existence of Activationlock, to ensure that "acceptconnections" the job of the operation to notify the "Serveapi" to open the official service to the API effect. The code is as follows:

var (protoaddrs = job. Argscherrors   = Make (chan error, Len (Protoaddrs))) Activationlock = Make (chan struct{})

Third, traverse Protoaddrs, which is job. Args, each of which is separated by the string "://", if the length of the protoaddrparts is not 2, then the Protocol plus address of the written form is incorrect, return job error, if not 2, then split to get the protocol in each item protoaddrpart[0] With address protoaddrparts[1]. Finally, create a goroutine to perform the listenandserve operation. Goroutine operation is mainly dependent on the operation result of Listenandserve (Protoaddrparts[0], protoaddrparts[1], job), if the error is returned, there is an error in Cherrors, The current Goroutine execution is complete, and if no error is returned, the Goroutine continues to operate and provides continuous service. One of the most important is the implementation of Listenandserve, which specifically implements how to create listener, router, and server, and coordinates the work of the three, ultimately serving the API request. The code is as follows:

For _, Protoaddr: = Range Protoaddrs {protoaddrparts: = strings. SPLITN (PROTOADDR, "://", 2) If Len (protoaddrparts)! = 2 {return job. Errorf ("Usage:%s proto://addr [proto://addr ...]", job. Name)}go func () {log. Infof ("Listening for HTTP on%s (%s)", Protoaddrparts[0], protoaddrparts[1]) cherrors <-Listenandserve ( Protoaddrparts[0], protoaddrparts[1], Job)} ()}

Finally, according to the value of cherrors, if there is an error in the channel cherrors, the function returns SERVEAPI, and if there is no error content, the loop is blocked. The code is as follows:

For I: = 0; I < Len (Protoaddrs); i + = 1 {err: = <-cherrorsif Err! = Nil {return job. Error (ERR)}}return engine. Statusok

At this point, Serveapi's running process has been analyzed in detail, the core part of the implementation of Listenandserve, the next chapter began to deepen.

5.ListenAndServe implementations

The function of Listenandserve is to enable Docker server to listen to a specified address, accept requests on that address, and forward the above request route to the appropriate handler function handler. From an implementation perspective, Listenandserve primarily implements a server that serves HTTP, which listens for requests on a specified address and makes a specific protocol check on the request, ultimately completing the routing and distribution of the request. The code implementation is located in./docker/api/server/server.go.

The implementation of Listenandserve can be divided into the following 4 parts:

(1) Create a router routing instance;

(2) Create listener listening instance;

(3) Create HTTP. Server;

(4) Start the API service.

Listenandserve execution processes such as:

Figure 5.1 Listenandserver Execution flowchart

The following will follow the Listenandserve execution flowchart one by one for an in-depth analysis of each section.

5.1 Creating a router routing instance

First, a router routing instance is created through createrouter in the implementation of Listenandserve. The code is implemented as follows:

RR, err: = Createrouter (Job. Eng, job. Getenvbool ("Logging"), job. Getenvbool ("Enablecors"), job. Getenv ("Version")) if err! = Nil {return err}

The implementation of Createrouter is located in./docker/api/server/server.go#l1094.

Creating a router Routing instance is an important part of routing instances that are responsible for routing and distributing requests by Docker server. In the implementation process, the main two steps: First, create a new router routing instance, and second, add a route record for the router instance.

5.1.1 Create an empty route instance

Essentially, Createrouter implements a powerful router and dispatcher through the package Gorilla/mux. As follows:

r: = Mux. Newrouter ()

The Newrouter () function returns a completely new router instance of R. When creating an router instance, assign values to the two properties of the Router object, both Nameroutes and Keepcontext. Where the Namedroutes property is a map type, where key is of type string, value is the route routing record type, and the Keepcontext property is false, which means that after the Docker server finishes processing the request, Clears the requested content and does not store operations on the request. The code is located in./docker/vendor/src/github.com/gorilla/mux/mux.go#l16, as follows:

Func newrouter () *router {return &router{namedroutes:make (Map[string]*route), Keepcontext:false}}

As can be seen, the above code returns a MUX type. Router. Mux. Router will match the accepted request through a series of registered routing records, first to find the corresponding route record through the requested URL or other condition, and invoke the execution handler in this route record. Mux. Router has the following features:

    • The request can be based on the URL's hostname, path, path prefix, shemes, request header and request value, HTTP request method type, or using a custom matching rule;
    • The URL host name and path can have a regular expression to represent;
    • The registered URL can be directly used, or can be retained, so as to ensure the maintenance of resource use;
    • Routing records can be used with sub-routers: If the parent Route records match, the nested records will only be used for testing. Sub-routing is helpful when designing routing records within a group to share the same matching criteria, such as hostname, path-strength prefix, or other repeating attributes;
    • Mux. The router implements the Http.handler interface and is therefore compatible with the standard Http.servemux.
5.1.2 adding Route records

Router Routing instance r is created, the next step is to add the required routing records for Router instance R. The routing record stores the information used to match the request, including the matching rules for the request, and the handler execution entry after the match.

Back in the Createrouter implementation code, first determine if the Docker daemon startup process has the debug mode turned on. When you start Docker Daemon with the Docker executable file, when parsing the flag parameter, if the value of Fldebug is false, the debug environment is not required, and if the value of Fldebug is true, the need for Docker Daemon Add the Debug function. The specific code is implemented as follows:

If OS. Getenv ("DEBUG")! = "" "{AttachProfiler (R)}

The function of Attachproiler (R) is to add debug-related routing records for route instance R, which is located at./docker/api/server/server.go#l1083, as follows:

Func AttachProfiler (Router *mux. Router) {Router. Handlefunc ("/debug/vars", Expvarhandler) router. Handlefunc ("/debug/pprof/", Pprof. Index) router. Handlefunc ("/debug/pprof/cmdline", Pprof. CmdLine) router. Handlefunc ("/debug/pprof/profile", Pprof. Profile) router. Handlefunc ("/debug/pprof/symbol", Pprof. Symbol) router. Handlefunc ("/debug/pprof/heap", Pprof. Handler ("Heap"). Servehttp) router. Handlefunc ("/debug/pprof/goroutine", Pprof. Handler ("Goroutine"). Servehttp) router. Handlefunc ("/debug/pprof/threadcreate", Pprof. Handler ("Threadcreate"). Servehttp)}

Analyzing the above source, you can find that Docker server uses two packages to complete debug-related work: Expvar and Pprof. Package Expvar provides a standardized interface for public variables so that these public variables can be accessed via HTTP in the form of "/debug/vars", which is transmitted in JSON format. Package Pprof exposes the analysis data from the Docker server runtime to the "/debug/pprof/" URL. These run-time information includes the list of available information, the command line information that is running, CPU information, program function reference information, servehttp the function (heap usage, goroutine usage, and thread usage).

Back in the Createrouter function implementation, after all the work of the debug function was completed, Docker Docker created an object m of the map type to initialize the routing record for route instance R. The simplified M object, the code is as follows:

M: = map[string]map[string]httpapifunc{"GET": {... "/images/{name:.*}/get":           getimagesget,......}, "POST": {... "/ Containers/{name:.*}/copy ":    postcontainerscopy,}," DELETE ": {"/containers/{name:.*} ": Deletecontainers,"/ images/{name:.*} ":     deleteimages,}," Options ": {" ": Optionshandler,},}

The type of object M is map, where key is a string type, which represents the request type of HTTP, such as "GET", "POST", "DELETE", and so on, value is another map type, which represents the map of the URL and the execution handler. In the second map type, key is a string type, representing the request Url,value as the Httpapifunc type, representing the specific execution handler. Where the Httpapifunc type is defined as follows:

Type Httpapifunc func (Eng *engine. Engine, version version. Version, W http. Responsewriter, R *http. Request, VARs map[string]string) error

Completes the definition of the object m, and then Docker server adds routing records for route instance r through the object m. Object m request method, request URL and request processing handler these three things can build a route record for object R. Implementation code. As follows:

For method, Routes: = range m {for route, FCT: = range Routes {log. DEBUGF ("Registering%s,%s", method, route) Localroute: = routelocalfct: = Fctlocalmethod: = methodf: = Makehttphandler (eng , logging, Localmethod, Localroute, LOCALFCT, Enablecors, version. Version (dockerversion)) if Localroute = = "" {r.methods (Localmethod). Handlerfunc (f)} else {R.path ("/v{version:[0-9.] +} "+ Localroute). Methods (Localmethod). Handlerfunc (f) r.path (Localroute). Methods (Localmethod). Handlerfunc (f)}}}

The above code, in the first layer of the loop, according to the HTTP request method Division, the request method to obtain the respective routing records, the second loop, according to the URL of the matching request, to obtain the corresponding execution handler URL. In a nested loop, returns an executing function f through Makehttphandler. In the returned function, logging information, cors information (cross-domain resource sharing protocol), and version information are involved. The following example illustrates the implementation of Makehttphandler, which can be seen from Object m, where the request URL is "/info" for a "GET" request, and the request handler to "GetInfo". The specific code for executing Makehttphandler is implemented as follows:

Func Makehttphandler (Eng *engine. Engine, logging bool, Localmethod string, localroute string, Handlerfunc Httpapifunc, enablecors bool, dockerversion versi On. Version) http. Handlerfunc {return func (w http. Responsewriter, R *http. Request) {//log the Requestlog. DEBUGF ("Calling%s%s", Localmethod, Localroute) if logging {log.infof ("%s%s", R.method, R.requesturi)}if strings. Contains (R.header.get ("User-agent"), "docker-client/") {useragent: = strings. Split (R.header.get ("User-agent"), "/") If Len (useragent) = = 2 &&!dockerversion.equal (version. Version (Useragent[1])) {log. DEBUGF ("warning:client and server don t have the same version (client:%s, Server:%s)", Useragent[1], dockerversion)}}ve Rsion: = version. Version (MUX. Vars (R) ["version"]) if Version = = "" {Version = API. Apiversion}if enablecors {writecorsheaders (W, R)}if version. GreaterThan (API. apiversion) {http. Error (W, FMT. Errorf ("Client and server don't have same version (client:%s, Server:%s)", version, API. apiversion). Error (), http. Statusnotfound) return}if Err: = Handlerfunc (Eng, version, W, R, MUX). Vars (R)); Err! = Nil {log. Errorf ("Handler for%s%s returned error:%s", Localmethod, Localroute, Err) httperror (W, Err)}}}

The execution of the visible Makehttphandler returns a function func (w http) directly. Responsewriter, R *http. Request). In the implementation of this Func function, the Makehttphandler passed the logging parameter is judged, if true, the execution of the handler is displayed through the log, In addition, by makehttphandler the incoming enablecors parameter to determine whether to add cross-domain resource sharing information in the header file of the HTTP request, if True, The HTTP Header for Cors is added to the response via the Writecorsheaders function, which is located in the./docker/api/server/server.go#l1022, as follows:

Func writecorsheaders (w http. Responsewriter, R *http. Request) {W.header (). ADD ("Access-control-allow-origin", "*") W.header (). ADD ("Access-control-allow-headers", "Origin, X-requested-with, Content-type, Accept") W.header (). ADD ("Access-control-allow-methods", "GET, POST, DELETE, PUT, OPTIONS")}

The most important part of the execution is located in Handlerfunc (Eng, version, W, R, MUX). Vars (R)), as in the following code:

If err: = Handlerfunc (Eng, version, W, R, Mux.) Vars (R)); Err! = Nil {log. Errorf ("Handler for%s%s returned error:%s", Localmethod, Localroute, Err) httperror (W, err)}

For the "GET" request type, "/info" request URL request, because handler named GetInfo, that is, handlerfunc the value of this parameter is GetInfo, so the execution part directly run GetInfo (Eng, version, W, R, Mux. Vars (R)), and the specific implementation of GetInfo is located in./docker/api/server/serve.go#l269, as follows:

Func GetInfo (Eng *engine. Engine, version version. Version, W http. Responsewriter, R *http. Request, VARs map[string]string) error {W.header (). Set ("Content-type", "Application/json") Eng. Servehttp (W, R) return nil}

The execution of the above Makehttphandler is complete, returning the Func function as the execution handler corresponding to the specified URL.

After creating the handler function handler, you need to add a new route record to the routing instance. If the URL information is empty, the route record is added directly for the HTTP request method type, and if the URL is not empty, a new route record is added for the request URL path. It is important to note that when the URL is not empty and routing records are added for route instance R, the API version is considered for the problem, via R. Path ("/v{version:[0-9.") +} "+ Localroute). Methods (Localmethod). Handlerfunc (f) to achieve.

At this point, mux. The two parts of router instance R have been completely completed: Create an empty route instance R, add the corresponding route record for R, and finally return to the route instance R.

Now I-time ER route record. There are different combinations of 1083lla/mux/mux.go in the need for extra cycles,

5.2 Creating a Listener listener instance

Routing module, which completes the routing and distribution of the request, is the first important work in the Listenandserve implementation. A module is also required to perform the requested listening function. In the Listenandserve implementation, the second important task is to create a listener. Listener is a general-purpose network monitoring module for streaming protocol.

Before creating the listener, the protocol allowed by Docker server is judged, and if the protocol is in FD form, the request is served directly through SERVEFD, and if the protocol is not in FD form, it continues to execute downward.

During the execution of the program, it is necessary to determine the value of "bufferrequests" in the Environment of the "SERVEAPI" job, whether it is true or not, and if true, create an instance of listener from the package listenbuffer. Otherwise, the listener instance L is created directly from package net. The specific code is located in the./docker/api/server/server.go#l1269, as follows:

If job. Getenvbool ("Bufferrequests") {L, err = Listenbuffer. Newlistenbuffer (proto, addr, activationlock)} else {L, err = net. Listen (proto, Addr)}

Since the job "Serveapi" was created in Maindaemon (), when you add an environment variable to the job, the "Bufferrequets" is assigned true, and the listener instance is created using the package Listenbuffer.

The role of Listenbuffer is to allow Docker server to immediately listen for requests on the specified protocol address, but temporarily cache these requests until the Docker daemon starts to accept these requests. The great benefit of this design is to ensure that as many user requests as possible are received and cached before the Docker daemon is fully booted.

If the protocol is of type TCP and the environment variable TLS or tlsverify in the job is true, then the Docker server needs to support HTTPS services and needs to configure secure Transport Layer Protocol (TLS) support for Docker server. To implement the TLS protocol, you first need to establish a TLS. The config type instance tlsconfig, then loads the certificate, the authentication information in the Tlsconfig, and finally, through the Newlistener function in the packet TLS, creates the listener instance that adapts to receive the HTTPS protocol request, the code is as follows:

L = TLS. Newlistener (L, Tlsconfig)

At this point, the listener part of creating the network listener has been fully completed.

5.3 Create HTTP. Server

Docker server also needs to create a server object to run the HTTP service side. The third important task in Listenandserve implementations is to create Http.server:

Httpsrv: = http. SERVER{ADDR:ADDR, Handler:r}

Where addr is the address that needs to be monitored, R is a mux. Router routing instance.

5.4 Starting the API service

Create HTTP. Server instance, Docker server immediately launches the API service, allowing Docker server to start accepting requests on listener listener instance L and generating a new goroutine for each request to do the exclusive service. For each request, Goroutine reads the request, queries the routing record entry in the routing table, finds the matching route record, and finally invokes the execution handler in the routing record, and Goroutine returns the response information to the request after execution is complete. The code is as follows:

Return Httpsrv.serve (L)

Now that all the listenandserver processes have been analyzed, Docker server has started to service API requests for different protocols.

6. Summary

Docker server takes over all of the Docker Daemon external communications as a requested entry in the Docker daemon architecture. The standardization of communication APIs, the security of communication processes, and the concurrency of service requests are often the most important concerns of Docker users. Based on the source code, this paper analyzes the most detail implementation of Docker server. Hopefully Docker users can explore the design concept of Docker server and make better use of Docker server to create greater value.

7. References

Http://guzalexander.com/2013/12/06/golang-channels-tutorial.html

Http://www.gorillatoolkit.org/pkg/mux

http://docs.studygolang.com/pkg/expvar/

http://docs.studygolang.com/pkg/net/http/pprof/

Docker Source Analysis (v): Creation of Docker server

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.