DOCKER源码分析2 docker daemon启动流程
上文分析了docker client段对于docker run命令的处理,client将create和start命令发送给daemon;
本文主要分析daemon的启动过程,以及对create和start命令的处理;
源码阅读基于docker 19.03。
1. docker daemon的入口main
1.1 源码
docker daemon的main函数位于/moby/cmd/dockerd/docker.go,代码的主要内容是:
funcmain(){//看有没有注册的初始化函数,如果有,就直接return。因为golang会先执行包内的 init 函数, 执行registeredInitializers[os.Args[0]],也就是driver初始化函数。ifreexec.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初始化的流程,主要代码为:
funcnewDaemonCommand()*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执行时,执行该函数returnrunDaemon(opts)},}cli.SetupRootCommand(cmd)//解析命令flags:=cmd.Flags()//设置docker daemon启动的时候是否使用了version等一些命令flags.BoolVarP(&opts.version,"version","v",false,"Print version information and quit")
////获取daemon.json配置文件
defaultDaemonConfigFile, err := getDefaultDaemonConfigFile()
flags.StringVar(&opts.configFile,"config-file",defaultDaemonConfigFile,"Daemon configuration file")opts.common.InstallFlags(flags)installConfigFlags(opts.daemonConfig,flags)installServiceFlags(flags)returncmd}
下面将分析runDaemon(opts)函数。
2.2 runDaemon(opts)
runDaemon()是在daemon command使用时被调用,主要代码为:
funcrunDaemon(optsdaemonOptions)error{daemonCli:=NewDaemonCli()//创建daemon客户端对象daemonCli.start(opts)////启动daemonCli}
它包括了NewDaemonCli()和daemonCli.start(opts)两个部分,下面将详细分析这两个部分。
2.2.1 NewDaemonCli()
NewDaemonCli()
创建一个DaemonCli结构体对象,该结构体包含配置信息,配置文件,参数信息,APIServer,Daemon对象,authzMiddleware(认证插件),代码如下:
typeDaemonClistruct{*config.Config//配置信息configFile*string//配置文件flags*pflag.FlagSet//flag参数信息api*apiserver.Server//APIServer:提供api服务,定义在docker/api/server/server.god*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 servertypeServerstruct{cfg*Config//apiserver的配置信息servers[]*HTTPServer//httpServer结构体对象,包括http.Server和net.Listener监听器。routers[]router.Router//路由表对象Route,包括Handler,Method, PathrouterSwapper*routerSwapper//路由交换器对象,使用新的路由交换旧的路由器middlewares[]middleware.Middleware//中间件}
2.2.2 daemonCli.start(opts)
daemonCli.start(opts)的主要代码以实现如下:
func(cli*DaemonCli)start(optsdaemonOptions)(errerror){//1.设置默认可选项参数opts.common.SetDefaultOptions(opts.flags)//2.根据opts对象信息来加载DaemonCli的配置信息config对象,并将该config对象配置到DaemonCli结构体对象中去 cli.Config,err=loadDaemonCliConfig(opts)//3.对DaemonCli结构体中的其它成员根据opts进行配置cli.configFile=&opts.configFilecli.flags=opts.flags//4.根据DaemonCli结构体对象中的信息定义APIServer配置信息结构体对象&apiserver.Config(包括tls传输层协议信息)
serverConfig, err := newAPIServerConfig(cli)
//这里是newAPIServerConfig内部实现
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
//开启服务端监听
hosts, err := loadListeners(cli, serverConfig)
//这里是loadListeners内部实现
fori:=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中的相关信息来新建libcontainerd对象 waitForContainerDShutdown, err := cli.initContainerD(ctx)
//下面是initContainerD的内部实现,启动containerd
logrus.Debug("Containerd not running, starting daemon managed containerd")
opts, err := cli.getContainerdDaemonOpts()
r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...)
/*
1
0
.设
置信号捕获,进入Trap()函数,可以看到os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE
四种信号,这里传入一个cleanup()函数,当捕获到这四种信号时,可以利用该函数进行shutdown善后处理
*/signal.Trap(func(){cli.stop()<-stopc// wait for daemonCli.start() to return处于阻塞状态,等待stopc通道返回数据})//11.提前通知系统api可以工作了,但是要在daemon安装成功之后preNotifySystem()//12.根据DaemonCli的配置信息,注册的服务对象及libcontainerd对象来构建Daemon对象d,err:=daemon.NewDaemon(cli.Config,registryService,containerdRemote,pluginStore)
//13.将新建的Daemon对象与DaemonCli相关联cli.d=d
//14.新建cluster对象
c, err := createAndStartCluster(cli, d)
//下面是createAndStartCluster内部实现,启动集群实例
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.初始化路由器initRouter(api,d,c)//17.新建goroutine来监听apiserver执行情况,当执行报错时通道serverAPIWait就会传出错误信息goapi.Wait(serveAPIWait)//18.通知系统Daemon已经安装完成,可以提供api服务了notifySystem()//19.等待apiserver执行出现错误,没有错误则会阻塞到该语句,直到server API完成errAPI:=<-serveAPIWait//20.执行到这一步说明,serverAPIWait有错误信息传出(一下均是),所以对cluster进行清理操作c.Cleanup()//21.关闭DaemonshutdownDaemon(d)}
这部分代码实现了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()中分析,主要代码如下(其实还有很多代码暂不分析):
funcNewDaemon(config*config.Config,registryServiceregistry.Service,containerdRemotelibcontainerd.Remote,pluginStore*plugin.Store)(daemon*Daemon,errerror){...iferr:=d.restore();err!=nil{returnnil,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,activeSandboxesmap[string]interface{})(libnetwork.NetworkController,error){netOptions,err:=daemon.networkOptions(config,daemon.PluginStore,activeSandboxes)//生成controller对象controller,err:=libnetwork.New(netOptions...)// Initialize default network on "null"ifn,_:=controller.NetworkByName("none");n==nil{_,err:=controller.NewNetwork("null","none","",libnetwork.NetworkOptionPersist(true))}// Initialize default network on "host"ifn,_:=controller.NetworkByName("host");n==nil{_,err:=controller.NewNetwork("host","host","",libnetwork.NetworkOptionPersist(true))}// Clear stale bridge networkn,err:=controller.NetworkByName("bridge");err==nil{n.Delete();}if!config.DisableBridge{// Initialize default driver "bridge"err:=initBridgeDriver(controller,config);err!=nil{returnnil,err}}else{removeDefaultBridgeInterface()}returncontroller,nil}
在部分代码会与后文study5.docker源码阅读之五—daemon端对于container start的处理有关系。
结语
本文介绍了docker daemon到serverapi的初始化过程,下文将分析serverapi如何将docker run命令路由到具体的执行函数。