1. 程式人生 > >docker run命令解析(二)之Docker daemon--container create

docker run命令解析(二)之Docker daemon--container create

上一篇文章咱們分析了docker run命令在Docker client中的處理,這一篇文章咱們分析在Docker daemon中的處理。

Docker client傳送容器管理請求,由Docker daemon接收並處理。原始碼基於Docker-ce17.09

1、所謂“執行docker”,即執行Docker daemon。其主要有兩方面的作用:

  • 接收並處理 Docker Client 傳送的請求。

  • 管理所有的 Docker 容器。

Docker Daemon 執行時,會在後臺啟動一個 Server Se er 負責接收 Docker Client 傳送 的請求;接收請求後, Server 通過路由與分發排程,找到相應的 Handler 來處理請求。

2、Docker daemon的結構分佈:

Docker Server、Docker Engine 和 Docker Job。

2.1、Docker Server

Docker Server Docker 架構中專門服務於 Docker Client ,它的功能是接收並排程分發 Docker Client 傳送的請求。

2.2、Docker Engine

Engine Docker 架構中的執行引擎,同時也是 Docker 執行的核心模組。 Engine 儲存著 大量的容器資訊,同時管理著 Docker 大部分 Job 的執行。在Docker 原始碼中,有關 Engine 的資料結構定義中含有一個名為 handlers 的物件。該 handlers 物件儲存的是關於眾多特定 Job 各自的處理方式 handler。

2.3、Docker Job

Job 可以認為是 Docker 架構中 Engine 內部最基本的工作執行單元。DockerDaemon 以完成的每一項工作都會呈現為一個Job 。例如,在 Docker 容器內部執行一個程序,這是 一個 Job; 建立一個新的容器,這是一個 Job; 在網路上下載一個文件,這是一個 Job; 包括 之前在 Docker Server 部分談及的,建立 Server 服務於 HTTP 協議的 API ,這也是一個 Job 等等。

對於 Job 而言,定義完畢之後,執行才能完成 Job 自身真正的使命。 Job 的執行函式 RunO 則用以執行 Job 本身。

3、

在上一篇中,ContainerCreate()ContainerStart()分別向daemon傳送了create和start命令。還是從docker engine的main()函式開始:

func newDaemonCommand() *cobra.Command {
	//獲取配置資訊
	opts := newDaemonOptions(config.New())
	//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.InstallFlags(flags)
	installConfigFlags(opts.daemonConfig, flags)
	installServiceFlags(flags)

	return cmd
}
func runDaemon(opts *daemonOptions) error {
	if opts.version {
		showVersion()
		return nil
	}

	//建立daemon客戶端物件
	daemonCli := NewDaemonCli()

	// Windows specific settings as these are not defaulted.
	if runtime.GOOS == "windows" {
		if opts.daemonConfig.Pidfile == "" {
			opts.daemonConfig.Pidfile = filepath.Join(opts.daemonConfig.Root, "docker.pid")
		}
		if opts.configFile == "" {
			opts.configFile = filepath.Join(opts.daemonConfig.Root, `config\daemon.json`)
		}
	}

	// On Windows, this may be launching as a service or with an option to
	// register the service.
	stop, runAsService, err := initService(daemonCli)
	if err != nil {
		logrus.Fatal(err)
	}

	if stop {
		return nil
	}

	// If Windows SCM manages the service - no need for PID files
	if runAsService {
		opts.daemonConfig.Pidfile = ""
	}

	//啟動daemonCli
	err = daemonCli.start(opts)
	notifyShutdown(err)
	return err
}
// NewDaemonCli returns a daemon CLI
func NewDaemonCli() *DaemonCli {
	return &DaemonCli{}
}


// DaemonCli represents the daemon CLI.
type DaemonCli struct {
	*config.Config      //配置資訊
	configFile *string     //配置檔案
	flags      *pflag.FlagSet    //flag引數資訊

	api             *apiserver.Server   //APIServer:提供api服務,定位在/engine/api/server/server.go
	d               *daemon.Daemon       //Daemon物件,結構體定義在/engine/daemon/daemon.go
	authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins
}

2.3、

