我们知道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, 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
| func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) { 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) } s.Linux.CgroupsPath = &cgroupsPath if err := setResources(&s, c.HostConfig.Resources); err != nil { return nil, fmt.Errorf("linux runtime spec resources: %v", err) } s.Linux.Resources.OOMScoreAdj = &c.HostConfig.OomScoreAdj s.Linux.Sysctl = c.HostConfig.Sysctls if err := setDevices(&s, c); err != nil { return nil, fmt.Errorf("linux runtime spec devices: %v", err) } if err := setRlimits(daemon, &s, c); err != nil { return nil, fmt.Errorf("linux runtime spec rlimits: %v", err) } if err := setUser(&s, c); err != nil { return nil, fmt.Errorf("linux spec user: %v", err) } if err := setNamespaces(daemon, &s, c); err != nil { return nil, fmt.Errorf("linux spec namespaces: %v", err) } if err := setCapabilities(&s, c); err != nil { return nil, fmt.Errorf("linux spec capabilities: %v", err) } if err := setSeccomp(daemon, &s, c); err != nil { return nil, fmt.Errorf("linux seccomp: %v", err) } if err := daemon.setupIpcDirs(c); err != nil { return nil, err } ms, err := daemon.setupMounts(c) if err != nil { return nil, err } ms = append(ms, c.IpcMounts()...) ms = append(ms, c.TmpfsMounts()...) sort.Sort(mounts(ms)) if err := setMounts(daemon, &s, c, ms); err != nil { return nil, fmt.Errorf("linux mounts: %v", err) } 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, 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()的流程如下:
- oci.DefaultSpec()生成spec模板;
- 设置linux.cgroupsPath,表明容器的cgroup路径;
- 设置linux.resources,里面有devices, memory, cpu等信息;
- 设置linux.resources.oomScoreAdj,和oom的优先级相关;
- 设置linux.sysctl字段(不知道用途);
- 设置linux.resources.devices(不知道用途);
- 设置process.rlimits字段,为进程资源限制;
- 设置process.user字段;
- 设置linux.namespace字段,为容器需要创建的namespace;
- 设置process.capabilities字段,表明进程所拥有的能力;
- 设置linux.seccomp字段(不知道用途);
- 设置mounts字段;
- 如果需要,设置hooks;
- 设置其他字段。
这里如何提取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文件非常复杂,现在也只有做个大概地了解,待以后慢慢深入。