github.com/ojongerius/docker@v1.11.2/daemon/daemon_unix.go (about) 1 // +build linux freebsd 2 3 package daemon 4 5 import ( 6 "fmt" 7 "io/ioutil" 8 "net" 9 "os" 10 "path/filepath" 11 "runtime" 12 "runtime/debug" 13 "strconv" 14 "strings" 15 "syscall" 16 "time" 17 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/docker/container" 20 "github.com/docker/docker/image" 21 "github.com/docker/docker/layer" 22 "github.com/docker/docker/pkg/idtools" 23 "github.com/docker/docker/pkg/parsers" 24 "github.com/docker/docker/pkg/parsers/kernel" 25 "github.com/docker/docker/pkg/sysinfo" 26 "github.com/docker/docker/reference" 27 "github.com/docker/docker/runconfig" 28 runconfigopts "github.com/docker/docker/runconfig/opts" 29 "github.com/docker/engine-api/types" 30 pblkiodev "github.com/docker/engine-api/types/blkiodev" 31 containertypes "github.com/docker/engine-api/types/container" 32 "github.com/docker/libnetwork" 33 nwconfig "github.com/docker/libnetwork/config" 34 "github.com/docker/libnetwork/drivers/bridge" 35 "github.com/docker/libnetwork/ipamutils" 36 "github.com/docker/libnetwork/netlabel" 37 "github.com/docker/libnetwork/options" 38 lntypes "github.com/docker/libnetwork/types" 39 "github.com/opencontainers/runc/libcontainer/label" 40 "github.com/opencontainers/runc/libcontainer/user" 41 "github.com/opencontainers/specs/specs-go" 42 ) 43 44 const ( 45 // See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269 46 linuxMinCPUShares = 2 47 linuxMaxCPUShares = 262144 48 platformSupported = true 49 // It's not kernel limit, we want this 4M limit to supply a reasonable functional container 50 linuxMinMemory = 4194304 51 // constants for remapped root settings 52 defaultIDSpecifier string = "default" 53 defaultRemappedID string = "dockremap" 54 55 // constant for cgroup drivers 56 cgroupFsDriver = "cgroupfs" 57 cgroupSystemdDriver = "systemd" 58 ) 59 60 func getMemoryResources(config containertypes.Resources) *specs.Memory { 61 memory := specs.Memory{} 62 63 if config.Memory > 0 { 64 limit := uint64(config.Memory) 65 memory.Limit = &limit 66 } 67 68 if config.MemoryReservation > 0 { 69 reservation := uint64(config.MemoryReservation) 70 memory.Reservation = &reservation 71 } 72 73 if config.MemorySwap != 0 { 74 swap := uint64(config.MemorySwap) 75 memory.Swap = &swap 76 } 77 78 if config.MemorySwappiness != nil { 79 swappiness := uint64(*config.MemorySwappiness) 80 memory.Swappiness = &swappiness 81 } 82 83 if config.KernelMemory != 0 { 84 kernelMemory := uint64(config.KernelMemory) 85 memory.Kernel = &kernelMemory 86 } 87 88 return &memory 89 } 90 91 func getCPUResources(config containertypes.Resources) *specs.CPU { 92 cpu := specs.CPU{} 93 94 if config.CPUShares != 0 { 95 shares := uint64(config.CPUShares) 96 cpu.Shares = &shares 97 } 98 99 if config.CpusetCpus != "" { 100 cpuset := config.CpusetCpus 101 cpu.Cpus = &cpuset 102 } 103 104 if config.CpusetMems != "" { 105 cpuset := config.CpusetMems 106 cpu.Mems = &cpuset 107 } 108 109 if config.CPUPeriod != 0 { 110 period := uint64(config.CPUPeriod) 111 cpu.Period = &period 112 } 113 114 if config.CPUQuota != 0 { 115 quota := uint64(config.CPUQuota) 116 cpu.Quota = "a 117 } 118 119 return &cpu 120 } 121 122 func getBlkioWeightDevices(config containertypes.Resources) ([]specs.WeightDevice, error) { 123 var stat syscall.Stat_t 124 var blkioWeightDevices []specs.WeightDevice 125 126 for _, weightDevice := range config.BlkioWeightDevice { 127 if err := syscall.Stat(weightDevice.Path, &stat); err != nil { 128 return nil, err 129 } 130 weight := weightDevice.Weight 131 d := specs.WeightDevice{Weight: &weight} 132 d.Major = int64(stat.Rdev / 256) 133 d.Minor = int64(stat.Rdev % 256) 134 blkioWeightDevices = append(blkioWeightDevices, d) 135 } 136 137 return blkioWeightDevices, nil 138 } 139 140 func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error { 141 var ( 142 labelOpts []string 143 err error 144 ) 145 146 for _, opt := range config.SecurityOpt { 147 if opt == "no-new-privileges" { 148 container.NoNewPrivileges = true 149 } else { 150 var con []string 151 if strings.Contains(opt, "=") { 152 con = strings.SplitN(opt, "=", 2) 153 } else if strings.Contains(opt, ":") { 154 con = strings.SplitN(opt, ":", 2) 155 logrus.Warnf("Security options with `:` as a separator are deprecated and will be completely unsupported in 1.13, use `=` instead.") 156 } 157 158 if len(con) != 2 { 159 return fmt.Errorf("Invalid --security-opt 1: %q", opt) 160 } 161 162 switch con[0] { 163 case "label": 164 labelOpts = append(labelOpts, con[1]) 165 case "apparmor": 166 container.AppArmorProfile = con[1] 167 case "seccomp": 168 container.SeccompProfile = con[1] 169 default: 170 return fmt.Errorf("Invalid --security-opt 2: %q", opt) 171 } 172 } 173 } 174 175 container.ProcessLabel, container.MountLabel, err = label.InitLabels(labelOpts) 176 return err 177 } 178 179 func getBlkioReadIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { 180 var blkioReadIOpsDevice []specs.ThrottleDevice 181 var stat syscall.Stat_t 182 183 for _, iopsDevice := range config.BlkioDeviceReadIOps { 184 if err := syscall.Stat(iopsDevice.Path, &stat); err != nil { 185 return nil, err 186 } 187 rate := iopsDevice.Rate 188 d := specs.ThrottleDevice{Rate: &rate} 189 d.Major = int64(stat.Rdev / 256) 190 d.Minor = int64(stat.Rdev % 256) 191 blkioReadIOpsDevice = append(blkioReadIOpsDevice, d) 192 } 193 194 return blkioReadIOpsDevice, nil 195 } 196 197 func getBlkioWriteIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { 198 var blkioWriteIOpsDevice []specs.ThrottleDevice 199 var stat syscall.Stat_t 200 201 for _, iopsDevice := range config.BlkioDeviceWriteIOps { 202 if err := syscall.Stat(iopsDevice.Path, &stat); err != nil { 203 return nil, err 204 } 205 rate := iopsDevice.Rate 206 d := specs.ThrottleDevice{Rate: &rate} 207 d.Major = int64(stat.Rdev / 256) 208 d.Minor = int64(stat.Rdev % 256) 209 blkioWriteIOpsDevice = append(blkioWriteIOpsDevice, d) 210 } 211 212 return blkioWriteIOpsDevice, nil 213 } 214 215 func getBlkioReadBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { 216 var blkioReadBpsDevice []specs.ThrottleDevice 217 var stat syscall.Stat_t 218 219 for _, bpsDevice := range config.BlkioDeviceReadBps { 220 if err := syscall.Stat(bpsDevice.Path, &stat); err != nil { 221 return nil, err 222 } 223 rate := bpsDevice.Rate 224 d := specs.ThrottleDevice{Rate: &rate} 225 d.Major = int64(stat.Rdev / 256) 226 d.Minor = int64(stat.Rdev % 256) 227 blkioReadBpsDevice = append(blkioReadBpsDevice, d) 228 } 229 230 return blkioReadBpsDevice, nil 231 } 232 233 func getBlkioWriteBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) { 234 var blkioWriteBpsDevice []specs.ThrottleDevice 235 var stat syscall.Stat_t 236 237 for _, bpsDevice := range config.BlkioDeviceWriteBps { 238 if err := syscall.Stat(bpsDevice.Path, &stat); err != nil { 239 return nil, err 240 } 241 rate := bpsDevice.Rate 242 d := specs.ThrottleDevice{Rate: &rate} 243 d.Major = int64(stat.Rdev / 256) 244 d.Minor = int64(stat.Rdev % 256) 245 blkioWriteBpsDevice = append(blkioWriteBpsDevice, d) 246 } 247 248 return blkioWriteBpsDevice, nil 249 } 250 251 func checkKernelVersion(k, major, minor int) bool { 252 if v, err := kernel.GetKernelVersion(); err != nil { 253 logrus.Warnf("%s", err) 254 } else { 255 if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: k, Major: major, Minor: minor}) < 0 { 256 return false 257 } 258 } 259 return true 260 } 261 262 func checkKernel() error { 263 // Check for unsupported kernel versions 264 // FIXME: it would be cleaner to not test for specific versions, but rather 265 // test for specific functionalities. 266 // Unfortunately we can't test for the feature "does not cause a kernel panic" 267 // without actually causing a kernel panic, so we need this workaround until 268 // the circumstances of pre-3.10 crashes are clearer. 269 // For details see https://github.com/docker/docker/issues/407 270 if !checkKernelVersion(3, 10, 0) { 271 v, _ := kernel.GetKernelVersion() 272 if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" { 273 logrus.Warnf("Your Linux kernel version %s can be unstable running docker. Please upgrade your kernel to 3.10.0.", v.String()) 274 } 275 } 276 return nil 277 } 278 279 // adaptContainerSettings is called during container creation to modify any 280 // settings necessary in the HostConfig structure. 281 func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConfig, adjustCPUShares bool) error { 282 if adjustCPUShares && hostConfig.CPUShares > 0 { 283 // Handle unsupported CPUShares 284 if hostConfig.CPUShares < linuxMinCPUShares { 285 logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, linuxMinCPUShares) 286 hostConfig.CPUShares = linuxMinCPUShares 287 } else if hostConfig.CPUShares > linuxMaxCPUShares { 288 logrus.Warnf("Changing requested CPUShares of %d to maximum allowed of %d", hostConfig.CPUShares, linuxMaxCPUShares) 289 hostConfig.CPUShares = linuxMaxCPUShares 290 } 291 } 292 if hostConfig.Memory > 0 && hostConfig.MemorySwap == 0 { 293 // By default, MemorySwap is set to twice the size of Memory. 294 hostConfig.MemorySwap = hostConfig.Memory * 2 295 } 296 if hostConfig.ShmSize == 0 { 297 hostConfig.ShmSize = container.DefaultSHMSize 298 } 299 var err error 300 if hostConfig.SecurityOpt == nil { 301 hostConfig.SecurityOpt, err = daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode) 302 if err != nil { 303 return err 304 } 305 } 306 if hostConfig.MemorySwappiness == nil { 307 defaultSwappiness := int64(-1) 308 hostConfig.MemorySwappiness = &defaultSwappiness 309 } 310 if hostConfig.OomKillDisable == nil { 311 defaultOomKillDisable := false 312 hostConfig.OomKillDisable = &defaultOomKillDisable 313 } 314 315 return nil 316 } 317 318 func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo, update bool) ([]string, error) { 319 warnings := []string{} 320 321 // memory subsystem checks and adjustments 322 if resources.Memory != 0 && resources.Memory < linuxMinMemory { 323 return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") 324 } 325 if resources.Memory > 0 && !sysInfo.MemoryLimit { 326 warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") 327 logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") 328 resources.Memory = 0 329 resources.MemorySwap = -1 330 } 331 if resources.Memory > 0 && resources.MemorySwap != -1 && !sysInfo.SwapLimit { 332 warnings = append(warnings, "Your kernel does not support swap limit capabilities, memory limited without swap.") 333 logrus.Warnf("Your kernel does not support swap limit capabilities, memory limited without swap.") 334 resources.MemorySwap = -1 335 } 336 if resources.Memory > 0 && resources.MemorySwap > 0 && resources.MemorySwap < resources.Memory { 337 return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.") 338 } 339 if resources.Memory == 0 && resources.MemorySwap > 0 && !update { 340 return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") 341 } 342 if resources.MemorySwappiness != nil && *resources.MemorySwappiness != -1 && !sysInfo.MemorySwappiness { 343 warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") 344 logrus.Warnf("Your kernel does not support memory swappiness capabilities, memory swappiness discarded.") 345 resources.MemorySwappiness = nil 346 } 347 if resources.MemorySwappiness != nil { 348 swappiness := *resources.MemorySwappiness 349 if swappiness < -1 || swappiness > 100 { 350 return warnings, fmt.Errorf("Invalid value: %v, valid memory swappiness range is 0-100.", swappiness) 351 } 352 } 353 if resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { 354 warnings = append(warnings, "Your kernel does not support memory soft limit capabilities. Limitation discarded.") 355 logrus.Warnf("Your kernel does not support memory soft limit capabilities. Limitation discarded.") 356 resources.MemoryReservation = 0 357 } 358 if resources.Memory > 0 && resources.MemoryReservation > 0 && resources.Memory < resources.MemoryReservation { 359 return warnings, fmt.Errorf("Minimum memory limit should be larger than memory reservation limit, see usage.") 360 } 361 if resources.KernelMemory > 0 && !sysInfo.KernelMemory { 362 warnings = append(warnings, "Your kernel does not support kernel memory limit capabilities. Limitation discarded.") 363 logrus.Warnf("Your kernel does not support kernel memory limit capabilities. Limitation discarded.") 364 resources.KernelMemory = 0 365 } 366 if resources.KernelMemory > 0 && resources.KernelMemory < linuxMinMemory { 367 return warnings, fmt.Errorf("Minimum kernel memory limit allowed is 4MB") 368 } 369 if resources.KernelMemory > 0 && !checkKernelVersion(4, 0, 0) { 370 warnings = append(warnings, "You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable.") 371 logrus.Warnf("You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable.") 372 } 373 if resources.OomKillDisable != nil && !sysInfo.OomKillDisable { 374 // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point 375 // warning the caller if they already wanted the feature to be off 376 if *resources.OomKillDisable { 377 warnings = append(warnings, "Your kernel does not support OomKillDisable, OomKillDisable discarded.") 378 logrus.Warnf("Your kernel does not support OomKillDisable, OomKillDisable discarded.") 379 } 380 resources.OomKillDisable = nil 381 } 382 383 if resources.PidsLimit != 0 && !sysInfo.PidsLimit { 384 warnings = append(warnings, "Your kernel does not support pids limit capabilities, pids limit discarded.") 385 logrus.Warnf("Your kernel does not support pids limit capabilities, pids limit discarded.") 386 resources.PidsLimit = 0 387 } 388 389 // cpu subsystem checks and adjustments 390 if resources.CPUShares > 0 && !sysInfo.CPUShares { 391 warnings = append(warnings, "Your kernel does not support CPU shares. Shares discarded.") 392 logrus.Warnf("Your kernel does not support CPU shares. Shares discarded.") 393 resources.CPUShares = 0 394 } 395 if resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod { 396 warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.") 397 logrus.Warnf("Your kernel does not support CPU cfs period. Period discarded.") 398 resources.CPUPeriod = 0 399 } 400 if resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota { 401 warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.") 402 logrus.Warnf("Your kernel does not support CPU cfs quota. Quota discarded.") 403 resources.CPUQuota = 0 404 } 405 406 // cpuset subsystem checks and adjustments 407 if (resources.CpusetCpus != "" || resources.CpusetMems != "") && !sysInfo.Cpuset { 408 warnings = append(warnings, "Your kernel does not support cpuset. Cpuset discarded.") 409 logrus.Warnf("Your kernel does not support cpuset. Cpuset discarded.") 410 resources.CpusetCpus = "" 411 resources.CpusetMems = "" 412 } 413 cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(resources.CpusetCpus) 414 if err != nil { 415 return warnings, fmt.Errorf("Invalid value %s for cpuset cpus.", resources.CpusetCpus) 416 } 417 if !cpusAvailable { 418 return warnings, fmt.Errorf("Requested CPUs are not available - requested %s, available: %s.", resources.CpusetCpus, sysInfo.Cpus) 419 } 420 memsAvailable, err := sysInfo.IsCpusetMemsAvailable(resources.CpusetMems) 421 if err != nil { 422 return warnings, fmt.Errorf("Invalid value %s for cpuset mems.", resources.CpusetMems) 423 } 424 if !memsAvailable { 425 return warnings, fmt.Errorf("Requested memory nodes are not available - requested %s, available: %s.", resources.CpusetMems, sysInfo.Mems) 426 } 427 428 // blkio subsystem checks and adjustments 429 if resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { 430 warnings = append(warnings, "Your kernel does not support Block I/O weight. Weight discarded.") 431 logrus.Warnf("Your kernel does not support Block I/O weight. Weight discarded.") 432 resources.BlkioWeight = 0 433 } 434 if resources.BlkioWeight > 0 && (resources.BlkioWeight < 10 || resources.BlkioWeight > 1000) { 435 return warnings, fmt.Errorf("Range of blkio weight is from 10 to 1000.") 436 } 437 if len(resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { 438 warnings = append(warnings, "Your kernel does not support Block I/O weight_device.") 439 logrus.Warnf("Your kernel does not support Block I/O weight_device. Weight-device discarded.") 440 resources.BlkioWeightDevice = []*pblkiodev.WeightDevice{} 441 } 442 if len(resources.BlkioDeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { 443 warnings = append(warnings, "Your kernel does not support Block read limit in bytes per second.") 444 logrus.Warnf("Your kernel does not support Block I/O read limit in bytes per second. --device-read-bps discarded.") 445 resources.BlkioDeviceReadBps = []*pblkiodev.ThrottleDevice{} 446 } 447 if len(resources.BlkioDeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { 448 warnings = append(warnings, "Your kernel does not support Block write limit in bytes per second.") 449 logrus.Warnf("Your kernel does not support Block I/O write limit in bytes per second. --device-write-bps discarded.") 450 resources.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{} 451 } 452 if len(resources.BlkioDeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { 453 warnings = append(warnings, "Your kernel does not support Block read limit in IO per second.") 454 logrus.Warnf("Your kernel does not support Block I/O read limit in IO per second. -device-read-iops discarded.") 455 resources.BlkioDeviceReadIOps = []*pblkiodev.ThrottleDevice{} 456 } 457 if len(resources.BlkioDeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { 458 warnings = append(warnings, "Your kernel does not support Block write limit in IO per second.") 459 logrus.Warnf("Your kernel does not support Block I/O write limit in IO per second. --device-write-iops discarded.") 460 resources.BlkioDeviceWriteIOps = []*pblkiodev.ThrottleDevice{} 461 } 462 463 return warnings, nil 464 } 465 466 func (daemon *Daemon) getCgroupDriver() string { 467 cgroupDriver := cgroupFsDriver 468 469 if UsingSystemd(daemon.configStore) { 470 cgroupDriver = cgroupSystemdDriver 471 } 472 return cgroupDriver 473 } 474 475 // getCD gets the raw value of the native.cgroupdriver option, if set. 476 func getCD(config *Config) string { 477 for _, option := range config.ExecOptions { 478 key, val, err := parsers.ParseKeyValueOpt(option) 479 if err != nil || !strings.EqualFold(key, "native.cgroupdriver") { 480 continue 481 } 482 return val 483 } 484 return "" 485 } 486 487 // VerifyCgroupDriver validates native.cgroupdriver 488 func VerifyCgroupDriver(config *Config) error { 489 cd := getCD(config) 490 if cd == "" || cd == cgroupFsDriver || cd == cgroupSystemdDriver { 491 return nil 492 } 493 return fmt.Errorf("native.cgroupdriver option %s not supported", cd) 494 } 495 496 // UsingSystemd returns true if cli option includes native.cgroupdriver=systemd 497 func UsingSystemd(config *Config) bool { 498 return getCD(config) == cgroupSystemdDriver 499 } 500 501 // verifyPlatformContainerSettings performs platform-specific validation of the 502 // hostconfig and config structures. 503 func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { 504 warnings := []string{} 505 sysInfo := sysinfo.New(true) 506 507 warnings, err := daemon.verifyExperimentalContainerSettings(hostConfig, config) 508 if err != nil { 509 return warnings, err 510 } 511 512 w, err := verifyContainerResources(&hostConfig.Resources, sysInfo, update) 513 if err != nil { 514 return warnings, err 515 } 516 warnings = append(warnings, w...) 517 518 if hostConfig.ShmSize < 0 { 519 return warnings, fmt.Errorf("SHM size must be greater then 0") 520 } 521 522 if hostConfig.OomScoreAdj < -1000 || hostConfig.OomScoreAdj > 1000 { 523 return warnings, fmt.Errorf("Invalid value %d, range for oom score adj is [-1000, 1000].", hostConfig.OomScoreAdj) 524 } 525 if sysInfo.IPv4ForwardingDisabled { 526 warnings = append(warnings, "IPv4 forwarding is disabled. Networking will not work.") 527 logrus.Warnf("IPv4 forwarding is disabled. Networking will not work") 528 } 529 // check for various conflicting options with user namespaces 530 if daemon.configStore.RemappedRoot != "" && hostConfig.UsernsMode.IsPrivate() { 531 if hostConfig.Privileged { 532 return warnings, fmt.Errorf("Privileged mode is incompatible with user namespaces") 533 } 534 if hostConfig.NetworkMode.IsHost() { 535 return warnings, fmt.Errorf("Cannot share the host's network namespace when user namespaces are enabled") 536 } 537 if hostConfig.PidMode.IsHost() { 538 return warnings, fmt.Errorf("Cannot share the host PID namespace when user namespaces are enabled") 539 } 540 if hostConfig.ReadonlyRootfs { 541 return warnings, fmt.Errorf("Cannot use the --read-only option when user namespaces are enabled") 542 } 543 } 544 if hostConfig.CgroupParent != "" && UsingSystemd(daemon.configStore) { 545 // CgroupParent for systemd cgroup should be named as "xxx.slice" 546 if len(hostConfig.CgroupParent) <= 6 || !strings.HasSuffix(hostConfig.CgroupParent, ".slice") { 547 return warnings, fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"") 548 } 549 } 550 return warnings, nil 551 } 552 553 // verifyDaemonSettings performs validation of daemon config struct 554 func verifyDaemonSettings(config *Config) error { 555 // Check for mutually incompatible config options 556 if config.bridgeConfig.Iface != "" && config.bridgeConfig.IP != "" { 557 return fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one") 558 } 559 if !config.bridgeConfig.EnableIPTables && !config.bridgeConfig.InterContainerCommunication { 560 return fmt.Errorf("You specified --iptables=false with --icc=false. ICC=false uses iptables to function. Please set --icc or --iptables to true") 561 } 562 if !config.bridgeConfig.EnableIPTables && config.bridgeConfig.EnableIPMasq { 563 config.bridgeConfig.EnableIPMasq = false 564 } 565 if err := VerifyCgroupDriver(config); err != nil { 566 return err 567 } 568 if config.CgroupParent != "" && UsingSystemd(config) { 569 if len(config.CgroupParent) <= 6 || !strings.HasSuffix(config.CgroupParent, ".slice") { 570 return fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"") 571 } 572 } 573 return nil 574 } 575 576 // checkSystem validates platform-specific requirements 577 func checkSystem() error { 578 if os.Geteuid() != 0 { 579 return fmt.Errorf("The Docker daemon needs to be run as root") 580 } 581 return checkKernel() 582 } 583 584 // configureMaxThreads sets the Go runtime max threads threshold 585 // which is 90% of the kernel setting from /proc/sys/kernel/threads-max 586 func configureMaxThreads(config *Config) error { 587 mt, err := ioutil.ReadFile("/proc/sys/kernel/threads-max") 588 if err != nil { 589 return err 590 } 591 mtint, err := strconv.Atoi(strings.TrimSpace(string(mt))) 592 if err != nil { 593 return err 594 } 595 maxThreads := (mtint / 100) * 90 596 debug.SetMaxThreads(maxThreads) 597 logrus.Debugf("Golang's threads limit set to %d", maxThreads) 598 return nil 599 } 600 601 // configureKernelSecuritySupport configures and validate security support for the kernel 602 func configureKernelSecuritySupport(config *Config, driverName string) error { 603 if config.EnableSelinuxSupport { 604 if selinuxEnabled() { 605 // As Docker on overlayFS and SELinux are incompatible at present, error on overlayfs being enabled 606 if driverName == "overlay" { 607 return fmt.Errorf("SELinux is not supported with the %s graph driver", driverName) 608 } 609 logrus.Debug("SELinux enabled successfully") 610 } else { 611 logrus.Warn("Docker could not enable SELinux on the host system") 612 } 613 } else { 614 selinuxSetDisabled() 615 } 616 return nil 617 } 618 619 func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) { 620 netOptions, err := daemon.networkOptions(config) 621 if err != nil { 622 return nil, err 623 } 624 625 controller, err := libnetwork.New(netOptions...) 626 if err != nil { 627 return nil, fmt.Errorf("error obtaining controller instance: %v", err) 628 } 629 630 // Initialize default network on "null" 631 if _, err := controller.NewNetwork("null", "none", libnetwork.NetworkOptionPersist(false)); err != nil { 632 return nil, fmt.Errorf("Error creating default \"null\" network: %v", err) 633 } 634 635 // Initialize default network on "host" 636 if _, err := controller.NewNetwork("host", "host", libnetwork.NetworkOptionPersist(false)); err != nil { 637 return nil, fmt.Errorf("Error creating default \"host\" network: %v", err) 638 } 639 640 if !config.DisableBridge { 641 // Initialize default driver "bridge" 642 if err := initBridgeDriver(controller, config); err != nil { 643 return nil, err 644 } 645 } 646 647 return controller, nil 648 } 649 650 func driverOptions(config *Config) []nwconfig.Option { 651 bridgeConfig := options.Generic{ 652 "EnableIPForwarding": config.bridgeConfig.EnableIPForward, 653 "EnableIPTables": config.bridgeConfig.EnableIPTables, 654 "EnableUserlandProxy": config.bridgeConfig.EnableUserlandProxy} 655 bridgeOption := options.Generic{netlabel.GenericData: bridgeConfig} 656 657 dOptions := []nwconfig.Option{} 658 dOptions = append(dOptions, nwconfig.OptionDriverConfig("bridge", bridgeOption)) 659 return dOptions 660 } 661 662 func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { 663 if n, err := controller.NetworkByName("bridge"); err == nil { 664 if err = n.Delete(); err != nil { 665 return fmt.Errorf("could not delete the default bridge network: %v", err) 666 } 667 } 668 669 bridgeName := bridge.DefaultBridgeName 670 if config.bridgeConfig.Iface != "" { 671 bridgeName = config.bridgeConfig.Iface 672 } 673 netOption := map[string]string{ 674 bridge.BridgeName: bridgeName, 675 bridge.DefaultBridge: strconv.FormatBool(true), 676 netlabel.DriverMTU: strconv.Itoa(config.Mtu), 677 bridge.EnableIPMasquerade: strconv.FormatBool(config.bridgeConfig.EnableIPMasq), 678 bridge.EnableICC: strconv.FormatBool(config.bridgeConfig.InterContainerCommunication), 679 } 680 681 // --ip processing 682 if config.bridgeConfig.DefaultIP != nil { 683 netOption[bridge.DefaultBindingIP] = config.bridgeConfig.DefaultIP.String() 684 } 685 686 var ( 687 ipamV4Conf *libnetwork.IpamConf 688 ipamV6Conf *libnetwork.IpamConf 689 ) 690 691 ipamV4Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} 692 693 nw, nw6List, err := ipamutils.ElectInterfaceAddresses(bridgeName) 694 if err == nil { 695 ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String() 696 hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask) 697 if hip.IsGlobalUnicast() { 698 ipamV4Conf.Gateway = nw.IP.String() 699 } 700 } 701 702 if config.bridgeConfig.IP != "" { 703 ipamV4Conf.PreferredPool = config.bridgeConfig.IP 704 ip, _, err := net.ParseCIDR(config.bridgeConfig.IP) 705 if err != nil { 706 return err 707 } 708 ipamV4Conf.Gateway = ip.String() 709 } else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" { 710 logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool) 711 } 712 713 if config.bridgeConfig.FixedCIDR != "" { 714 _, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR) 715 if err != nil { 716 return err 717 } 718 719 ipamV4Conf.SubPool = fCIDR.String() 720 } 721 722 if config.bridgeConfig.DefaultGatewayIPv4 != nil { 723 ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.bridgeConfig.DefaultGatewayIPv4.String() 724 } 725 726 var deferIPv6Alloc bool 727 if config.bridgeConfig.FixedCIDRv6 != "" { 728 _, fCIDRv6, err := net.ParseCIDR(config.bridgeConfig.FixedCIDRv6) 729 if err != nil { 730 return err 731 } 732 733 // In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has 734 // at least 48 host bits, we need to guarantee the current behavior where the containers' 735 // IPv6 addresses will be constructed based on the containers' interface MAC address. 736 // We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints 737 // on this network until after the driver has created the endpoint and returned the 738 // constructed address. Libnetwork will then reserve this address with the ipam driver. 739 ones, _ := fCIDRv6.Mask.Size() 740 deferIPv6Alloc = ones <= 80 741 742 if ipamV6Conf == nil { 743 ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} 744 } 745 ipamV6Conf.PreferredPool = fCIDRv6.String() 746 747 // In case the --fixed-cidr-v6 is specified and the current docker0 bridge IPv6 748 // address belongs to the same network, we need to inform libnetwork about it, so 749 // that it can be reserved with IPAM and it will not be given away to somebody else 750 for _, nw6 := range nw6List { 751 if fCIDRv6.Contains(nw6.IP) { 752 ipamV6Conf.Gateway = nw6.IP.String() 753 break 754 } 755 } 756 } 757 758 if config.bridgeConfig.DefaultGatewayIPv6 != nil { 759 if ipamV6Conf == nil { 760 ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} 761 } 762 ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = config.bridgeConfig.DefaultGatewayIPv6.String() 763 } 764 765 v4Conf := []*libnetwork.IpamConf{ipamV4Conf} 766 v6Conf := []*libnetwork.IpamConf{} 767 if ipamV6Conf != nil { 768 v6Conf = append(v6Conf, ipamV6Conf) 769 } 770 // Initialize default network on "bridge" with the same name 771 _, err = controller.NewNetwork("bridge", "bridge", 772 libnetwork.NetworkOptionEnableIPv6(config.bridgeConfig.EnableIPv6), 773 libnetwork.NetworkOptionDriverOpts(netOption), 774 libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), 775 libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc)) 776 if err != nil { 777 return fmt.Errorf("Error creating default \"bridge\" network: %v", err) 778 } 779 return nil 780 } 781 782 // setupInitLayer populates a directory with mountpoints suitable 783 // for bind-mounting things into the container. 784 // 785 // This extra layer is used by all containers as the top-most ro layer. It protects 786 // the container from unwanted side-effects on the rw layer. 787 func setupInitLayer(initLayer string, rootUID, rootGID int) error { 788 for pth, typ := range map[string]string{ 789 "/dev/pts": "dir", 790 "/dev/shm": "dir", 791 "/proc": "dir", 792 "/sys": "dir", 793 "/.dockerenv": "file", 794 "/etc/resolv.conf": "file", 795 "/etc/hosts": "file", 796 "/etc/hostname": "file", 797 "/dev/console": "file", 798 "/etc/mtab": "/proc/mounts", 799 } { 800 parts := strings.Split(pth, "/") 801 prev := "/" 802 for _, p := range parts[1:] { 803 prev = filepath.Join(prev, p) 804 syscall.Unlink(filepath.Join(initLayer, prev)) 805 } 806 807 if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil { 808 if os.IsNotExist(err) { 809 if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootUID, rootGID); err != nil { 810 return err 811 } 812 switch typ { 813 case "dir": 814 if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, pth), 0755, rootUID, rootGID); err != nil { 815 return err 816 } 817 case "file": 818 f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755) 819 if err != nil { 820 return err 821 } 822 f.Chown(rootUID, rootGID) 823 f.Close() 824 default: 825 if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil { 826 return err 827 } 828 } 829 } else { 830 return err 831 } 832 } 833 } 834 835 // Layer is ready to use, if it wasn't before. 836 return nil 837 } 838 839 // Parse the remapped root (user namespace) option, which can be one of: 840 // username - valid username from /etc/passwd 841 // username:groupname - valid username; valid groupname from /etc/group 842 // uid - 32-bit unsigned int valid Linux UID value 843 // uid:gid - uid value; 32-bit unsigned int Linux GID value 844 // 845 // If no groupname is specified, and a username is specified, an attempt 846 // will be made to lookup a gid for that username as a groupname 847 // 848 // If names are used, they are verified to exist in passwd/group 849 func parseRemappedRoot(usergrp string) (string, string, error) { 850 851 var ( 852 userID, groupID int 853 username, groupname string 854 ) 855 856 idparts := strings.Split(usergrp, ":") 857 if len(idparts) > 2 { 858 return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp) 859 } 860 861 if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil { 862 // must be a uid; take it as valid 863 userID = int(uid) 864 luser, err := user.LookupUid(userID) 865 if err != nil { 866 return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err) 867 } 868 username = luser.Name 869 if len(idparts) == 1 { 870 // if the uid was numeric and no gid was specified, take the uid as the gid 871 groupID = userID 872 lgrp, err := user.LookupGid(groupID) 873 if err != nil { 874 return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err) 875 } 876 groupname = lgrp.Name 877 } 878 } else { 879 lookupName := idparts[0] 880 // special case: if the user specified "default", they want Docker to create or 881 // use (after creation) the "dockremap" user/group for root remapping 882 if lookupName == defaultIDSpecifier { 883 lookupName = defaultRemappedID 884 } 885 luser, err := user.LookupUser(lookupName) 886 if err != nil && idparts[0] != defaultIDSpecifier { 887 // error if the name requested isn't the special "dockremap" ID 888 return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err) 889 } else if err != nil { 890 // special case-- if the username == "default", then we have been asked 891 // to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid} 892 // ranges will be used for the user and group mappings in user namespaced containers 893 _, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID) 894 if err == nil { 895 return defaultRemappedID, defaultRemappedID, nil 896 } 897 return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err) 898 } 899 username = luser.Name 900 if len(idparts) == 1 { 901 // we only have a string username, and no group specified; look up gid from username as group 902 group, err := user.LookupGroup(lookupName) 903 if err != nil { 904 return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err) 905 } 906 groupID = group.Gid 907 groupname = group.Name 908 } 909 } 910 911 if len(idparts) == 2 { 912 // groupname or gid is separately specified and must be resolved 913 // to a unsigned 32-bit gid 914 if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil { 915 // must be a gid, take it as valid 916 groupID = int(gid) 917 lgrp, err := user.LookupGid(groupID) 918 if err != nil { 919 return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err) 920 } 921 groupname = lgrp.Name 922 } else { 923 // not a number; attempt a lookup 924 if _, err := user.LookupGroup(idparts[1]); err != nil { 925 return "", "", fmt.Errorf("Error during groupname lookup for %q: %v", idparts[1], err) 926 } 927 groupname = idparts[1] 928 } 929 } 930 return username, groupname, nil 931 } 932 933 func setupRemappedRoot(config *Config) ([]idtools.IDMap, []idtools.IDMap, error) { 934 if runtime.GOOS != "linux" && config.RemappedRoot != "" { 935 return nil, nil, fmt.Errorf("User namespaces are only supported on Linux") 936 } 937 938 // if the daemon was started with remapped root option, parse 939 // the config option to the int uid,gid values 940 var ( 941 uidMaps, gidMaps []idtools.IDMap 942 ) 943 if config.RemappedRoot != "" { 944 username, groupname, err := parseRemappedRoot(config.RemappedRoot) 945 if err != nil { 946 return nil, nil, err 947 } 948 if username == "root" { 949 // Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op 950 // effectively 951 logrus.Warnf("User namespaces: root cannot be remapped with itself; user namespaces are OFF") 952 return uidMaps, gidMaps, nil 953 } 954 logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname) 955 // update remapped root setting now that we have resolved them to actual names 956 config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname) 957 958 uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname) 959 if err != nil { 960 return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err) 961 } 962 } 963 return uidMaps, gidMaps, nil 964 } 965 966 func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error { 967 config.Root = rootDir 968 // the docker root metadata directory needs to have execute permissions for all users (g+x,o+x) 969 // so that syscalls executing as non-root, operating on subdirectories of the graph root 970 // (e.g. mounted layers of a container) can traverse this path. 971 // The user namespace support will create subdirectories for the remapped root host uid:gid 972 // pair owned by that same uid:gid pair for proper write access to those needed metadata and 973 // layer content subtrees. 974 if _, err := os.Stat(rootDir); err == nil { 975 // root current exists; verify the access bits are correct by setting them 976 if err = os.Chmod(rootDir, 0711); err != nil { 977 return err 978 } 979 } else if os.IsNotExist(err) { 980 // no root exists yet, create it 0711 with root:root ownership 981 if err := os.MkdirAll(rootDir, 0711); err != nil { 982 return err 983 } 984 } 985 986 // if user namespaces are enabled we will create a subtree underneath the specified root 987 // with any/all specified remapped root uid/gid options on the daemon creating 988 // a new subdirectory with ownership set to the remapped uid/gid (so as to allow 989 // `chdir()` to work for containers namespaced to that uid/gid) 990 if config.RemappedRoot != "" { 991 config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootUID, rootGID)) 992 logrus.Debugf("Creating user namespaced daemon root: %s", config.Root) 993 // Create the root directory if it doesn't exists 994 if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil { 995 return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err) 996 } 997 } 998 return nil 999 } 1000 1001 // registerLinks writes the links to a file. 1002 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error { 1003 if hostConfig == nil || hostConfig.NetworkMode.IsUserDefined() { 1004 return nil 1005 } 1006 1007 for _, l := range hostConfig.Links { 1008 name, alias, err := runconfigopts.ParseLink(l) 1009 if err != nil { 1010 return err 1011 } 1012 child, err := daemon.GetContainer(name) 1013 if err != nil { 1014 //An error from daemon.GetContainer() means this name could not be found 1015 return fmt.Errorf("Could not get container for %s", name) 1016 } 1017 for child.HostConfig.NetworkMode.IsContainer() { 1018 parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2) 1019 child, err = daemon.GetContainer(parts[1]) 1020 if err != nil { 1021 return fmt.Errorf("Could not get container for %s", parts[1]) 1022 } 1023 } 1024 if child.HostConfig.NetworkMode.IsHost() { 1025 return runconfig.ErrConflictHostNetworkAndLinks 1026 } 1027 if err := daemon.registerLink(container, child, alias); err != nil { 1028 return err 1029 } 1030 } 1031 1032 // After we load all the links into the daemon 1033 // set them to nil on the hostconfig 1034 return container.WriteHostConfig() 1035 } 1036 1037 // conditionalMountOnStart is a platform specific helper function during the 1038 // container start to call mount. 1039 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { 1040 return daemon.Mount(container) 1041 } 1042 1043 // conditionalUnmountOnCleanup is a platform specific helper function called 1044 // during the cleanup of a container to unmount. 1045 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error { 1046 return daemon.Unmount(container) 1047 } 1048 1049 func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error { 1050 // Unix has no custom images to register 1051 return nil 1052 } 1053 1054 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) { 1055 if !c.IsRunning() { 1056 return nil, errNotRunning{c.ID} 1057 } 1058 stats, err := daemon.containerd.Stats(c.ID) 1059 if err != nil { 1060 return nil, err 1061 } 1062 s := &types.StatsJSON{} 1063 cgs := stats.CgroupStats 1064 if cgs != nil { 1065 s.BlkioStats = types.BlkioStats{ 1066 IoServiceBytesRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceBytesRecursive), 1067 IoServicedRecursive: copyBlkioEntry(cgs.BlkioStats.IoServicedRecursive), 1068 IoQueuedRecursive: copyBlkioEntry(cgs.BlkioStats.IoQueuedRecursive), 1069 IoServiceTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceTimeRecursive), 1070 IoWaitTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoWaitTimeRecursive), 1071 IoMergedRecursive: copyBlkioEntry(cgs.BlkioStats.IoMergedRecursive), 1072 IoTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoTimeRecursive), 1073 SectorsRecursive: copyBlkioEntry(cgs.BlkioStats.SectorsRecursive), 1074 } 1075 cpu := cgs.CpuStats 1076 s.CPUStats = types.CPUStats{ 1077 CPUUsage: types.CPUUsage{ 1078 TotalUsage: cpu.CpuUsage.TotalUsage, 1079 PercpuUsage: cpu.CpuUsage.PercpuUsage, 1080 UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode, 1081 UsageInUsermode: cpu.CpuUsage.UsageInUsermode, 1082 }, 1083 ThrottlingData: types.ThrottlingData{ 1084 Periods: cpu.ThrottlingData.Periods, 1085 ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods, 1086 ThrottledTime: cpu.ThrottlingData.ThrottledTime, 1087 }, 1088 } 1089 mem := cgs.MemoryStats.Usage 1090 s.MemoryStats = types.MemoryStats{ 1091 Usage: mem.Usage, 1092 MaxUsage: mem.MaxUsage, 1093 Stats: cgs.MemoryStats.Stats, 1094 Failcnt: mem.Failcnt, 1095 Limit: mem.Limit, 1096 } 1097 // if the container does not set memory limit, use the machineMemory 1098 if mem.Limit > daemon.statsCollector.machineMemory && daemon.statsCollector.machineMemory > 0 { 1099 s.MemoryStats.Limit = daemon.statsCollector.machineMemory 1100 } 1101 if cgs.PidsStats != nil { 1102 s.PidsStats = types.PidsStats{ 1103 Current: cgs.PidsStats.Current, 1104 } 1105 } 1106 } 1107 s.Read = time.Unix(int64(stats.Timestamp), 0) 1108 return s, nil 1109 } 1110 1111 // setDefaultIsolation determine the default isolation mode for the 1112 // daemon to run in. This is only applicable on Windows 1113 func (daemon *Daemon) setDefaultIsolation() error { 1114 return nil 1115 } 1116 1117 func rootFSToAPIType(rootfs *image.RootFS) types.RootFS { 1118 var layers []string 1119 for _, l := range rootfs.DiffIDs { 1120 layers = append(layers, l.String()) 1121 } 1122 return types.RootFS{ 1123 Type: rootfs.Type, 1124 Layers: layers, 1125 } 1126 }