Server結構體在daemonCli.start()實現過程中具有重要作用,並且這部分程式碼實現了daemonCli、apiserver的初始化,其中apiserver的功能是處理client段傳送請求,並將請求路由出去。所以initRouter(api, d, c)中包含了api路由的具體實現。

func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
	stopc := make(chan bool)
	defer close(stopc)

	// warn from uuid package when running the daemon
	uuid.Loggerf = logrus.Warnf

	//1.設定預設可選項引數
	opts.SetDefaultOptions(opts.flags)
	//2.根據opts物件資訊來載入DaemonCli的配置資訊config物件,並將該config物件配置到DaemonCli結構體物件中去
	if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
		return err
	}
	//3.對DaemonCli結構體中的其它成員根據opts進行配置
	cli.configFile = &opts.configFile
	cli.flags = opts.flags

	if cli.Config.Debug {
		debug.Enable()
	}

	if cli.Config.Experimental {
		logrus.Warn("Running experimental build")
	}

	logrus.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: jsonlog.RFC3339NanoFixed,
		DisableColors:   cli.Config.RawLogs,
		FullTimestamp:   true,
	})

	if err := setDefaultUmask(); err != nil {
		return fmt.Errorf("Failed to set umask: %v", err)
	}

	if len(cli.LogConfig.Config) > 0 {
		if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil {
			return fmt.Errorf("Failed to set log opts: %v", err)
		}
	}

	// Create the daemon root before we create ANY other files (PID, or migrate keys)
	// to ensure the appropriate ACL is set (particularly relevant on Windows)
	if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
		return err
	}

	if cli.Pidfile != "" {
		pf, err := pidfile.New(cli.Pidfile)
		if err != nil {
			return fmt.Errorf("Error starting daemon: %v", err)
		}
		defer func() {
			if err := pf.Remove(); err != nil {
				logrus.Error(err)
			}
		}()
	}
	//4.根據DaemonCli結構體物件中的資訊定義APIServer配置資訊結構體物件&apiserver.Config(包括tls傳輸層協議資訊)
	// TODO: extract to newApiServerConfig()
	serverConfig := &apiserver.Config{
		Logging:     true,
		SocketGroup: cli.Config.SocketGroup,
		Version:     dockerversion.Version,
		CorsHeaders: cli.Config.CorsHeaders,
	}

	if cli.Config.TLS {
		tlsOptions := tlsconfig.Options{
			CAFile:             cli.Config.CommonTLSOptions.CAFile,
			CertFile:           cli.Config.CommonTLSOptions.CertFile,
			KeyFile:            cli.Config.CommonTLSOptions.KeyFile,
			ExclusiveRootPools: true,
		}

		if cli.Config.TLSVerify {
			// server requires and verifies client's certificate
			tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
		}
		tlsConfig, err := tlsconfig.Server(tlsOptions)
		if err != nil {
			return err
		}
		serverConfig.TLSConfig = tlsConfig
	}

	if len(cli.Config.Hosts) == 0 {
		cli.Config.Hosts = make([]string, 1)
	}
	//5.根據定義好的&apiserver.Config新建APIServer物件,並賦值到DaemonCli例項的對應屬性中
	cli.api = apiserver.New(serverConfig)

	var hosts []string

	for i := 0; i < len(cli.Config.Hosts); i++ {
		var err error
		//6.解析host檔案及傳輸協議(tcp)等內容
		if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
			return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
		}

		protoAddr := cli.Config.Hosts[i]
		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
		if len(protoAddrParts) != 2 {
			return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
		}

		proto := protoAddrParts[0]
		addr := protoAddrParts[1]

		// It's a bad idea to bind to TCP without tlsverify.
		if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) {
			logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]")
		}
		//7.根據host解析內容初始化監聽器listener.Init()
		ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
		if err != nil {
			return err
		}
		ls = wrapListeners(proto, ls)
		// If we're binding to a TCP port, make sure that a container doesn't try to use it.
		if proto == "tcp" {
			if err := allocateDaemonPort(addr); err != nil {
				return err
			}
		}
		logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr)
		hosts = append(hosts, protoAddrParts[1])
		//8.為建立好的APIServer設定我們初始化的監聽器listener,可以監聽該地址的連線
		cli.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()...)
	if err != nil {
		return err
	}
	//11.設定訊號捕獲,進入Trap()函式
	signal.Trap(func() {
		cli.stop()
		<-stopc // wait for daemonCli.start() to return
	}, logrus.StandardLogger())

	// Notify that the API is active, but before daemon is set up.
	//12.提前通知系統api可以工作了,但是要在daemon安裝成功之後
	preNotifySystem()

	pluginStore := plugin.NewStore()

	if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil {
		logrus.Fatalf("Error creating middlewares: %v", err)
	}

	if system.LCOWSupported() {
		logrus.Warnln("LCOW support is enabled - this feature is incomplete")
	}
	//13.根據DaemonCli的配置資訊,註冊的服務物件及libcontainerd物件來構建Daemon物件
	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
	if err != nil {
		return fmt.Errorf("Error starting daemon: %v", err)
	}

	d.StoreHosts(hosts)

	// validate after NewDaemon has restored enabled plugins. Dont change order.
	if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
		return fmt.Errorf("Error validating authorization plugin: %v", err)
	}

	// TODO: move into startMetricsServer()
	if cli.Config.MetricsAddress != "" {
		if !d.HasExperimental() {
			return fmt.Errorf("metrics-addr is only supported when experimental is enabled")
		}
		if err := startMetricsServer(cli.Config.MetricsAddress); err != nil {
			return err
		}
	}

	// TODO: createAndStartCluster()
	name, _ := os.Hostname()

	// Use a buffered channel to pass changes from store watch API to daemon
	// A buffer allows store watch API and daemon processing to not wait for each other
	watchStream := make(chan *swarmapi.WatchMessage, 32)
	//14.新建cluster物件
	c, err := cluster.New(cluster.Config{
		Root:                   cli.Config.Root,
		Name:                   name,
		Backend:                d,
		PluginBackend:          d.PluginManager(),
		NetworkSubnetsProvider: d,
		DefaultAdvertiseAddr:   cli.Config.SwarmDefaultAdvertiseAddr,
		RuntimeRoot:            cli.getSwarmRunRoot(),
		WatchStream:            watchStream,
	})
	if err != nil {
		logrus.Fatalf("Error creating cluster component: %v", err)
	}
	d.SetCluster(c)
	err = c.Start()
	if err != nil {
		logrus.Fatalf("Error starting cluster component: %v", err)
	}

	// Restart all autostart containers which has a swarm endpoint
	// and is not yet running now that we have successfully
	// initialized the cluster.
	//15.重啟Swarm容器
	d.RestartSwarmContainers()

	logrus.Info("Daemon has completed initialization")
	//16.將新建的Daemon物件與DaemonCli相關聯
	cli.d = d

	routerOptions, err := newRouterOptions(cli.Config, d)
	if err != nil {
		return err
	}
	routerOptions.api = cli.api
	routerOptions.cluster = c
	//17.初始化路由器  ----對後面尤為重要
	initRouter(routerOptions)

	// process cluster change notifications
	watchCtx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go d.ProcessClusterNotifications(watchCtx, watchStream)

	cli.setupConfigReloadTrap()

	// 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
	serveAPIWait := make(chan error)
	//18.新建goroutine來監聽apiserver執行情況,當執行報錯時通道serverAPIWait就會傳出錯誤資訊
	go cli.api.Wait(serveAPIWait)

	// after the daemon is done setting up we can notify systemd api
	//19.通知系統Daemon已經安裝完成,可以提供api服務了
	notifySystem()

	// Daemon is fully initialized and handling API traffic
	// Wait for serve API to complete
	//20.等待apiserver執行出現錯誤,沒有錯誤則會阻塞到該語句,直到server API完成
	errAPI := <-serveAPIWait
	//21.執行到這一步說明,serverAPIWait有錯誤資訊傳出(以下均是),所以對cluster進行清理操作
	c.Cleanup()
	//22.關閉Daemon
	shutdownDaemon(d)
	//23.關閉libcontainerd
	containerdRemote.Cleanup()
	if errAPI != nil {
		return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
	}

	return nil
}

