神刀安全网

Docker源码分析之Docker Daemon创建和销毁

1.引言

Docker daemon是docker架构中运行在后台的守护进程,docker daemon架构大致分为四部分:docker server,docker engine,docker job和docker drivers。四者之间的关系大致可以分为,docker daemon通过docker server模块接收 docker client的请求,并在docker engine根据不同的请求创建出不同的docker job并执行这些job。docker engine通过docker driver完成不同的job的执行。docker server又可以细分为http server,router,以及route handlers。docker server根据不同的请求,完成不同的route转发至handler,并通过docker engine api创建不同的docker job。

本章主要介绍docker daemon的创建,初始化流程和docker daemon的销毁流程。

注:代码基于docker-1.9.0-dev

2.Docker daemon的启动流程

docker daemon的启动入口还是在docker项目的main()函数中,通过判断docker启动参数中是否包含daemon cammnd来启动docker client还是docker daemon。因此,docker daemon的启动通过docker daemon [OPTIONS]的形式启动,具体可以见docker daemon -h提示。

Docker源码分析之Docker Daemon创建和销毁

2.1 docker参数解析

在/docker/docker/docker.go文件中,定义了docker的main()函数。

main()函数中对docker flag参数解析这里不作详细分析。解析完flag参数进入handleGlobalDaemonFlag()中,判 否带有daemon cammnd,如果是docker daemon则表示启动的是docker daemon守护进程,否则创建一个docker client。

2.2 docker daemon对象创建并初始化/docker/docker/daemon.go handleGlobalDaemonFlag()中判断如果是启动daemon进程,则创建一个docker daemon对象:

func NewDaemonCli() *DaemonCli { daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true)  // TODO(tiborvass): remove InstallFlags? daemonConfig := new(daemon.Config) daemonConfig.LogConfig.Config = make(map[string]string) daemonConfig.ClusterOpts = make(map[string]string) daemonConfig.InstallFlags(daemonFlags, presentInHelp) daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)  registryOptions := new(registry.Options) registryOptions.InstallFlags(daemonFlags, presentInHelp) registryOptions.InstallFlags(flag.CommandLine, absentFromHelp) daemonFlags.Require(flag.Exact, 0)  return &DaemonCli{     Config:          daemonConfig,     registryOptions: registryOptions, } 

}

这里最终返回的是一个初始化的daemon cli对象,包含daemon confing和docker registry config,但是config值的设定在CmdDaemon()中。CmdDaemon()中根据docker daemon之后的参数,进行配置初始化。这个函数中解析的参数就是docker daemon -h看到参数,主要包含daemon的日志配置和daemon的pid文件路径,这里不进行详细的分析。

2.3 启动docker serverdocker daemon初始化过程中很重要的一项工作就是要启动docker server的http server接受docker client的请求,也在CmdDaemon()中实现,涉及的最重要的就是Host参数docker.sock文件,用于接受client端的请求。

    api, err := apiserver.New(serverConfig)     if err != nil {         logrus.Fatal(err)     } // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit // All servers must be protected with some mechanism (systemd socket, listenbuffer) // which prevents real handling of request until routes will be set. serveAPIWait := make(chan error) go func() {     if err := api.ServeAPI(); err != nil {         logrus.Errorf("ServeAPI error: %v", err)         serveAPIWait <- err         return     }     serveAPIWait <- nil }() 

2.3.1 初始化route表这里实际上只是通过goroutine一个匿名函数将docker server启动,并没有开始接受docker client的请求。在开始接受docker client请求之前,初始化docker server的两张route表:local route和network route表。关于这两张route表,前者是docker容器生命周期管理,docker images管理,docker volume等除了docker network相关的操作。后者是docker network相关的管理。

// InitRouters initializes a list of routers for the server. // Sets those routers as Handler for each server. func (s *Server) InitRouters(d *daemon.Daemon) { //创建docker容器生命周期和docker镜像管理的route表 s.addRouter(local.NewRouter(d)) //创建docker netwotk管理的的route表 s.addRouter(network.NewRouter(d))  for _, srv := range s.servers {     srv.srv.Handler = s.CreateMux() } 

}

2.3.2 设置daemon进程信号处理函数对docker daemon守护进程设置一个信号管理,用于销毁docker daemon进程并清理现场。

//捕捉信号,清理daemon现场 signal.Trap(func() {     //关闭docker server     api.Close()     //通过channel发送消息给docker server     <-serveAPIWait     //关闭daemon进程     shutdownDaemon(d, 15)     if pfile != nil {         if err := pfile.Remove(); err != nil {             logrus.Error(err)         }     } }) 

2.3.3 docker server开始接受请求完成了docker daemon的所有初始化工作之后,docker daemon启动完成。docker server开始接受docker client请求。首先,通过go channel机制向docker server发送消息。当docker server遇到不可恢复错误,销毁docker daemon进程。

