我们知道runc是从spec文件,即config.json启动容器的,而Docker所做的事就是生成这份config.json。本次分析将介绍Docker如何生成config.json文件的,及config.json中主要字段的含义。

daemon.createSpec()

我们先来看Docker中容器启动函数containerStart(),定义在/daemon/start.go中:

1
2
3
4
5
6
7
8
9
10
11
func (daemon *Daemon) containerStart(container *container.Container) (err error) {
......
//***创建spec文件***//
//***定义在/daemon/oci_linux中***//
spec, err := daemon.createSpec(container)
if err != nil {
return err
}
......
return nil
}

createSpec()可以依据container的信息创建出spec文件。

createSpec()定义在/daemon/oci_linux.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
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//***创建oci的spec文件***//
func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) {
//***Fankang***//
//***生成spec模板***//
s := oci.DefaultSpec()
if err := daemon.populateCommonSpec(&s, c); err != nil {
return nil, err
}
var cgroupsPath string
scopePrefix := "docker"
parent := "/docker"
useSystemd := UsingSystemd(daemon.configStore)
if useSystemd {
parent = "system.slice"
}
if c.HostConfig.CgroupParent != "" {
parent = c.HostConfig.CgroupParent
} else if daemon.configStore.CgroupParent != "" {
parent = daemon.configStore.CgroupParent
}
if useSystemd {
cgroupsPath = parent + ":" + scopePrefix + ":" + c.ID
logrus.Debugf("createSpec: cgroupsPath: %s", cgroupsPath)
} else {
cgroupsPath = filepath.Join(parent, c.ID)
}
//***Fankang***//
//***设置spec的Linux.CgroupsPath字段***//
s.Linux.CgroupsPath = &cgroupsPath
//***Fankang***//
//***设置spec的Linux.Resources字段***//
if err := setResources(&s, c.HostConfig.Resources); err != nil {
return nil, fmt.Errorf("linux runtime spec resources: %v", err)
}
//***Fankang***//
//***设置spec的Linux.Resources.OOMScoreAdj字段***//
s.Linux.Resources.OOMScoreAdj = &c.HostConfig.OomScoreAdj
//***Fankang***//
//***设置spec的Linux.Sysctl字段***//
s.Linux.Sysctl = c.HostConfig.Sysctls
if err := setDevices(&s, c); err != nil {
return nil, fmt.Errorf("linux runtime spec devices: %v", err)
}
//***Fankang***//
//***设置spec的Process.Rlimits字段***//
if err := setRlimits(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux runtime spec rlimits: %v", err)
}
//***Fankang***//
//***设置spec的Process.User字段***//
if err := setUser(&s, c); err != nil {
return nil, fmt.Errorf("linux spec user: %v", err)
}
//***Fankang***//
//***设置spec的Linux.Namespace字段***//
if err := setNamespaces(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux spec namespaces: %v", err)
}
//***Fankang***//
//***设置spec的Proecess.capabilities字段***//
if err := setCapabilities(&s, c); err != nil {
return nil, fmt.Errorf("linux spec capabilities: %v", err)
}
//***Fankang***//
//***定义在/daemon/seccomp_linux.go中***//
//***设置spec的Linux.Seccomp字段***//
if err := setSeccomp(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux seccomp: %v", err)
}
//***Fankang***//
//***定义在/daemon/container_operations_unix.go***//
if err := daemon.setupIpcDirs(c); err != nil {
return nil, err
}
//***Fankang***//
//***ms: [{/var/lib/docker1/containers/f9df7a72e6891b2a3d5ef63ca1b7bc3f836706cd4293e558dda792d957b7da2c/resolv.conf /etc/resolv.conf true rprivate} {/var/lib/docker1/containers/f9df7a72e6891b2a3d5ef63ca1b7bc3f836706cd4293e558dda792d957b7da2c/hostname /etc/hostname true rprivate} {/var/lib/docker1/containers/f9df7a72e6891b2a3d5ef63ca1b7bc3f836706cd4293e558dda792d957b7da2c/hosts /etc/hosts true rprivate}]***//
ms, err := daemon.setupMounts(c)
if err != nil {
return nil, err
}
//***Fankang***//
//***c.IpcMounts(): [{/var/lib/docker1/containers/f9df7a72e6891b2a3d5ef63ca1b7bc3f836706cd4293e558dda792d957b7da2c/shm /dev/shm true rprivate}]***//
ms = append(ms, c.IpcMounts()...)
//***Fankang***//
//***c.TmpfsMounts(): []***//
ms = append(ms, c.TmpfsMounts()...)
sort.Sort(mounts(ms))
//***Fankang***//
//***设置spec的mounts字段***//
if err := setMounts(daemon, &s, c, ms); err != nil {
return nil, fmt.Errorf("linux mounts: %v", err)
}
//***Fankang***//
//***如果namespace中有network,且Path为空,则设置spec的hooks.prestart字段***//
for _, ns := range s.Linux.Namespaces {
if ns.Type == "network" && ns.Path == "" && !c.Config.NetworkDisabled {
target, err := os.Readlink(filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe"))
if err != nil {
return nil, err
}
s.Hooks = specs.Hooks{
Prestart: []specs.Hook{{
Path: target, // FIXME: cross-platform
//***Fankang***//
//***子命令为libnetwork-setkey***//
Args: []string{"libnetwork-setkey", c.ID, daemon.netController.ID()},
}},
}
}
}
if apparmor.IsEnabled() {
appArmorProfile := "docker-default"
if len(c.AppArmorProfile) > 0 {
appArmorProfile = c.AppArmorProfile
} else if c.HostConfig.Privileged {
appArmorProfile = "unconfined"
}
s.Process.ApparmorProfile = appArmorProfile
}
s.Process.SelinuxLabel = c.GetProcessLabel()
s.Process.NoNewPrivileges = c.NoNewPrivileges
s.Linux.MountLabel = c.MountLabel
return (*libcontainerd.Spec)(&s), nil
}

createSpec()的流程如下:

  1. oci.DefaultSpec()生成spec模板;
  2. 设置linux.cgroupsPath,表明容器的cgroup路径;
  3. 设置linux.resources,里面有devices, memory, cpu等信息;
  4. 设置linux.resources.oomScoreAdj,和oom的优先级相关;
  5. 设置linux.sysctl字段(不知道用途);
  6. 设置linux.resources.devices(不知道用途);
  7. 设置process.rlimits字段,为进程资源限制;
  8. 设置process.user字段;
  9. 设置linux.namespace字段,为容器需要创建的namespace;
  10. 设置process.capabilities字段,表明进程所拥有的能力;
  11. 设置linux.seccomp字段(不知道用途);
  12. 设置mounts字段;
  13. 如果需要,设置hooks;
  14. 设置其他字段。

这里如何提取container的信息设置spec的字段就不详细展开了。

config.json

下面我们来分析一个完整的Docker生成的config.json,使用’docker run -d -m 64M -c 100 nginx:v1’生成,文件位于/var/run/docker/libcontainerd/cotnainer-id/目录下:

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
{
//***容器版本***//
"ociVersion": "1.0.0-rc2-dev",
//***平台***//
"platform": {
"os": "linux",
"arch": "amd64"
},
//***容器进程信息***//
"process": {
//***猜测为console输出窗口大小***//
"consoleSize": {
"height": 0,
"width": 0
},
//***进程用户***//
"user": {
"uid": 0,
"gid": 0
},
//***进程启动命令***//
"args": [
"/usr/bin/supervisord"
],
//***进程的环境变量***//
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=3b267840d22d"
],
//***进程工作目录***//
"cwd": "/",
//***进程的特权***//
"capabilities": [
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE"
]
},
//***容器根目录***//
"root": {
"path": "/var/lib/docker1/aufs/mnt/1a6f845dd5db2efd6b23398c56a935a16ac8c2d8c69dc9284faabcbb8f953936"
},
//***容器的hostname***//
"hostname": "3b267840d22d",
//***挂载信息,对/proc,/dev等还不是很清楚为什么要挂载***//
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
......,
{
"destination": "/etc/resolv.conf",
"type": "bind",
"source": "/var/lib/docker1/containers/3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011/resolv.conf",
"options": [
"rbind",
"rprivate"
]
},
{
"destination": "/etc/hostname",
"type": "bind",
"source": "/var/lib/docker1/containers/3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011/hostname",
"options": [
"rbind",
"rprivate"
]
},
{
"destination": "/etc/hosts",
"type": "bind",
"source": "/var/lib/docker1/containers/3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011/hosts",
"options": [
"rbind",
"rprivate"
]
},
{
"destination": "/dev/shm",
"type": "bind",
"source": "/var/lib/docker1/containers/3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011/shm",
"options": [
"rbind",
"rprivate"
]
}
],
"hooks": {
//***调用libnetwork接口***//
"prestart": [
{
"path": "/home/fankang/docker/src/github.com/docker/docker/bundles/1.12.3/dynbinary-daemon/dockerd-1.12.3",
"args": [
"libnetwork-setkey",
"3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011",
"6ce40dca1e7066e077fd87e757d392e37733036c7b4508a89dcf6904e8196851"
]
}
]
},
"linux": {
"resources": {
//***devices中的major为主设备号,minor为次设备号,通过ll /dev可以查看***//
"devices": [
{
"allow": false,
"access": "rwm"
},
{
"allow": true,
"type": "c",
"major": 1,
"minor": 5,
"access": "rwm"
},
......
],
//***oom开关***//
"disableOOMKiller": false,
//***oom等级***//
"oomScoreAdj": 0,
//***限制容器内存使用,单位为b***//
"memory": {
"limit": 67108864,
"swap": 18446744073709552000,
"swappiness": 18446744073709552000
},
//***限制容器cpu使用***//
"cpu": {
"shares": 100
},
//***不知道用途***//
"pids": {
"limit": 0
},
//***限制容器磁盘读写速度***//
"blockIO": {
"blkioWeight": 0
}
},
//***容器所使用的cgroup名称***//
"cgroupsPath": "/docker/3b267840d22d94630aabd527dc6e5610c845b6e923e50f011d600ecbb83a8011",
//***容器所使用的namespace***//
"namespaces": [
{
"type": "mount"
},
//***如果有"path": "/proc/6002/ns/net",则表明容器使用指定的network namespace***//
{
"type": "network"
},
{
"type": "uts"
},
{
"type": "pid"
},
{
"type": "ipc"
}
],
//***容器中可以使用的设备***//
"devices": [
{
"path": "/dev/fuse",
"type": "c",
"major": 10,
"minor": 229,
"fileMode": 438,
"uid": 0,
"gid": 0
}
],
//***容器中需要屏蔽的文件,由模板自带,这几个文件用途,还有待进一步了解***//
"maskedPaths": [
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug"
],
//***表明容器内部只读的文件,由模板自带,这几个文件用途,还有待进一步了解***//
"readonlyPaths": [
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}

config.json文件非常复杂,现在也只有做个大概地了解,待以后慢慢深入。