3、

路由排程分發: initRouter(routerOptions),原始碼:位置

3.1、

都知道,docker run命令其實是由兩部分組成:create和start。所以,最後將client發來的命令,分別路由到postContainersCreate和postContainersStart函式,在這個兩個函式中分別做容器建立和啟動的工作。

3.2、

流程:

3.2.1、原始碼:postContainersCreate()

func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	//1、解析表單
	if err := httputils.ParseForm(r); err != nil {
		return err
	}
    //2、檢查JSON檔案
	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}
	//3、從form表單中獲取名字,“/containers/create”
	name := r.Form.Get("name")
	//4、獲取從client傳過來的Config、hostConfig和networkingConfig配置資訊
	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
	if err != nil {
		return err
	}
	version := httputils.VersionFromContext(ctx)
	adjustCPUShares := versions.LessThan(version, "1.19")

	// When using API 1.24 and under, the client is responsible for removing the container
	if hostConfig != nil && versions.LessThan(version, "1.25") {
		hostConfig.AutoRemove = false
	}
	//5、傳入配置資訊,呼叫ContainerCreate進一步建立容器
	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
		Name:             name,
		Config:           config,  //Config是基於容器的可移植性資訊,與host相互獨立,
                                          //包括容器的基本資訊,名字,io流等
		HostConfig:       hostConfig, //包含非可移植性的資訊
		NetworkingConfig: networkingConfig,
		AdjustCPUShares:  adjustCPUShares,
	})
	if err != nil {
		return err
	}
    //6、給client返回結果
	return httputils.WriteJSON(w, http.StatusCreated, ccr)
}
// ContainerCreate creates a regular container
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {
	return daemon.containerCreate(params, false)
}