// after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon //发送消息通知docker daemon notifySystem() //读取无缓存go channel开始api listen api.AcceptConnections()  // Daemon is fully initialized and handling API traffic // Wait for serve API to complete //读取无缓存channel,有不可恢复错误时,销毁docker daemon进程并清理现场 errAPI := <-serveAPIWait shutdownDaemon(d, 15) if errAPI != nil {     if pfile != nil {         if err := pfile.Remove(); err != nil {             logrus.Error(err)         }     }     logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } 

docker daemon启动完成。

3.docker daemon的销毁流程

docker daemon的销毁相对比较简单,daemon进程退出并清理现场。docker daemon守护进程退出有两种原因:

1.外部发送信号,被动销毁;

2.docker server api遇到不可恢复错误,内部主动销毁docker daemon进程

需要注意的是,docker daemon进程退出之前,会将宿主机上所有运行着的(runing and paused)docker container销毁。销毁daemon进程需谨慎。

首先看一下daemon进程退出的两种场景:

3.1 被动销毁被动销毁是值外部同步发送信号将docker daemon进程销毁。这里暂时不展开。

 signal.Trap(func() {     //关闭docker server     api.Close()     //通过channel发送消息给docker server     <-serveAPIWait     //关闭daemon进程     shutdownDaemon(d, 15)     if pfile != nil {         if err := pfile.Remove(); err != nil {             logrus.Error(err)         }     } }) 

3.2 主动销毁主动销毁是在docker server api遇到不可恢复错误时,自动将docker daemon进程销毁。docker daemon主进程和docker server协程通过channel进行通信。当docker server遇到错误时,通知到docker daemon,进行自动销毁。

errAPI := <-serveAPIWait shutdownDaemon(d, 15) if errAPI != nil {     if pfile != nil {         if err := pfile.Remove(); err != nil {             logrus.Error(err)         }     }     logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } 

3.3 进程销毁并清理现场

docker dameon进程退出之后,清理现场很简单,就是将docker.pid进程文件删除。docker daemon进程开始退出到完全退出设置了15s的超时时间。如果15s还没有完全退出,则会强制退出。

docker daemon进程销毁的时候将会判断当前宿主机上是否有docker容器。如果有docker容器正在运行进程退出过程将会稍微复杂一些,否则简单一些。下面先看看宿主机上没有docker容器的情况。 当docker宿主机上没有docker容器时,daemon进程退出时,主要完成以下清理:

3.3.1 断开数据库连接

daemon进程将容器实例的信息和容器之间的关联保存在数据库中,称之为graph database。docker daemon进程退出时,将这个数据库连接关闭。

if daemon.containerGraphDB != nil {     //关闭和graph database数据库的连接     //graph database数据库存储的是container实例和他们之间的关联     if err := daemon.containerGraphDB.Close(); err != nil {         logrus.Errorf("Error during container graph.Close(): %v", err)     } } 

3.3.2 devicemapper cleanupdocker如果使用的存储驱动是devicemapper,在docker daemon退出的时候,要清理devicemapper设备。

if daemon.driver != nil { //根据不同的联合文件系统的driver进行相应的清理,umount目录,devicemapper设备 if err := daemon.driver.Cleanup(); err != nil {     logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) } 

}

3.3.3 umount设备

umount /dev/shm

umount /dev/mqueue

这两个设备的具体作用后面在分析

3.3.4 容器销毁

当docker节点上还有docker container在运行时,daemon进程退出之前,会对这些container做最后的销毁。完成对container最后处理之后,再按照3.3.1-3的步骤,最后docker daemon进程完全退出。

容器有几中状态:runging, paused和exited。

如果当前docker节点上所有的container都已经是exited状态,则只要清理docker network,暂停docker network。

如果容器状态为running,则首先发送一个SIGTERM的信号给容器进程,如果超时(10s)未关闭,则发送KILL信号,强制销毁。

如果容器状态为paused,则处理的时候稍微复杂一点。首先明白,docker在paused一个容器是通过cgroup的freezer子系统实现的。将container的init进程添加到freezer中,将进程从系统调度中拉出。因此,在处理paused状态的容器时:

send SIGTERM signal

upaused container

send SIGTERM signal

send SIGKILL signal(maybe)

也就是说,当宿主机上存在paused状态的容器时,处理流程是最长的。

在docker v1.9.1之后,docker新增了docker network的命令,用来控制docker和docker容器的网络。如果当前宿主机上已经已经运行过docker container,则还需要清理network。

docker daemon shutdown流程图:

Docker源码分析之Docker Daemon创建和销毁

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Docker源码分析之Docker Daemon创建和销毁

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