Docker的run命令可以运行一个容器,支持的过程分为两步:Create和Start。
client端
先来看下Docker client端的run实现,实现函数为runRun(),定义在/pkg/client/container/run.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error { stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In() client := dockerCli.Client() cmdPath := "run" ...... config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) if err != nil { reportError(stderr, cmdPath, err.Error(), true) return cli.StatusError{StatusCode: 125} } if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n") } if len(hostConfig.DNS) > 0 { for _, dnsIP := range hostConfig.DNS { if dns.IsLocalhost(dnsIP) { fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) break } } } ...... ctx, cancelFun := context.WithCancel(context.Background()) createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name) ...... attach := config.AttachStdin || config.AttachStdout || config.AttachStderr if attach { ...... } if opts.autoRemove { defer func() { if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil { fmt.Fprintf(stderr, "%v\n", err) } }() } if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil { ...... } ...... }
|
runRun()的流程如下:
- 依据配置文件生成config, hostConfig, networkingConfig;
- 调用createContainer()创建容器;
- 调用docker client的ContainerStart()启动容器。
再来看createContainer()的实现,定义在/api/client/container/create.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| func createContainer(ctx context.Context, dockerCli *client.DockerCli, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { ...... response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) if err != nil { if apiclient.IsErrImageNotFound(err) && ref != nil { fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String()) if err = pullImage(ctx, dockerCli, config.Image, stderr); err != nil { return nil, err } if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil { if err := dockerCli.TagTrusted(ctx, trustedRef, ref); err != nil { return nil, err } } var retryErr error response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) if retryErr != nil { return nil, retryErr } } else { return nil, err } } ...... return &response, nil }
|
createContainer()的流程如下:
- 调用docker client的ContainerCreate()创建容器;
- 如果创建过程出错,则尝试下载镜像,然后再调用ContainerCreate()创建容器。
所以,容器的创建和运行是调用docker client的ContainerCreate()和ContainerStart()完成的。
server端
create流程
再来看server端响应create的流程。postConatinersCreate()处理容器创建的请求,定义在/api/server/router/container/container_routes.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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") config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) if err != nil { return err } version := httputils.VersionFromContext(ctx) adjustCPUShares := versions.LessThan(version, "1.19") validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ Name: name, Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, AdjustCPUShares: adjustCPUShares, }, validateHostname) if err != nil { return err } return httputils.WriteJSON(w, http.StatusCreated, ccr) }
|
postContainersCreate()的流程如下:
- 从请求表单中解析出config, hostConfig和networkingConfig;
- 调用backend.ContainerCreate()创建容器,这里的backend即Daemon。
start流程
postContainersStart()处理启动容器的请求,定义在/api/server/router/container/container_routes.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { version := httputils.VersionFromContext(ctx) var hostConfig *container.HostConfig if r.ContentLength > 7 || r.ContentLength == -1 { if versions.GreaterThanOrEqualTo(version, "1.24") { return validationError{fmt.Errorf("starting container with HostConfig was deprecated since v1.10 and removed in v1.12")} } if err := httputils.CheckForJSON(r); err != nil { return err } c, err := s.decoder.DecodeHostConfig(r.Body) if err != nil { return err } hostConfig = c } validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil { return err } w.WriteHeader(http.StatusNoContent) return nil }
|
postContainersStart()调用backend.ContainerStart()启动容器,这里的backend即Daemon。
Daemon侧
create流程
来看Daemon的ContainerCreate(),定义在/daemon/create.go中:
1 2 3 4
| func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) { return daemon.containerCreate(params, false, validateHostname) }
|
ContainerCreate()调用了containerCreate():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool, validateHostname bool) (types.ContainerCreateResponse, error) { if params.Config == nil { return types.ContainerCreateResponse{}, fmt.Errorf("Config cannot be empty in order to create a container") } warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config, false, validateHostname) if err != nil { return types.ContainerCreateResponse{Warnings: warnings}, err } err = daemon.verifyNetworkingConfig(params.NetworkingConfig) if err != nil { return types.ContainerCreateResponse{}, err } if params.HostConfig == nil { params.HostConfig = &containertypes.HostConfig{} } err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares) if err != nil { return types.ContainerCreateResponse{Warnings: warnings}, err } container, err := daemon.create(params, managed) if err != nil { return types.ContainerCreateResponse{Warnings: warnings}, daemon.imageNotExistToErrcode(err) } return types.ContainerCreateResponse{ID: container.ID, Warnings: warnings}, nil }
|
containerCreate()的流程如下:
- 检查container的各项参数;
- 检查网络参数;
- 调用create()创建容器。
create()定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| 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); 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 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 } container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) 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 }
|
- 获取imageID;
- merge config;
- 处理log的配置;
- 调用newContainer()通过电脑一个container;
- 设置container安全相关的参数;
- 调用setRWLayer()创建读写层;
- 创建容器根目录;
- 设置网络模式;
- 把container的json写到容器目录;
- 调用Daemon的Register()注册新生成的container;
- 返回container。
newContainer()定义在/daemon/container.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| func (daemon *Daemon) newContainer(name string, config *containertypes.Config, imgID image.ID, managed bool) (*container.Container, error) { var ( id string err error noExplicitName = name == "" ) id, name, err = daemon.generateIDAndName(name) if err != nil { return nil, err } daemon.generateHostname(id, config) entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd) base := daemon.newBaseContainer(id) base.Created = time.Now().UTC() base.Managed = managed base.Path = entrypoint base.Args = args 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 }
|
start流程
Daemon的ContainerStart()实现定义在/daemon/start.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, validateHostname bool) error { container, err := daemon.GetContainer(name) ...... if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false, validateHostname); err != nil { return err } if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil { return err } return daemon.containerStart(container) }
|
ContainerStart()的流程如下:
- 调用GetContainer()获取name对应的container;
- 调用containerStart()启动容器。
containerStart()定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| func (daemon *Daemon) containerStart(container *container.Container) (err error) { ...... if err := daemon.conditionalMountOnStart(container); err != nil { return err } container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig) if err := daemon.initializeNetworking(container); err != nil { return err } spec, err := daemon.createSpec(container) if err != nil { return err } createOptions := []libcontainerd.CreateOption{libcontainerd.WithRestartManager(container.RestartManager(true))} copts, err := daemon.getLibcontainerdCreateOptions(container) if err != nil { return err } if copts != nil { createOptions = append(createOptions, *copts...) } if err := daemon.containerd.Create(container.ID, *spec, createOptions...); err != nil { errDesc := grpc.ErrorDesc(err) logrus.Errorf("Create container failed with error: %s", errDesc) if strings.Contains(errDesc, container.Path) && (strings.Contains(errDesc, "executable file not found") || strings.Contains(errDesc, "no such file or directory") || strings.Contains(errDesc, "system cannot find the file specified")) { container.SetExitCode(127) } if strings.Contains(errDesc, syscall.EACCES.Error()) { container.SetExitCode(126) } container.Reset(false) return fmt.Errorf("%s", errDesc) } return nil }
|
containerStart()的流程如下:
- 调用conditionalMountOnStart()执行挂载操作;
- 调用runconfig.SetDefaultNetModeIfBlank()确保NetworkMode存在;
- 调用initializeNetworking()初始化网络;
- 调用createSpec()创建spec文件,即runc启动配置文件,里面有网络信息,volume挂载信息等;
- 调用containerd client的Create()创建容器。
这里只分析conditionalMountOnStart()。conditionalMountOnStart()定义在/daemon/daemon_unix.go中:
1 2 3 4
| func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { return daemon.Mount(container) }
|
conditionalMountOnStart()调用Daemon的Mount(),Mount()定义在/daemon/daemon.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| func (daemon *Daemon) Mount(container *container.Container) error { dir, err := container.RWLayer.Mount(container.GetMountLabel()) if err != nil { return err } logrus.Debugf("container mounted via layerStore: %v", dir) if container.BaseFS != dir { if container.BaseFS != "" && runtime.GOOS != "windows" { daemon.Unmount(container) return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')", daemon.GraphDriverName(), container.ID, container.BaseFS, dir) } } container.BaseFS = dir return nil }
|
Mount()的流程如下:
- 执行挂载操作,挂载目录为dir;
- 如果container.BaseFS和dir不相等,则执行Unmount操作;
- 设置container.BaseFS=dir。
Mount()会执行两次,第一次是container.BaseFS和dir不相等,其作用就是设置container.BaseFS;第二次两者相等,则挂载状态将维持。
这里注释掉的内容是介绍如何从一个具体目录直接启动容器的,我们借用Docker label机制传入目录,然后设置container.BaseFS为传入的目录,从而直接从目录启动容器:
1 2 3 4 5 6 7 8
| if baseFSDir, found := container.Config.Labels["baseFSDir"]; found { if _, err := os.Lstat(baseFSDir); err != nil { return fmt.Errorf("Error: cannot find baseFS dir %s", baseFSDir) } container.BaseFS = baseFSDir return nil }
|
容器的创建和启动流程涉及到非常多的代码,这里只是做了一个流程上的分析,具体功能的代码实现将分散到Docker的各模块分析中。