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()
// TODO: pass this as an argument
cmdPath := "run"
......
//***1.8.3中只有config, hostConfig,现在多了一个networkingConfig***//
//***config, hostConfig和networkingConfig详见docker inspect的结果***//
config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
// just in case the Parse does not exit
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")
}
//***检测DNS是否合法***//
if len(hostConfig.DNS) > 0 {
// check the DNS settings passed via --dns against
// localhost regexp to warn if they are trying to
// set a DNS to a localhost address
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
}
}
}
......
//***WithCancel context***//
ctx, cancelFun := context.WithCancel(context.Background())
//***create container***//
createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
......
//***处理attach相关的参数***//
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
if attach {
......
}
//***defer这种形式的用法第一次见到,可以学着用下***//
//***如需auto remove,在defer中remove container***//
if opts.autoRemove {
defer func() {
// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
// and thus the container would not be removed.
if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil {
fmt.Fprintf(stderr, "%v\n", err)
}
}()
}
//start the container
//***Fankang***//
//***start container***//
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
......
}
......
}

runRun()的流程如下:

  1. 依据配置文件生成config, hostConfig, networkingConfig;
  2. 调用createContainer()创建容器;
  3. 调用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) {
......
//create the container
//***尝试create container***//
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
//if image not found try to pull it
//***处理image不存在的情况***//
if err != nil {
if apiclient.IsErrImageNotFound(err) && ref != nil {
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String())
// we don't want to write to stdout anything apart from container.ID
//***定义在本文件中,下载镜像用***//
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
}
}
// Retry
var retryErr error
//***下载完镜像之后,再重新create container***//
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
if retryErr != nil {
return nil, retryErr
}
} else {
return nil, err
}
}
......
return &response, nil
}

createContainer()的流程如下:

  1. 调用docker client的ContainerCreate()创建容器;
  2. 如果创建过程出错,则尝试下载镜像,然后再调用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
}
//***获取container name***//
name := r.Form.Get("name")
//***从Body中解析出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")
validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
//***调用ContainerCreate()***//
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()的流程如下:

  1. 从请求表单中解析出config, hostConfig和networkingConfig;
  2. 调用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
// A non-nil json object is at least 7 characters.
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")
//***调用ContainerStart()***//
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
//***ContainerCreate()方法定义***//
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
//***具体实现containerCreate()的函数***//
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")
}
//***检查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
}
//***调用create()函数***//
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()的流程如下:

  1. 检查container的各项参数;
  2. 检查网络参数;
  3. 调用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
//***解析参数,并依据流程create container***//
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
}
imgID = img.ID()
}
//***merge config***//
if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
return nil, err
}
//***处理log的配置***//
if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
return nil, err
}
//***调用newContainer()来生成一个container***//
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)
}
}
}()
//***设置container安全相关的参数***//
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
}
//***把容器相关的hostConfig保存到硬盘上***//
//***就是更新container中的HostConfig***//
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)
}
}
}()
//***createContainerPlatformSpecificSettings()会调用daemon.Mount()和daemon.Unmount()***//
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)
if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
return nil, err
}
//***把container的json写到容器目录***//
if err := container.ToDisk(); err != nil {
logrus.Errorf("Error saving new container to disk: %v", err)
return nil, err
}
//***注册container***//
if err := daemon.Register(container); err != nil {
return nil, err
}
daemon.LogContainerEvent(container, "create")
return container, nil
}

  1. 获取imageID;
  2. merge config;
  3. 处理log的配置;
  4. 调用newContainer()通过电脑一个container;
  5. 设置container安全相关的参数;
  6. 调用setRWLayer()创建读写层;
  7. 创建容器根目录;
  8. 设置网络模式;
  9. 把container的json写到容器目录;
  10. 调用Daemon的Register()注册新生成的container;
  11. 返回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
