Docker基础之容器(container)创建命令

一、Docker基础之容器(container)创建命令的用法

docker container create --name myetcd etcd_cluster:gc4.0 

create命令完成的工作,分为以下步骤:
1、 在hots主机上根据用户指定的配置生成一个container的工作目录,/var/lib/docker/containers/{id},同时把配置属性给持久化下来。
2、然后向daemon注册该container,注册之后,daemon就可以通过<container.ID>来使用该容器。
3、 并没后把该容器run起来!不涉及到底层containerd等工具的调用。

二、创建主机容器

这是docker daemon响应docker client端命令的handler method。
1、 解析request,得到相关参数
2、调用backend.ContainerCreate(),其中backend其实就是daemon

func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
	if err := httputils.ParseForm(r); err != nil {
		return err
	}
	if err := httputils.CheckForJSON(r); err != nil {
		return err
	}
	name := r.Form.Get("name")

	//从这里可以学习go是如何把复杂的json数据转化为响应的数据结构的!
	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
	if err != nil {
		return err
	}
	version := httputils.VersionFromContext(ctx)
	adjustCPUShares := versions.LessThan(version, "1.19")
	/创建一个container
	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
		Name:             name,
		Config:           config,
		HostConfig:       hostConfig,
		NetworkingConfig: networkingConfig,
		AdjustCPUShares:  adjustCPUShares,
	})
	if err != nil {
		return err
	}

	return httputils.WriteJSON(w, http.StatusCreated, ccr)
}

三、创建一个容器

下面函数实现了创建一个容器

// ContainerCreate函数创建了一个定时的容器
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()
	//验证HostConfig、Config、NetworkingConfig、AdjustCPUShares配置信息
	if params.Config == nil {
		return containertypes.ContainerCreateCreatedBody{}, fmt.Errorf("Config cannot be empty in order to create a container")
	}
	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config, false)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
	}

	err = daemon.verifyNetworkingConfig(params.NetworkingConfig)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
	}
	if params.HostConfig == nil {
		params.HostConfig = &containertypes.HostConfig{}
	}
	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
	}
	//调用create(daemon *Daemon)函数
	*/
	container, err := daemon.create(params, managed)
	if err != nil {
		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, daemon.imageNotExistToErrcode(err)
	}
	containerActions.WithValues("create").UpdateSince(start)
	return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil
}

四、create函数

create函数的执行步骤分为以下步骤:
1、获取到image
2、调用func (daemon *Daemon) newContainer 创建一个baseContainer
3、设置baseContainer的读写层、config文件
4、向daemon注册该container,注册之后,daemon就可以通过<container.ID>来使用该容器

// Create函数实现使用给定的名称从给定的配置创建一个新的容器。
func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) {
	//创建一个container需要三个元素
	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
		}
		if runtime.GOOS == "solaris" && img.OS != "solaris " {
			return nil, errors.New("Platform on which parent image was created is not Solaris")
		}
		imgID = img.ID() //镜像ID
	}

	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
		return nil, err
	}
	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
		return nil, err
	}
	//继续调用函数(daemon *Daemon) newContainer
	if container, err = daemon.newContainer(params.Name, params.Config, params.HostConfig, imgID, managed); err != nil {
		return nil, err
	}
	defer func() {
		//创建失败的话,调用func (daemon *Daemon) cleanupContainer执行cleanup动作
		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
	//设置容器的可读写层layer
	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
	}
	//创建目录 /var/lib/docker/containers/{id}/checkpoints并设置ownership
	if err := idtools.MkdirAs(container.CheckpointDir(), 0700, rootUID, rootGID); err != nil {
		return nil, err
	}
	//执行到这里,/var/lib/docker/containers/{id}下仅仅创建了一个checkpoints目录。setHostConfig()设置container的一些属性(包括MountPoints),然后把容器的配置信息持久化
	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
	}
	// 确保NetworkMode有一个可接受的值。我们这样做是为了确保API向后兼容性。
	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
	}
	//向daemon注册该container。注册之后,daemon就可以通过<container.ID>来使用该容器
	if err := daemon.Register(container); err != nil {
		return nil, err
	}
	daemon.LogContainerEvent(container, "create")
	return container, nil
}

五、newContainer函数

func (daemon *Daemon) newContainer(name string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
	var (
		id             string
		err            error
		noExplicitName = name == ""
	)
	//生成容器的ID和name
	id, name, err = daemon.generateIDAndName(name)
	if err != nil {
		return nil, err
	}
	if hostConfig.NetworkMode.IsHost() {
		if config.Hostname == "" {
			config.Hostname, err = os.Hostname()
			if err != nil {
				return nil, err
			}
		}
	} else {
		daemon.generateHostname(id, config)
	}
	entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
	//创建一个BaseContainer
	base := daemon.newBaseContainer(id)
	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()

	return base, err
}