func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {
	start := time.Now()
	if params.Config == nil {
		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
	}

	// Default the platform if not supplied
	if params.Platform == "" {
		params.Platform = runtime.GOOS
	}
	if system.LCOWSupported() {
		params.Platform = "linux"
	}
	//驗證HostConfig、Config的資訊正確性。
	warnings, err := daemon.verifyContainerSettings(params.Platform, params.HostConfig, params.Config, false)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
	}
	//驗證NetworkingConfig的正確性
	err = verifyNetworkingConfig(params.NetworkingConfig)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
	}
	//如果配置hostConfig為空,則使用預設值
	if params.HostConfig == nil {
		params.HostConfig = &containertypes.HostConfig{}
	}
	//修改並更新hostconfig的不正常值,例如CPUShares、Memory
	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
	}
	//進一步呼叫daemon的create()
	container, err := daemon.create(params, managed)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
	}
	containerActions.WithValues("create").UpdateSince(start)
	//返回執行結果
	return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil
}

此函式,開始建立容器,建立容器的方法中經過一系列的操作完成了容器的建立過程,總結下來,主要是一下幾步:

  • 獲取映象ID   GetImage()

  • 合併容器配置    mergeAndVerifyConfig()

  • 合併日誌配置    mergeAndVerifyLogConfig()

  • 建立容器物件    newContainer()

  • 設定安全選項    setSecurityOptions()

  • 設定容器讀寫層    setRWLayer()

  • 建立資料夾儲存容器配置資訊   MkdirAndChown()

  • 容器網路配置