//***生成一个新container***//
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***//
id, name, err = daemon.generateIDAndName(name)
if err != nil {
return nil, err
}
//***设置hostname字段***//
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 //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
}

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
//***ContainerStart()实现,用来启动一个容器***//
func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, validateHostname bool) error {
//***获取container***//
container, err := daemon.GetContainer(name)
......
// check if hostConfig is in line with the current system settings.
// It may happen cgroups are umounted or the like.
//****检测HostConfig***//
if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false, validateHostname); err != nil {
return err
}
// Adapt for old containers in case we have updates in this function and
// old containers never have chance to call the new function in create stage.
if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
return err
}
//***调用containerStart()***//
return daemon.containerStart(container)
}

ContainerStart()的流程如下:

  1. 调用GetContainer()获取name对应的container;
  2. 调用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) {
......
//***定义在/daemon/daemon_unix.go中***//
if err := daemon.conditionalMountOnStart(container); err != nil {
return err
}
// Make sure NetworkMode has an acceptable value. We do this to ensure
// backwards API compatibility.
//***确保NetworkMode存在***//
container.HostConfig = runconfig.SetDefaultNetModeIfBlank(container.HostConfig)
//***初始化网络***//
//***定义在/daemon/container_operations.go中***/
if err := daemon.initializeNetworking(container); err != nil {
return err
}
//***创建spec文件***//
//***定义在/daemon/oci_linux中***//
spec, err := daemon.createSpec(container)
if err != nil {
return err
}
//***获取createOptions***//
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...)
}
//***调用libcontainer的Create()函数创建容器***//
//***定义在/libcontainerd/client_linux.go***//
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 we receive an internal error from the initial start of a container then lets
// return it instead of entering the restart loop
// set to 127 for container cmd not found/does not exist)
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)
}
// set to 126 for container cmd can't be invoked errors
if strings.Contains(errDesc, syscall.EACCES.Error()) {
container.SetExitCode(126)
}
container.Reset(false)
return fmt.Errorf("%s", errDesc)
}
return nil
}

containerStart()的流程如下:

  1. 调用conditionalMountOnStart()执行挂载操作;
  2. 调用runconfig.SetDefaultNetModeIfBlank()确保NetworkMode存在;
  3. 调用initializeNetworking()初始化网络;
  4. 调用createSpec()创建spec文件,即runc启动配置文件,里面有网络信息,volume挂载信息等;
  5. 调用containerd client的Create()创建容器。

这里只分析conditionalMountOnStart()。conditionalMountOnStart()定义在/daemon/daemon_unix.go中:

1
2
3
4
//***daemon.Mount()定义在/daemon/daemon.go中***//
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 {
//***从具体目录启动添加的代码***//
// 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
// }
//***RWLayer.Mount()定义在/layer/mounted_layer.go中***//
//***docker run -d nginx:v1命令的container.GetMountLabel()为空***//
dir, err := container.RWLayer.Mount(container.GetMountLabel())
if err != nil {
return err
}
logrus.Debugf("container mounted via layerStore: %v", dir)
//***create.go中调用:***//
//***container.BaseFS为""***//
//***dir: /var/lib/docker/aufs/mnt/c9a78db9f0c68201d4ba35720342a8c65751faabb514685fcb14d8b092eaaf09***//
//***start.go中调用:***//
//***container.BaseFS: /var/lib/docker/aufs/mnt/c9a78db9f0c68201d4ba35720342a8c65751faabb514685fcb14d8b092eaaf09***//
//***dir: /var/lib/docker/aufs/mnt/c9a78db9f0c68201d4ba35720342a8c65751faabb514685fcb14d8b092eaaf09***//
if container.BaseFS != dir {
// The mount path reported by the graph driver should always be trusted on Windows, since the
// volume path for a given mounted layer may change over time. This should only be an error
// on non-Windows operating systems.
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: /var/lib/docker/aufs/mnt/7471e8f2861af2df8a7906dab8d64409b57ff46164e30ee5e113f74be93e8e23***//
container.BaseFS = dir // TODO: combine these fields
return nil
}

Mount()的流程如下:

  1. 执行挂载操作,挂载目录为dir;
  2. 如果container.BaseFS和dir不相等,则执行Unmount操作;
  3. 设置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的各模块分析中。