docker container create 流程 源码分析
tags: docker,container,源码分析
docker container create 流程 源码分析
1. docker container create 简介
创建容器,通过docker container create
命令调用,该流程在运行容器时也会执行。
https://docs.docker.com/engine/reference/commandline/container_create/
2. 源码入口位置
由cli接收docker container create
命令参数,发送至docker engine api
cli与engine api的代码入口分别位于:
cli https://github.com/docker/cli/blob/v20.10.13/cli/command/container/create.go#L43
engine https://github.com/moby/moby/blob/v20.10.13/api/server/router/container/container.go#L49
3. cli
docker使用 github.com/spf13/cobra
实现cli功能,NewCreateCommand函数中就定义了cobra.Command。该函数中用户输入的参数经cobra
解析后,会被传给runCreate
函数。
https://github.com/docker/cli/blob/v20.10.13/cli/command/container/create.go#L56
func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
...
cmd := &cobra.Command{
...
RunE: func(cmd *cobra.Command, args []string) error {
...
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
},
}
...
return cmd
}
https://github.com/docker/cli/blob/v20.10.13/cli/command/container/create.go#L97
func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
...
response, err := createContainer(context.Background(), dockerCli, containerConfig, options)
...
}
跟踪调用链,来到createContainer
函数,如果配置了pull镜像选项,或是第一次创建容器返回镜像不存在,则会先拉取镜像,再调用dockerCli.Client().ContainerCreate
方法创建容器。
https://github.com/docker/cli/blob/v20.10.13/cli/command/container/create.go#L192
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) {
...
if opts.pull == PullImageAlways {
if err := pullAndTagImage(); err != nil {
return nil, err
}
}
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name)
if err != nil {
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
if apiclient.IsErrNotFound(err) && namedRef != nil && opts.pull == PullImageMissing {
// we don't want to write to stdout anything apart from container.ID
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
if err := pullAndTagImage(); err != nil {
return nil, err
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name)
...
}
接下来,在校验了一些版本信息后,调用api /containers/create
,由docker engine具体实现容器创建。
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) {
...
query := url.Values{}
if platform != nil {
query.Set("platform", platforms.Format(*platform))
}
if containerName != "" {
query.Set("name", containerName)
}
body := configWrapper{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}
4. api
api /containers/create
对应 r.postContainersCreate
方法。
https://github.com/moby/moby/blob/v20.10.13/api/server/router/container/container.go#L49
func (r *containerRouter) initRoutes() {
r.routes = []router.Route{
...
router.NewPostRoute("/containers/create", r.postContainersCreate),
...
}
}
https://github.com/moby/moby/blob/v20.10.13/api/server/router/container/container_routes.go#L460
func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
...
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
Name: name,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
Platform: platform,
})
...
}
s.backend
初始化为 Daemon
对象,所以跟进Daemon.ContainerCreate
。
https://github.com/moby/moby/blob/v20.10.13/daemon/create.go#L42
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {
return daemon.containerCreate(createOpts{
params: params,
managed: false,
ignoreImagesArgsEscaped: false})
}
func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.ContainerCreateCreatedBody, error) {
...
ctr, err := daemon.create(opts)
...
}
跳过一些配置封装和校验,来到daemon.create
方法。该方法创建了容器,并为容器准备好了文件系统、创建了卷、把容器中已存在的文件复制到卷中等,这些操作将在下文中逐一分析。
创建容器,如果遇到错误在函数结束前清理容器;这里创建的容器实际只是创建了一个github.com/docker/docker/container.Container
类型的实例。
https://github.com/moby/moby/blob/v20.10.13/daemon/create.go#L174-L183
func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr error) {
...
if ctr, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil {
return nil, err
}
defer func() {
if retErr != nil {
if err := daemon.cleanupContainer(ctr, true, true); err != nil {
logrus.Errorf("failed to cleanup container on create error: %v", err)
}
}
}()
...
}
https://github.com/moby/moby/blob/v20.10.13/daemon/container.go#L128
func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
...
base := daemon.newBaseContainer(id)
base.Created = time.Now().UTC()
base.Managed = managed
base.Path = entrypoint
...
return base, err
}
创建容器文件系统,以容器镜像rootfs的只读层作为上层文件系统,创建一个读写层。
https://github.com/moby/moby/blob/v20.10.13/daemon/create.go#L209-L221
func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr error) {
...
rwLayer, err := daemon.imageService.CreateLayer(ctr, setupInitLayer(daemon.idMapping))
if err != nil {
return nil, errdefs.System(err)
}
ctr.RWLayer = rwLayer
current := idtools.CurrentIdentity()
if err := idtools.MkdirAndChown(ctr.Root, 0710, idtools.Identity{UID: current.UID, GID: daemon.IdentityMapping().RootPair().GID}); err != nil {
return nil, err
}
if err := idtools.MkdirAndChown(ctr.CheckpointDir(), 0700, current); err != nil {
return nil, err
}
...
}
https://github.com/moby/moby/blob/v20.10.13/daemon/images/service.go#L128
func (i *ImageService) CreateLayer(container *container.Container, initFunc layer.MountInit) (layer.RWLayer, error) {
var layerID layer.ChainID
if container.ImageID != "" {
img, err := i.imageStore.Get(container.ImageID)
if err != nil {
return nil, err
}
layerID = img.RootFS.ChainID()
}
...
return i.layerStores[container.OS].CreateRWLayer(container.ID, layerID, rwLayerOpts)
}
https://github.com/moby/moby/blob/v20.10.13/daemon/daemon_unix.go#L1069
func setupInitLayer(idMapping *idtools.IdentityMapping) func(containerfs.ContainerFS) error {
return func(initPath containerfs.ContainerFS) error {
return initlayer.Setup(initPath, idMapping.RootPair())
}
}
其中,在创建读写层前,先对容器的rootfs做了一些设置:清除并重建了一些重要路径。
https://github.com/moby/moby/blob/v20.10.13/daemon/initlayer/setup_unix.go#L20
func Setup(initLayerFs containerfs.ContainerFS, rootIdentity idtools.Identity) error {
// Since all paths are local to the container, we can just extract initLayerFs.Path()
initLayer := initLayerFs.Path()
for pth, typ := range map[string]string{
"/dev/pts": "dir",
"/dev/shm": "dir",
"/proc": "dir",
"/sys": "dir",
"/.dockerenv": "file",
"/etc/resolv.conf": "file",
"/etc/hosts": "file",
"/etc/hostname": "file",
"/dev/console": "file",
"/etc/mtab": "/proc/mounts",
} {
parts := strings.Split(pth, "/")
prev := "/"
for _, p := range parts[1:] {
prev = filepath.Join(prev, p)
unix.Unlink(filepath.Join(initLayer, prev))
}
if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootIdentity); err != nil {
return err
}
switch typ {
case "dir":
if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, pth), 0755, rootIdentity); err != nil {
return err
}
case "file":
f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755)
if err != nil {
return err
}
f.Chown(rootIdentity.UID, rootIdentity.GID)
f.Close()
default:
if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil {
return err
}
}
} else {
return err
}
}
}
// Layer is ready to use, if it wasn't before.
return nil
}
setHostConfig
, createContainerOSSpecificSettings
都主要是创建卷。
https://github.com/moby/moby/blob/v20.10.13/daemon/create.go#L223-L229
func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr error) {
...
if err := daemon.setHostConfig(ctr, opts.params.HostConfig); err != nil {
return nil, err
}
if err := daemon.createContainerOSSpecificSettings(ctr, opts.params.Config, opts.params.HostConfig); err != nil {
return nil, err
}
...
}
https://github.com/moby/moby/blob/v20.10.13/daemon/create_unix.go#L22
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
if err := daemon.Mount(container); err != nil {
return err
}
defer daemon.Unmount(container)
...
for spec := range config.Volumes {
name := stringid.GenerateRandomID()
destination := filepath.Clean(spec)
...
v, err := daemon.volumes.Create(context.TODO(), name, hostConfig.VolumeDriver, volumeopts.WithCreateReference(container.ID))
...
container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
}
return daemon.populateVolumes(container)
}
创建完卷后,对于每个挂载点,如果rootfs中挂载点已存在,则将从该路径复制文件到卷中。
https://github.com/moby/moby/blob/v20.10.13/daemon/create_unix.go#L80
func (daemon *Daemon) populateVolumes(c *container.Container) error {
for _, mnt := range c.MountPoints {
...
if err := c.CopyImagePathContent(mnt.Volume, mnt.Destination); err != nil {
return err
}
}
return nil
}
https://github.com/moby/moby/blob/master/container/container_unix.go#L129
func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error {
rootfs, err := container.GetResourcePath(destination)
if err != nil {
return err
}
...
id := stringid.GenerateRandomID()
path, err := v.Mount(id)
if err != nil {
return err
}
defer func() {
if err := v.Unmount(id); err != nil {
logrus.Warnf("error while unmounting volume %s: %v", v.Name(), err)
}
}()
...
return copyExistingContents(rootfs, path)
}
至此,创建容器的流程基本结束。