func (daemon *Daemon) createRootfs(container *Container) error { // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return err } if err := idtools.MkdirAs(container.root, 0700, rootUID, rootGID); err != nil { return err } initID := fmt.Sprintf("%s-init", container.ID) if err := daemon.driver.Create(initID, container.ImageID); err != nil { return err } initPath, err := daemon.driver.Get(initID, "") if err != nil { return err } if err := setupInitLayer(initPath, rootUID, rootGID); err != nil { daemon.driver.Put(initID) return err } // We want to unmount init layer before we take snapshot of it // for the actual container. daemon.driver.Put(initID) if err := daemon.driver.Create(container.ID, initID); err != nil { return err } return nil }
// Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent. func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { if len(storageOpt) != 0 { return fmt.Errorf("--storage-opt is not supported for vfs") } dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } if err := idtools.MkdirAllAs(filepath.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(dir, 0755, rootUID, rootGID); err != nil { return err } opts := []string{"level:s0"} if _, mountLabel, err := label.InitLabels(opts); err == nil { label.SetFileLabel(dir, mountLabel) } if parent == "" { return nil } parentDir, err := d.Get(parent, "") if err != nil { return fmt.Errorf("%s: %s", parent, err) } if err := CopyWithTar(parentDir, dir); err != nil { return err } return nil }
// Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent. func (d *Driver) Create(id, parent, mountLabel string) error { dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } if err := idtools.MkdirAllAs(filepath.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(dir, 0755, rootUID, rootGID); err != nil { return err } opts := []string{"level:s0"} if _, mountLabel, err := label.InitLabels(opts); err == nil { label.SetFileLabel(dir, mountLabel) } if parent == "" { return nil } parentDir, err := d.Get(parent, "") if err != nil { return fmt.Errorf("%s: %s", parent, err) } if err := chrootarchive.CopyWithTar(parentDir, dir); err != nil { return err } return nil }
// Get mounts a device with given id into the root filesystem func (d *Driver) Get(id, mountLabel string) (string, error) { mp := path.Join(d.home, "mnt", id) rootFs := path.Join(mp, "rootfs") if count := d.ctr.Increment(mp); count > 1 { return rootFs, nil } uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { d.ctr.Decrement(mp) return "", err } // Create the target directories if they don't exist if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0755, uid, gid); err != nil && !os.IsExist(err) { d.ctr.Decrement(mp) return "", err } if err := idtools.MkdirAs(mp, 0755, uid, gid); err != nil && !os.IsExist(err) { d.ctr.Decrement(mp) return "", err } // Mount the device if err := d.DeviceSet.MountDevice(id, mp, mountLabel); err != nil { d.ctr.Decrement(mp) return "", err } if err := idtools.MkdirAllAs(rootFs, 0755, uid, gid); err != nil && !os.IsExist(err) { d.ctr.Decrement(mp) d.DeviceSet.UnmountDevice(id, mp) return "", err } idFile := path.Join(mp, "id") if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) { // Create an "id" file with the container/image id in it to help reconstruct this in case // of later problems if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil { d.ctr.Decrement(mp) d.DeviceSet.UnmountDevice(id, mp) return "", err } } return rootFs, nil }
func configureSysInit(config *Config, rootUID, rootGID int) (string, error) { localCopy := filepath.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) if sysInitPath == "" { return "", fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See https://docs.docker.com/project/set-up-dev-env/ for official build instructions.") } if sysInitPath != localCopy { // When we find a suitable dockerinit binary (even if it's our local binary), we copy it into config.Root at localCopy for future use (so that the original can go away without that being a problem, for example during a package upgrade). if err := idtools.MkdirAs(filepath.Dir(localCopy), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { return "", err } if _, err := fileutils.CopyFile(sysInitPath, localCopy); err != nil { return "", err } if err := os.Chmod(localCopy, 0700); err != nil { return "", err } sysInitPath = localCopy } return sysInitPath, nil }
func (clnt *client) prepareBundleDir(uid, gid int) (string, error) { root, err := filepath.Abs(clnt.remote.stateDir) if err != nil { return "", err } if uid == 0 && gid == 0 { return root, nil } p := string(filepath.Separator) for _, d := range strings.Split(root, string(filepath.Separator))[1:] { p = filepath.Join(p, d) fi, err := os.Stat(p) if err != nil && !os.IsNotExist(err) { return "", err } if os.IsNotExist(err) || fi.Mode()&1 == 0 { p = fmt.Sprintf("%s.%d.%d", p, uid, gid) if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) { return "", err } } } return p, nil }
// Create creates a new container from the given configuration with a given name. func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *container.Container, retErr error) { var ( container *container.Container img *image.Image imgID image.ID err error ) if params.Config.Image != "" { img, err = daemon.GetImage(params.Config.Image) if err != nil { return nil, err } imgID = img.ID() } if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, err } if err := daemon.mergeAndVerifyLogConfig(¶ms.HostConfig.LogConfig); err != nil { return nil, err } if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.cleanupContainer(container, 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 // Set RWLayer for container after mount labels have been set if err := daemon.setRWLayer(container); err != nil { return nil, err } rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return nil, err } if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { return nil, err } if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.removeMountPoints(container, true); err != nil { logrus.Error(err) } } }() 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 } if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil { return nil, err } if err := container.ToDisk(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, err } if err := daemon.Register(container); err != nil { return nil, err } daemon.LogContainerEvent(container, "create") return container, nil }
// 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 ) if params.Config.Image != "" { img, err = daemon.GetImage(params.Config.Image) if err != nil { return nil, err } imgID = img.ID() } if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, err } if err := daemon.mergeAndVerifyLogConfig(¶ms.HostConfig.LogConfig); err != nil { return nil, err } if container, err = daemon.newContainer(params.Name, params.Config, 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 // Set RWLayer for container after mount labels have been set if err := daemon.setRWLayer(container); err != nil { return nil, err } rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return nil, err } if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { return nil, err } if err := idtools.MkdirAs(container.CheckpointDir(), 0700, rootUID, rootGID); err != nil { return nil, err } if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } 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. container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) daemon.updateContainerNetworkSettings(container, endpointsConfigs) if err := container.ToDisk(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, err } if err := daemon.Register(container); err != nil { return nil, err } daemon.LogContainerEvent(container, "create") return container, nil }
// Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. // The parent filesystem is used to configure these directories for the overlay. func (d *Driver) Create(id, parent, mountLabel string) (retErr error) { dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil { return err } defer func() { // Clean up on failure if retErr != nil { os.RemoveAll(dir) } }() // Toplevel images are just a "root" dir if parent == "" { if err := idtools.MkdirAs(path.Join(dir, "root"), 0755, rootUID, rootGID); err != nil { return err } return nil } parentDir := d.dir(parent) // Ensure parent exists if _, err := os.Lstat(parentDir); err != nil { return err } // If parent has a root, just do a overlay to it parentRoot := path.Join(parentDir, "root") if s, err := os.Lstat(parentRoot); err == nil { if err := idtools.MkdirAs(path.Join(dir, "upper"), s.Mode(), rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { return err } if err := ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666); err != nil { return err } return nil } // Otherwise, copy the upper and the lower-id from the parent lowerID, err := ioutil.ReadFile(path.Join(parentDir, "lower-id")) if err != nil { return err } if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerID, 0666); err != nil { return err } parentUpperDir := path.Join(parentDir, "upper") s, err := os.Lstat(parentUpperDir) if err != nil { return err } upperDir := path.Join(dir, "upper") if err := idtools.MkdirAs(upperDir, s.Mode(), rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { return err } return copyDir(parentUpperDir, upperDir, 0) }
// Create creates a new container from the given configuration with a given name. func (daemon *Daemon) create(params *ContainerCreateConfig) (retC *container.Container, retErr error) { var ( container *container.Container img *image.Image imgID image.ID err error ) if params.Config.Image != "" { img, err = daemon.GetImage(params.Config.Image) if err != nil { return nil, err } imgID = img.ID() } if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, err } if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.ContainerRm(container.ID, &ContainerRmConfig{ForceRemove: true}); err != nil { logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err) } } }() if err := daemon.Register(container); err != nil { return nil, err } rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return nil, err } if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { return nil, err } if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.removeMountPoints(container, true); err != nil { logrus.Error(err) } } }() if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig, img); err != nil { return nil, err } if err := container.ToDiskLocking(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, err } daemon.LogContainerEvent(container, "create") return container, nil }
// Create creates a new container from the given configuration with a given name. func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *container.Container, retErr error) { var ( container *container.Container img *image.Image imgID image.ID err error ) if params.Config.Image != "" { img, err = daemon.GetImage(params.Config.Image) if err != nil { return nil, err } imgID = img.ID() } if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, err } if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true}); err != nil { logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err) } } }() logCfg := container.GetLogConfig(daemon.defaultLogConfig) if err := logger.ValidateLogOpts(logCfg.Type, logCfg.Config); err != nil { return nil, err } if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil { return nil, err } // Set RWLayer for container after mount labels have been set if err := daemon.setRWLayer(container); err != nil { return nil, err } if err := daemon.Register(container); err != nil { return nil, err } rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return nil, err } if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { return nil, err } if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.removeMountPoints(container, true); err != nil { logrus.Error(err) } } }() 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 } if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil { return nil, err } if err := container.ToDiskLocking(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, err } daemon.LogContainerEvent(container, "create") return container, nil }
// Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. // The parent filesystem is used to configure these directories for the overlay. func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) (retErr error) { if len(storageOpt) != 0 && !projectQuotaSupported { return fmt.Errorf("--storage-opt is supported only for overlay over xfs with 'pquota' mount option") } dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil { return err } defer func() { // Clean up on failure if retErr != nil { os.RemoveAll(dir) } }() if len(storageOpt) > 0 { driver := &Driver{} if err := d.parseStorageOpt(storageOpt, driver); err != nil { return err } if driver.options.quota.Size > 0 { // Set container disk quota limit if err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil { return err } } } if err := idtools.MkdirAs(path.Join(dir, "diff"), 0755, rootUID, rootGID); err != nil { return err } lid := generateID(idLength) if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil { return err } // Write link id to link file if err := ioutil.WriteFile(path.Join(dir, "link"), []byte(lid), 0644); err != nil { return err } // if no parent directory, done if parent == "" { return nil } if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { return err } lower, err := d.getLower(parent) if err != nil { return err } if lower != "" { if err := ioutil.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0666); err != nil { return err } } return nil }
// Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. // The parent filesystem is used to configure these directories for the overlay. func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) (retErr error) { if len(storageOpt) != 0 { return fmt.Errorf("--storage-opt is not supported for overlay") } dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { return err } if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil { return err } defer func() { // Clean up on failure if retErr != nil { os.RemoveAll(dir) } }() if err := idtools.MkdirAs(path.Join(dir, "diff"), 0755, rootUID, rootGID); err != nil { return err } lid := generateID(idLength) if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil { return err } // Write link id to link file if err := ioutil.WriteFile(path.Join(dir, "link"), []byte(lid), 0644); err != nil { return err } // if no parent directory, done if parent == "" { return nil } if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { return err } if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { return err } lower, err := d.getLower(parent) if err != nil { return err } if lower != "" { if err := ioutil.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0666); err != nil { return err } } return nil }
// Create creates a new container from the given configuration with a given name. func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *container.Container, retErr error) { var ( container *container.Container img *image.Image imgID image.ID err error ) if params.Config.Image != "" { //获取镜像 img, err = daemon.GetImage(params.Config.Image) if err != nil { return nil, err } //获取镜像ID号 imgID = img.ID() } //合并并且检查参数 if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, err } //创建一个新的容器,此时需要跟踪分析newContainer方法,该方法在daemon/daemon.go中。 //该方法返回的是container,是一个这样的结构: /* type Container struct { //这是通用的参数 CommonContainer // 这个是平台特殊的参数,只在类unix系统上有意义。 AppArmorProfile string HostnamePath string HostsPath string ShmPath string ResolvConfPath string SeccompProfile string NoNewPrivileges bool } */ //创建容器实例,实际上只是在代码中创建,并没有创建文件系统和namespace。 //实际上,创建容器的过程中最多也就创建到文件系统,namespace只有在容器 //运行时候才会生效。 if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil { return nil, err } //如果创建容器出错,就试图删除容器。 defer func() { if retErr != nil { if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true}); err != nil { logrus.Errorf("Clean up Error! Cannot destroy container %s: %v", container.ID, err) } } }() //配置容器的安全设置,例如apparmor,selinux等。 if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil { return nil, err } // Set RWLayer for container after mount labels have been set // 设置可读写层,就是获取layID等信息,包括镜像层、容器层。 //详情请见setRWLayer函数,就在本文件中。 if err := daemon.setRWLayer(container); err != nil { return nil, err } //向daemon注册容器: //daemon.containers.Add(c.ID, c) if err := daemon.Register(container); err != nil { return nil, err } rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) if err != nil { return nil, err } //设置容器root目录(元数据目录/var/lib/docker/containers)的权限。 if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { return nil, err } //这个方法主要做两件事情: //挂载volumes; //设置容器之间的链接links; if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.removeMountPoints(container, true); err != nil { logrus.Error(err) } } }() //这一步是处理和平台相关的操作,具体在daemon/create_unix.go文件中 /* func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error { if err := daemon.Mount(container); err != nil { return err } defer daemon.Unmount(container) rootUID, rootGID := daemon.GetRemappedUIDGID() if err := container.SetupWorkingDirectory(rootUID, rootGID); err != nil { return err } for spec := range config.Volumes { name := stringid.GenerateNonCryptoID() destination := filepath.Clean(spec) // Skip volumes for which we already have something mounted on that // destination because of a --volume-from. if container.IsDestinationMounted(destination) { continue } path, err := container.GetResourcePath(destination) if err != nil { return err } stat, err := os.Stat(path) if err == nil && !stat.IsDir() { return fmt.Errorf("cannot mount volume over existing file, file exists %s", path) } v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil, nil) if err != nil { return err } if err := label.Relabel(v.Path(), container.MountLabel, true); err != nil { return err } container.AddMountPointWithVolume(destination, v, true) } return daemon.populateVolumes(container) } */ //在这一步骤里面进行一些跟平台相关的设置,主要为mount目录文件,以及volume挂载。 if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig); err != nil { return nil, err } //网络endpoints的配置 var endpointsConfigs map[string]*networktypes.EndpointSettings if params.NetworkingConfig != nil { endpointsConfigs = params.NetworkingConfig.EndpointsConfig } //更新网路配置,在daemon/container_operations.go中,调用了libnetwork模块,需要仔细研究。 //这里仅仅是更新container.NetworkSettings.Networks[]数组中的网络模式。并没有真正创建网络。 if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil { return nil, err } //将容器的配置保存到磁盘。但是这个和另外一个todisk的区别是什么? if err := container.ToDiskLocking(); err != nil { logrus.Errorf("Error saving new container to disk: %v", err) return nil, err } //记录容器的事件日志。 daemon.LogContainerEvent(container, "create") return container, nil }