上文分析了docker client段对于docker run命令的处理,client将create和start命令发送给daemon;
本文主要分析daemon的启动过程,以及对create和start命令的处理;
源码阅读基于docker version1.17.05.x。
1. docker daemon的入口main
1.1 源码
docker daemon的main函数位于moby/cmd/dockerd/docker.go,代码的主要内容是:
func main() {
//看有没有注册的初始化函数,如果有,就直接return,这个函数似乎与dockerinit有关
if reexec.Init() {
return
}
//构建一个docker服务器命令行接口对象,命令行接口包含了docker服务器所有可以执行的命令,并通过每一个命令结构体对象中的Run等成员函数来具体执行
cmd := newDaemonCommand()
cmd.Execute()
newDaemonCommand()
调用的地方:1.cmd/dockerd/docker.go的main函数;2.cmd/docker/docker.go的newDockerCommand()
中也有调用,这里的调用是为了启动daemon。
1.2 流程图
2. newDaemonCommand()
2.1 newDaemonCommand()主代码
newDaemonCommand()包含daemon初始化的流程,主要代码为:
func newDaemonCommand() *cobra.Command {
//获取配置信息
opts := daemonOptions{
daemonConfig: config.New(),
common: cliflags.NewCommonOptions(),
}
//docker daemon命令行对象,与docker client中的相似
cmd := &cobra.Command{
Use: "dockerd [OPTIONS]",
Short: "A self-sufficient runtime for containers.",
SilenceUsage: true,
SilenceErrors: true,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.flags = cmd.Flags()
//daemon command执行时,执行该函数
return runDaemon(opts)
},
}
cli.SetupRootCommand(cmd)
//解析命令
flags := cmd.Flags()
//设置docker daemon启动的时候是否使用了version等一些命令
flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit")
flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file")
opts.common.InstallFlags(flags)
installConfigFlags(opts.daemonConfig, flags)
installServiceFlags(flags)
return cmd
}
下面将分析runDaemon(opts)函数。
2.2 runDaemon(opts)
runDaemon()是在daemon command使用时被调用,主要代码为:
func runDaemon(opts daemonOptions) error {
daemonCli := NewDaemonCli() //创建daemon客户端对象
daemonCli.start(opts) ////启动daemonCli
}
它包括了NewDaemonCli()和daemonCli.start(opts)两个部分,下面将详细分析这两个部分。
2.2.1 NewDaemonCli()
NewDaemonCli()
创建一个DaemonCli结构体对象,该结构体包含配置信息,配置文件,参数信息,APIServer,Daemon对象,authzMiddleware(认证插件),代码如下:
type DaemonCli struct {
*config.Config //配置信息
configFile *string //配置文件
flags *pflag.FlagSet //flag参数信息
api *apiserver.Server //APIServer:提供api服务,定义在docker/api/server/server.go
d *daemon.Daemon //Daemon对象,结构体定义在daemon/daemon.go文件中
authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins
}
其中APIServer在接下来的daemonCli.start()实现过程中具有非常重要的作用。apiserver.Server的结构体为:
// Server contains instance details for the server
type Server struct {
cfg *Config//apiserver的配置信息
servers []*HTTPServer//httpServer结构体对象,包括http.Server和net.Listener监听器。
routers []router.Router//路由表对象Route,包括Handler,Method, Path
routerSwapper *routerSwapper//路由交换器对象,使用新的路由交换旧的路由器
middlewares []middleware.Middleware//中间件
}
2.2.2 daemonCli.start(opts)
daemonCli.start(opts)的主要代码以实现如下:
func (cli *DaemonCli) start(opts daemonOptions) (err error) {
//1.设置默认可选项参数
opts.common.SetDefaultOptions(opts.flags)
//2.根据opts对象信息来加载DaemonCli的配置信息config对象,并将该config对象配置到DaemonCli结构体对象中去
cli.Config, err = loadDaemonCliConfig(opts)
//3.对DaemonCli结构体中的其它成员根据opts进行配置
cli.configFile = &opts.configFile
cli.flags = opts.flags
//4.根据DaemonCli结构体对象中的信息定义APIServer配置信息结构体对象&apiserver.Config(包括tls传输层协议信息)
serverConfig := &apiserver.Config{
Logging: true,
SocketGroup: cli.Config.SocketGroup,
Version: dockerversion.Version,
EnableCors: cli.Config.EnableCors,
CorsHeaders: cli.Config.CorsHeaders,
}
//5.根据定义好的&apiserver.Config新建APIServer对象,并赋值到DaemonCli实例的对应属性中
api := apiserver.New(serverConfig)
cli.api = api
for i := 0; i < len(cli.Config.Hosts); i++ {
//6.解析host文件及传输协议(tcp)等内容
cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i])
protoAddr := cli.Config.Hosts[i]
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
proto := protoAddrParts[0]
addr := protoAddrParts[1]
//7.根据host解析内容初始化监听器listener.Init()
ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
ls = wrapListeners(proto, ls)
//8.为建立好的APIServer设置我们初始化的监听器listener,可以监听该地址的连接
api.Accept(addr, ls...)
}
//9.根据DaemonCli.Config.ServiceOptions来注册一个新的服务对象
registryService := registry.NewService(cli.Config.ServiceOptions)
//10.根据DaemonCli中的相关信息来新建libcontainerd对象
containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)
/*
11.设置信号捕获,进入Trap()函数,可以看到os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE
四种信号,这里传入一个cleanup()函数,当捕获到这四种信号时,可以利用该函数进行shutdown善后处理
*/
signal.Trap(func() {
cli.stop()
<-stopc // wait for daemonCli.start() to return处于阻塞状态,等待stopc通道返回数据
})
//12.提前通知系统api可以工作了,但是要在daemon安装成功之后
preNotifySystem()
//13.根据DaemonCli的配置信息,注册的服务对象及libcontainerd对象来构建Daemon对象
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
//14.新建cluster对象
c, err := cluster.New(cluster.Config{
Root: cli.Config.Root,
Name: name,
Backend: d,
NetworkSubnetsProvider: d,
DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr,
RuntimeRoot: cli.getSwarmRunRoot(),
})
d.SetCluster(c)
//15.重启Swarm容器
d.RestartSwarmContainers()
//16.将新建的Daemon对象与DaemonCli相关联
cli.d = d
//17.初始化路由器
initRouter(api, d, c)
//18.新建goroutine来监听apiserver执行情况,当执行报错时通道serverAPIWait就会传出错误信息
go api.Wait(serveAPIWait)
//19.通知系统Daemon已经安装完成,可以提供api服务了
notifySystem()
//20.等待apiserver执行出现错误,没有错误则会阻塞到该语句,直到server API完成
errAPI := <-serveAPIWait
//21.执行到这一步说明,serverAPIWait有错误信息传出(一下均是),所以对cluster进行清理操作
c.Cleanup()
//22.关闭Daemon
shutdownDaemon(d)
//23.闭libcontainerd
containerdRemote.Cleanup()
}
这部分代码实现了daemonCli、apiserver的初始化,其中apiserver的功能是处理client段发送请求,并将请求路由出去。所以initRouter(api, d, c)
中包含了api路由的具体实现,将会在下一篇博客中分析。下面再分析步骤13中的daemon.NewDaemon()
2.2.2.1 从daemon.NewDaemon()分析daemon启动过程中的initNetworkController()
从daemon.NewDaemon()到initNetworkController()的过程为:daemon.NewDaemon()—>d.restore()—>daemon.initNetworkController()
首先,从NewDaemon()中分析,主要代码如下:
func NewDaemon(config *config.Config, registryService registry.Service, containerdRemote libcontainerd.Remote, pluginStore *plugin.Store) (daemon *Daemon, err error) {
...
if err := d.restore(); err != nil {
return nil, err
}
...
}
再到d.restore()的调用,主要代码如下,
func (daemon *Daemon) restore() error {
...
daemon.netController, err = daemon.initNetworkController(daemon.configStore, activeSandboxes)
...
}
下面详细分析daemon启动过程中的网络初始化daemon.initNetworkController()
,代码位于moby/daemon/daemon_unix.go#L727#L774。
函数主要做了以下两件事情:
- 初始化controller
- 初始化null(none的驱动)/host/bridge三个内置网络
主要代码为:
func (daemon *Daemon) initNetworkController(config *config.Config, activeSandboxes map[string]interface{}) (libnetwork.NetworkController, error) {
netOptions, err := daemon.networkOptions(config, daemon.PluginStore, activeSandboxes)
//生成controller对象
controller, err := libnetwork.New(netOptions...)
// Initialize default network on "null"
if n, _ := controller.NetworkByName("none"); n == nil {
_, err := controller.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(true))
}
// Initialize default network on "host"
if n, _ := controller.NetworkByName("host"); n == nil {
_, err := controller.NewNetwork("host", "host", "", libnetwork.NetworkOptionPersist(true))
}
// Clear stale bridge network
n, err := controller.NetworkByName("bridge"); err == nil {
n.Delete();
}
if !config.DisableBridge {
// Initialize default driver "bridge"
err := initBridgeDriver(controller, config); err != nil {
return nil, err
}
} else {
removeDefaultBridgeInterface()
}
return controller, nil
}
在部分代码会与后文study5.docker源码阅读之五—daemon端对于container start的处理有关系。
结语
本文介绍了docker daemon到serverapi的初始化过程,下文将分析serverapi如何将docker run命令路由到具体的执行函数。