1、BaseContainer

// NewBaseContainer使用其基本配置创建一个新容器。
func NewBaseContainer(id, root string) *Container {
	return &Container{
		CommonContainer: CommonContainer{
			ID:            id,
			State:         NewState(),
			ExecCommands:  exec.NewStore(),
			Root:          root,
			MountPoints:   make(map[string]*volume.MountPoint),
			StreamConfig:  stream.NewConfig(),
			attachContext: &attachContext{},
		},
	}
}

至此,创建一个BaseContainer流程已经完成,那么后面将由daemon继续完成设置该container的属性配置、读写layer设置和注册等工作。

2、CommonContainer构造对象

// CommonContainer保存容器的字段,这些字段适用于守护进程支持的所有平台。
type CommonContainer struct {
	StreamConfig *stream.Config
	//集成一个可以直接支持平台的容器
	*State          `json:"State"` //需要驱动引擎版本号小于1.11
	Root            string         `json:"-"` //容器的“主”路径,包括元数据。
	BaseFS          string         `json:"-"` //graphdriver挂载点的路径
	RWLayer         layer.RWLayer  `json:"-"`
	ID              string
	Created         time.Time
	Managed         bool
	Path            string
	Args            []string
	Config          *containertypes.Config
	ImageID         image.ID `json:"Image"`
	NetworkSettings *network.Settings
	LogPath         string
	Name            string
	Driver          string
	//MountLabel包含'mount'命令的选项
	MountLabel             string
	ProcessLabel           string
	RestartCount           int
	HasBeenStartedBefore   bool
	HasBeenManuallyStopped bool //用于非停止重启策略
	MountPoints            map[string]*volume.MountPoint
	HostConfig             *containertypes.HostConfig `json:"-"` 			// 不要在json中序列化主机配置,否则我们会使容器不可移植
	ExecCommands           *exec.Store                `json:"-"`
	SecretStore            agentexec.SecretGetter     `json:"-"`
	SecretReferences       []*swarmtypes.SecretReference
	//关闭日志驱动器
	LogDriver      logger.Logger  `json:"-"`
	LogCopier      *logger.Copier `json:"-"`
	restartManager restartmanager.RestartManager
	attachContext  *attachContext
}

3、创建一个container的RWLayer

func (daemon *Daemon) setRWLayer(container *container.Container) error {
	var layerID layer.ChainID
	if container.ImageID != "" {
		img, err := daemon.imageStore.Get(container.ImageID)
		if err != nil {
			return err
		}
		//根据img.RootFS中记录的diffID计算出该image的最后一个chainID
		layerID = img.RootFS.ChainID()
	}

	//创建该container的RWLayer
	rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, container.MountLabel, daemon.getLayerInit(), container.HostConfig.StorageOpt)

	if err != nil {
		return err
	}
	container.RWLayer = rwLayer

	return nil
}

4、CreateRWLayer()函数

func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel string, initFunc MountInit, storageOpt map[string]string) (RWLayer, error) {
	ls.mountL.Lock()
	defer ls.mountL.Unlock()
	m, ok := ls.mounts[name]
	if ok {
		return nil, ErrMountNameConflict
	}
	var err error
	var pid string
	var p *roLayer
	if string(parent) != "" {
		p = ls.get(parent)
		if p == nil {
			return nil, ErrLayerDoesNotExist
		}
		pid = p.cacheID

		// Release parent chain if error
		defer func() {
			if err != nil {
				ls.layerL.Lock()
				ls.releaseLayer(p)
				ls.layerL.Unlock()
			}
		}()
	}

	m = &mountedLayer{
		name:   name, //容器编号
		parent: p,    //一个container的读写layer的parent属性是其使用的image的最后一层的ChainID
		//mountID 对应的目录是 /var/docker/overlay/{mountID} 由Random随机生成的 
		mountID:    ls.mountID(name),
		layerStore: ls,
		references: map[RWLayer]*referencedRWLayer{},
	}
	if initFunc != nil {
		pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc, storageOpt)
		if err != nil {
			return nil, err
		}
		m.initID = pid
	}
	createOpts := &graphdriver.CreateOpts{
		StorageOpt: storageOpt,
	}
	if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
		return nil, err
	}

	if err = ls.saveMount(m); err != nil {
		return nil, err
	}

	return m.getReference(), nil
}

5、 mountedLayer 结构体函数

//实现了/layer/layer.go中的type RWLayer interface
type mountedLayer struct {
	name       string //其值一般是container.ID
	mountID    string //对应的目录是 /var/docker/overlay/{mountID}
	initID     string
	parent     *roLayer //一个container的读写layer的parent属性是其使用的image的最后一层的ChainID
	path       string
	layerStore *layerStore

	references map[RWLayer]*referencedRWLayer
}