// Create creates a new container from the given configuration with a given name.
func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) {
	//定義一些全域性變數
	var (
		container *container.Container
		img       *image.Image
		imgID     image.ID
		err       error
	)
	//查詢Image
	if params.Config.Image != "" {
		img, err = daemon.GetImage(params.Config.Image)
		if err != nil {
			return nil, err
		}

		if runtime.GOOS == "solaris" && img.OS != "solaris " {
			return nil, errors.New("platform on which parent image was created is not Solaris")
		}
		imgID = img.ID()

		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
			return nil, errors.New("platform on which parent image was created is not Windows")
		}
	}

	// Make sure the platform requested matches the image
	if img != nil {
		if params.Platform != img.Platform() {
			// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
			if !system.LCOWSupported() {
				return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
			}
		}
	}
	//將使用者指定的Config引數與映象json檔案中的config合併並驗證
	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
		return nil, validationError{err}
	}
	//如果沒有指定container、log、driver,將daemon log config與container log config合併
	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
		return nil, validationError{err}
	}
	//進一步呼叫daemon包下的newContainer函式,下面再詳細分析
	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
		return nil, err
	}
	defer func() {
		if retErr != nil {
			if err := daemon.cleanupContainer(container, true, true); err != nil {
				logrus.Errorf("failed to cleanup container on create error: %v", err)
			}
		}
	}()
	//設定安全選項
	if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil {
		return nil, err
	}

	container.HostConfig.StorageOpt = params.HostConfig.StorageOpt

	// Fixes: https://github.com/moby/moby/issues/34074 and
	// https://github.com/docker/for-win/issues/999.
	// Merge the daemon's storage options if they aren't already present. We only
	// do this on Windows as there's no effective sandbox size limit other than
	// physical on Linux.
	if runtime.GOOS == "windows" {
		if container.HostConfig.StorageOpt == nil {
			container.HostConfig.StorageOpt = make(map[string]string)
		}
		for _, v := range daemon.configStore.GraphOptions {
			opt := strings.SplitN(v, "=", 2)
			if _, ok := container.HostConfig.StorageOpt[opt[0]]; !ok {
				container.HostConfig.StorageOpt[opt[0]] = opt[1]
			}
		}
	}

	// Set RWLayer for container after mount labels have been set
	//掛載了labels後,為容器設定讀寫層
	if err := daemon.setRWLayer(container); err != nil {
		return nil, systemError{err}
	}
	//以 root uid gid的屬性建立目錄,在/var/lib/docker/containers目錄下建立容器檔案,並在容器檔案下建立checkpoints目錄
	rootIDs := daemon.idMappings.RootPair()
	if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil {
		return nil, err
	}
	if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil {
		return nil, err
	}

	if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
		return nil, err
	}
	//呼叫daemon.Mount()函式,在 /var/lib/docker/aufs/mnt 目錄下建立檔案,以及設定工作目錄
	if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig); err != nil {
		return nil, err
	}

	var endpointsConfigs map[string]*networktypes.EndpointSettings
	if params.NetworkingConfig != nil {
		endpointsConfigs = params.NetworkingConfig.EndpointsConfig
	}
	// Make sure NetworkMode has an acceptable value. We do this to ensure
	// backwards API compatibility.
	//如果沒有設定網路,將網路模式設定為 default
	runconfig.SetDefaultNetModeIfBlank(container.HostConfig)
	//更新網路設定
	daemon.updateContainerNetworkSettings(container, endpointsConfigs)
	//在Daemon中註冊新建的container物件
	if err := daemon.Register(container); err != nil {
		return nil, err
	}
	//這個不知道幹啥的?
	stateCtr.set(container.ID, "stopped")
	//生成一個只有預設屬性容器相關事件
	daemon.LogContainerEvent(container, "create")
	//將container物件返回
	return container, nil
}

3.2.3.1、newContainer( ) 函式,主要生成一個container結構體。

func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
	var (
		id             string
		err            error
		noExplicitName = name == ""
	)
	//為容器生成name和id
	id, name, err = daemon.generateIDAndName(name)
	if err != nil {
		return nil, err
	}

	if hostConfig.NetworkMode.IsHost() {
		if config.Hostname == "" {
			//如果network是host mode,而且配置中沒有指定hostname,則使用宿主機的hostname
			config.Hostname, err = os.Hostname()
			if err != nil {
				return nil, systemError{err}
			}
		}
	} else {
		//否則,根據id和config生成一個hostname
		daemon.generateHostname(id, config)
	}
	//獲得entrypoint和args
	entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
	//生成一個最基礎的container物件
	base := daemon.newBaseContainer(id)
	//對container一些屬性進行初始化
	base.Created = time.Now().UTC()
	base.Managed = managed
	base.Path = entrypoint
	base.Args = args //FIXME: de-duplicate from config
	base.Config = config
	base.HostConfig = &containertypes.HostConfig{}
	base.ImageID = imgID
	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
	base.Name = name
	base.Driver = daemon.GraphDriverName(platform)
	base.Platform = platform
	//將生成的container物件返回
	return base, err
}

到這裡就已經完成了container物件的初始化,然後根據postContainersCreate()中的return httputils.WriteJSON(w, http.StatusCreated, ccr),將結果返回給client。

最後,由於daemon端對container start的處理沒有搞懂,下篇再發。

參考:

       《docker原始碼分析》

         百度網盤:連結: https://pan.baidu.com/s/1m5AqgfZKvPRu2BcmWz-WxA 提取碼: wjrj