6、MountPoints的设置
最后,关于该container挂载点的设置需要注意一下规则,一个容器可能会有多个MountPoints,指行步骤如下:
1、如果有的话,选择容器的先前配置的MountPoints。
2、选择从另一个容器装入的卷。 覆盖以前配置的MountPoints。
3、 选择client端设置的 bind mounts。 覆盖以前配置的MountPoints。
4、 清理即将被重新分配的旧volumes。

/* registerMountPoints使用配置的卷初始化容器挂载点并绑定挂载。
:registerMountPoints使用配置的卷初始化容器挂载点并绑定挂载。
1. 如果容器有挂载点,请选择之前配置的挂载点。
2. 选择从其他容器安装的卷。覆盖先前配置的挂载点目的地。
3.选择客户端设置的绑定挂载。覆盖先前配置的挂载点目的地。
4. 清理即将重新分配的旧卷。
*/
func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
	binds := map[string]bool{}
	mountPoints := map[string]*volume.MountPoint{}
	defer func() {
		// 在返回错误时清理容器挂载点
		if retErr != nil {
			for _, m := range mountPoints {
				if m.Volume == nil {
					continue
				}
				daemon.volumes.Dereference(m.Volume, container.ID)
			}
		}
	}()
	// 1. 读取已配置的挂载点。
	for destination, point := range container.MountPoints {
		mountPoints[destination] = point
	}
	// 2. 从其他容器中读取卷。
	for _, v := range hostConfig.VolumesFrom {
		containerID, mode, err := volume.ParseVolumesFrom(v)
		if err != nil {
			return err
		}
		c, err := daemon.GetContainer(containerID)
		if err != nil {
			return err
		}
		for _, m := range c.MountPoints {
			cp := &volume.MountPoint{
				Name:        m.Name,
				Source:      m.Source,
				RW:          m.RW && volume.ReadWrite(mode),
				Driver:      m.Driver,
				Destination: m.Destination,
				Propagation: m.Propagation,
				Spec:        m.Spec,
				CopyData:    false,
			}

			if len(cp.Source) == 0 {
				v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID)
				if err != nil {
					return err
				}
				cp.Volume = v
			}
			mountPoints[cp.Destination] = cp
		}
	}
	//3.读取绑定安装
	for _, b := range hostConfig.Binds {
		bind, err := volume.ParseMountRaw(b, hostConfig.VolumeDriver)
		if err != nil {
			return err
		}
		_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
		if binds[bind.Destination] || tmpfsExists {
			return fmt.Errorf("Duplicate mount point '%s'", bind.Destination)
		}
		if bind.Type == mounttypes.TypeVolume {
			//创建卷
			v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil)
			if err != nil {
				return err
			}
			bind.Volume = v
			bind.Source = v.Path()
			//Name是一个已经存在的卷,我们需要在这里使用它
			bind.Driver = v.DriverName()
			if bind.Driver == volume.DefaultDriverName {
				setBindModeIfNull(bind)
			}
		}
		binds[bind.Destination] = true
		mountPoints[bind.Destination] = bind
	}
	for _, cfg := range hostConfig.Mounts {
		mp, err := volume.ParseMountSpec(cfg)
		if err != nil {
			return dockererrors.NewBadRequestError(err)
		}
		if binds[mp.Destination] {
			return fmt.Errorf("Duplicate mount point '%s'", cfg.Target)
		}
		if mp.Type == mounttypes.TypeVolume {
			var v volume.Volume
			if cfg.VolumeOptions != nil {
				var driverOpts map[string]string
				if cfg.VolumeOptions.DriverConfig != nil {
					driverOpts = cfg.VolumeOptions.DriverConfig.Options
				}
				v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, driverOpts, cfg.VolumeOptions.Labels)
			} else {
				v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, nil, nil)
			}
			if err != nil {
				return err
			}
			if err := label.Relabel(mp.Source, container.MountLabel, false); err != nil {
				return err
			}
			mp.Volume = v
			mp.Name = v.Name()
			mp.Driver = v.DriverName()

			//在这里只使用缓存的路径,因为现在不需要获取路径,并且调用path()可能会很慢
			if cv, ok := v.(interface {
				CachedPath() string
			}); ok {
				mp.Source = cv.CachedPath()
			}
		}

		binds[mp.Destination] = true
		mountPoints[mp.Destination] = mp
	}
	container.Lock()
	//4. 清理即将重新分配的旧卷。
	for _, m := range mountPoints {
		if m.BackwardsCompatible() {
			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
				daemon.volumes.Dereference(mp.Volume, container.ID)
			}
		}
	}
	container.MountPoints = mountPoints
	container.Unlock()
	return nil
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝域时空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值