github.com/moby/docker@v26.1.3+incompatible/daemon/daemon_windows.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 "github.com/Microsoft/hcsshim" 12 "github.com/Microsoft/hcsshim/osversion" 13 "github.com/containerd/log" 14 containertypes "github.com/docker/docker/api/types/container" 15 networktypes "github.com/docker/docker/api/types/network" 16 "github.com/docker/docker/container" 17 "github.com/docker/docker/daemon/config" 18 "github.com/docker/docker/libcontainerd/local" 19 "github.com/docker/docker/libcontainerd/remote" 20 "github.com/docker/docker/libnetwork" 21 nwconfig "github.com/docker/docker/libnetwork/config" 22 winlibnetwork "github.com/docker/docker/libnetwork/drivers/windows" 23 "github.com/docker/docker/libnetwork/netlabel" 24 "github.com/docker/docker/libnetwork/options" 25 "github.com/docker/docker/libnetwork/scope" 26 "github.com/docker/docker/pkg/idtools" 27 "github.com/docker/docker/pkg/parsers" 28 "github.com/docker/docker/pkg/parsers/operatingsystem" 29 "github.com/docker/docker/pkg/sysinfo" 30 "github.com/docker/docker/pkg/system" 31 "github.com/docker/docker/runconfig" 32 "github.com/pkg/errors" 33 "golang.org/x/sys/windows" 34 "golang.org/x/sys/windows/svc/mgr" 35 ) 36 37 const ( 38 isWindows = true 39 windowsMinCPUShares = 1 40 windowsMaxCPUShares = 10000 41 windowsMinCPUPercent = 1 42 windowsMaxCPUPercent = 100 43 44 windowsV1RuntimeName = "com.docker.hcsshim.v1" 45 windowsV2RuntimeName = "io.containerd.runhcs.v1" 46 ) 47 48 // Windows containers are much larger than Linux containers and each of them 49 // have > 20 system processes which why we use much smaller parallelism value. 50 func adjustParallelLimit(n int, limit int) int { 51 return int(math.Max(1, math.Floor(float64(runtime.NumCPU())*.8))) 52 } 53 54 // Windows has no concept of an execution state directory. So use config.Root here. 55 func getPluginExecRoot(cfg *config.Config) string { 56 return filepath.Join(cfg.Root, "plugins") 57 } 58 59 func (daemon *Daemon) parseSecurityOpt(daemonCfg *config.Config, securityOptions *container.SecurityOptions, hostConfig *containertypes.HostConfig) error { 60 return nil 61 } 62 63 func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error { 64 return nil 65 } 66 67 // adaptContainerSettings is called during container creation to modify any 68 // settings necessary in the HostConfig structure. 69 func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig) error { 70 return nil 71 } 72 73 // verifyPlatformContainerResources performs platform-specific validation of the container's resource-configuration 74 func verifyPlatformContainerResources(resources *containertypes.Resources, isHyperv bool) (warnings []string, err error) { 75 fixMemorySwappiness(resources) 76 if !isHyperv { 77 // The processor resource controls are mutually exclusive on 78 // Windows Server Containers, the order of precedence is 79 // CPUCount first, then CPUShares, and CPUPercent last. 80 if resources.CPUCount > 0 { 81 if resources.CPUShares > 0 { 82 warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded") 83 resources.CPUShares = 0 84 } 85 if resources.CPUPercent > 0 { 86 warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded") 87 resources.CPUPercent = 0 88 } 89 } else if resources.CPUShares > 0 { 90 if resources.CPUPercent > 0 { 91 warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded") 92 resources.CPUPercent = 0 93 } 94 } 95 } 96 97 if resources.CPUShares < 0 || resources.CPUShares > windowsMaxCPUShares { 98 return warnings, fmt.Errorf("range of CPUShares is from %d to %d", windowsMinCPUShares, windowsMaxCPUShares) 99 } 100 if resources.CPUPercent < 0 || resources.CPUPercent > windowsMaxCPUPercent { 101 return warnings, fmt.Errorf("range of CPUPercent is from %d to %d", windowsMinCPUPercent, windowsMaxCPUPercent) 102 } 103 if resources.CPUCount < 0 { 104 return warnings, fmt.Errorf("invalid CPUCount: CPUCount cannot be negative") 105 } 106 107 if resources.NanoCPUs > 0 && resources.CPUPercent > 0 { 108 return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Percent cannot both be set") 109 } 110 if resources.NanoCPUs > 0 && resources.CPUShares > 0 { 111 return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Shares cannot both be set") 112 } 113 // The precision we could get is 0.01, because on Windows we have to convert to CPUPercent. 114 // We don't set the lower limit here and it is up to the underlying platform (e.g., Windows) to return an error. 115 if resources.NanoCPUs < 0 || resources.NanoCPUs > int64(sysinfo.NumCPU())*1e9 { 116 return warnings, fmt.Errorf("range of CPUs is from 0.01 to %d.00, as there are only %d CPUs available", sysinfo.NumCPU(), sysinfo.NumCPU()) 117 } 118 119 if len(resources.BlkioDeviceReadBps) > 0 { 120 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadBps") 121 } 122 if len(resources.BlkioDeviceReadIOps) > 0 { 123 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadIOps") 124 } 125 if len(resources.BlkioDeviceWriteBps) > 0 { 126 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteBps") 127 } 128 if len(resources.BlkioDeviceWriteIOps) > 0 { 129 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteIOps") 130 } 131 if resources.BlkioWeight > 0 { 132 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeight") 133 } 134 if len(resources.BlkioWeightDevice) > 0 { 135 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeightDevice") 136 } 137 if resources.CgroupParent != "" { 138 return warnings, fmt.Errorf("invalid option: Windows does not support CgroupParent") 139 } 140 if resources.CPUPeriod != 0 { 141 return warnings, fmt.Errorf("invalid option: Windows does not support CPUPeriod") 142 } 143 if resources.CpusetCpus != "" { 144 return warnings, fmt.Errorf("invalid option: Windows does not support CpusetCpus") 145 } 146 if resources.CpusetMems != "" { 147 return warnings, fmt.Errorf("invalid option: Windows does not support CpusetMems") 148 } 149 if resources.KernelMemory != 0 { 150 return warnings, fmt.Errorf("invalid option: Windows does not support KernelMemory") 151 } 152 if resources.MemoryReservation != 0 { 153 return warnings, fmt.Errorf("invalid option: Windows does not support MemoryReservation") 154 } 155 if resources.MemorySwap != 0 { 156 return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwap") 157 } 158 if resources.MemorySwappiness != nil { 159 return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwappiness") 160 } 161 if resources.OomKillDisable != nil && *resources.OomKillDisable { 162 return warnings, fmt.Errorf("invalid option: Windows does not support OomKillDisable") 163 } 164 if resources.PidsLimit != nil && *resources.PidsLimit != 0 { 165 return warnings, fmt.Errorf("invalid option: Windows does not support PidsLimit") 166 } 167 if len(resources.Ulimits) != 0 { 168 return warnings, fmt.Errorf("invalid option: Windows does not support Ulimits") 169 } 170 return warnings, nil 171 } 172 173 // verifyPlatformContainerSettings performs platform-specific validation of the 174 // hostconfig and config structures. 175 func verifyPlatformContainerSettings(daemon *Daemon, daemonCfg *configStore, hostConfig *containertypes.HostConfig, update bool) (warnings []string, err error) { 176 if hostConfig == nil { 177 return nil, nil 178 } 179 return verifyPlatformContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig)) 180 } 181 182 // verifyDaemonSettings performs validation of daemon config struct 183 func verifyDaemonSettings(config *config.Config) error { 184 return nil 185 } 186 187 // checkSystem validates platform-specific requirements 188 func checkSystem() error { 189 // Validate the OS version. Note that dockerd.exe must be manifested for this 190 // call to return the correct version. 191 if osversion.Get().MajorVersion < 10 || osversion.Build() < osversion.RS5 { 192 return fmt.Errorf("this version of Windows does not support the docker daemon (Windows build %d or higher is required)", osversion.RS5) 193 } 194 195 vmcompute := windows.NewLazySystemDLL("vmcompute.dll") 196 if vmcompute.Load() != nil { 197 return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed") 198 } 199 200 // Ensure that the required Host Network Service and vmcompute services 201 // are running. Docker will fail in unexpected ways if this is not present. 202 requiredServices := []string{"hns", "vmcompute"} 203 if err := ensureServicesInstalled(requiredServices); err != nil { 204 return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed") 205 } 206 207 return nil 208 } 209 210 func ensureServicesInstalled(services []string) error { 211 m, err := mgr.Connect() 212 if err != nil { 213 return err 214 } 215 defer m.Disconnect() 216 for _, service := range services { 217 s, err := m.OpenService(service) 218 if err != nil { 219 return errors.Wrapf(err, "failed to open service %s", service) 220 } 221 s.Close() 222 } 223 return nil 224 } 225 226 // configureKernelSecuritySupport configures and validate security support for the kernel 227 func configureKernelSecuritySupport(config *config.Config, driverName string) error { 228 return nil 229 } 230 231 // configureMaxThreads sets the Go runtime max threads threshold 232 func configureMaxThreads(config *config.Config) error { 233 return nil 234 } 235 236 func (daemon *Daemon) initNetworkController(daemonCfg *config.Config, activeSandboxes map[string]interface{}) error { 237 netOptions, err := daemon.networkOptions(daemonCfg, nil, nil) 238 if err != nil { 239 return err 240 } 241 daemon.netController, err = libnetwork.New(netOptions...) 242 if err != nil { 243 return errors.Wrap(err, "error obtaining controller instance") 244 } 245 246 hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "") 247 if err != nil { 248 return err 249 } 250 251 ctx := context.TODO() 252 253 // Remove networks not present in HNS 254 for _, v := range daemon.netController.Networks(ctx) { 255 hnsid := v.DriverOptions()[winlibnetwork.HNSID] 256 found := false 257 258 for _, v := range hnsresponse { 259 if v.Id == hnsid { 260 found = true 261 break 262 } 263 } 264 265 if !found { 266 // non-default nat networks should be re-created if missing from HNS 267 if v.Type() == "nat" && v.Name() != networktypes.NetworkNat { 268 _, _, v4Conf, v6Conf := v.IpamConfig() 269 netOption := map[string]string{} 270 for k, v := range v.DriverOptions() { 271 if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID { 272 netOption[k] = v 273 } 274 } 275 name := v.Name() 276 id := v.ID() 277 278 err = v.Delete() 279 if err != nil { 280 log.G(context.TODO()).Errorf("Error occurred when removing network %v", err) 281 } 282 283 _, err := daemon.netController.NewNetwork("nat", name, id, 284 libnetwork.NetworkOptionGeneric(options.Generic{ 285 netlabel.GenericData: netOption, 286 }), 287 libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), 288 ) 289 if err != nil { 290 log.G(context.TODO()).Errorf("Error occurred when creating network %v", err) 291 } 292 continue 293 } 294 295 // global networks should not be deleted by local HNS 296 if v.Scope() != scope.Global { 297 err = v.Delete() 298 if err != nil { 299 log.G(context.TODO()).Errorf("Error occurred when removing network %v", err) 300 } 301 } 302 } 303 } 304 305 _, err = daemon.netController.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(false)) 306 if err != nil { 307 return err 308 } 309 310 defaultNetworkExists := false 311 312 if network, err := daemon.netController.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { 313 hnsid := network.DriverOptions()[winlibnetwork.HNSID] 314 for _, v := range hnsresponse { 315 if hnsid == v.Id { 316 defaultNetworkExists = true 317 break 318 } 319 } 320 } 321 322 // discover and add HNS networks to windows 323 // network that exist are removed and added again 324 for _, v := range hnsresponse { 325 networkTypeNorm := strings.ToLower(v.Type) 326 if networkTypeNorm == "private" || networkTypeNorm == "internal" { 327 continue // workaround for HNS reporting unsupported networks 328 } 329 var n *libnetwork.Network 330 daemon.netController.WalkNetworks(func(current *libnetwork.Network) bool { 331 hnsid := current.DriverOptions()[winlibnetwork.HNSID] 332 if hnsid == v.Id { 333 n = current 334 return true 335 } 336 return false 337 }) 338 339 drvOptions := make(map[string]string) 340 nid := "" 341 if n != nil { 342 nid = n.ID() 343 344 // global networks should not be deleted by local HNS 345 if n.Scope() == scope.Global { 346 continue 347 } 348 v.Name = n.Name() 349 // This will not cause network delete from HNS as the network 350 // is not yet populated in the libnetwork windows driver 351 352 // restore option if it existed before 353 drvOptions = n.DriverOptions() 354 n.Delete() 355 } 356 netOption := map[string]string{ 357 winlibnetwork.NetworkName: v.Name, 358 winlibnetwork.HNSID: v.Id, 359 } 360 361 // add persisted driver options 362 for k, v := range drvOptions { 363 if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID { 364 netOption[k] = v 365 } 366 } 367 368 v4Conf := []*libnetwork.IpamConf{} 369 for _, subnet := range v.Subnets { 370 ipamV4Conf := libnetwork.IpamConf{} 371 ipamV4Conf.PreferredPool = subnet.AddressPrefix 372 ipamV4Conf.Gateway = subnet.GatewayAddress 373 v4Conf = append(v4Conf, &ipamV4Conf) 374 } 375 376 name := v.Name 377 378 // If there is no nat network create one from the first NAT network 379 // encountered if it doesn't already exist 380 if !defaultNetworkExists && 381 runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) && 382 n == nil { 383 name = runconfig.DefaultDaemonNetworkMode().NetworkName() 384 defaultNetworkExists = true 385 } 386 387 v6Conf := []*libnetwork.IpamConf{} 388 _, err := daemon.netController.NewNetwork(strings.ToLower(v.Type), name, nid, 389 libnetwork.NetworkOptionGeneric(options.Generic{ 390 netlabel.GenericData: netOption, 391 }), 392 libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), 393 ) 394 if err != nil { 395 log.G(context.TODO()).Errorf("Error occurred when creating network %v", err) 396 } 397 } 398 399 if !daemonCfg.DisableBridge { 400 // Initialize default driver "bridge" 401 if err := initBridgeDriver(daemon.netController, daemonCfg.BridgeConfig); err != nil { 402 return err 403 } 404 } 405 406 return nil 407 } 408 409 func initBridgeDriver(controller *libnetwork.Controller, config config.BridgeConfig) error { 410 if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { 411 return nil 412 } 413 414 netOption := map[string]string{ 415 winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(), 416 } 417 418 var ipamOption libnetwork.NetworkOption 419 var subnetPrefix string 420 421 if config.FixedCIDR != "" { 422 subnetPrefix = config.FixedCIDR 423 } 424 425 if subnetPrefix != "" { 426 ipamV4Conf := libnetwork.IpamConf{PreferredPool: subnetPrefix} 427 v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} 428 v6Conf := []*libnetwork.IpamConf{} 429 ipamOption = libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil) 430 } 431 432 _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), "", 433 libnetwork.NetworkOptionGeneric(options.Generic{ 434 netlabel.GenericData: netOption, 435 }), 436 ipamOption, 437 ) 438 if err != nil { 439 return errors.Wrap(err, "error creating default network") 440 } 441 442 return nil 443 } 444 445 // registerLinks sets up links between containers and writes the 446 // configuration out for persistence. As of Windows TP4, links are not supported. 447 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error { 448 return nil 449 } 450 451 func (daemon *Daemon) cleanupMountsByID(in string) error { 452 return nil 453 } 454 455 func (daemon *Daemon) cleanupMounts(*config.Config) error { 456 return nil 457 } 458 459 func recursiveUnmount(_ string) error { 460 return nil 461 } 462 463 func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) { 464 return idtools.IdentityMapping{}, nil 465 } 466 467 func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error { 468 config.Root = rootDir 469 // Create the root directory if it doesn't exists 470 if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil { 471 return err 472 } 473 return nil 474 } 475 476 // runasHyperVContainer returns true if we are going to run as a Hyper-V container 477 func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool { 478 if hostConfig.Isolation.IsDefault() { 479 // Container is set to use the default, so take the default from the daemon configuration 480 return daemon.defaultIsolation.IsHyperV() 481 } 482 483 // Container is requesting an isolation mode. Honour it. 484 return hostConfig.Isolation.IsHyperV() 485 } 486 487 // conditionalMountOnStart is a platform specific helper function during the 488 // container start to call mount. 489 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { 490 if daemon.runAsHyperVContainer(container.HostConfig) { 491 // We do not mount if a Hyper-V container as it needs to be mounted inside the 492 // utility VM, not the host. 493 return nil 494 } 495 return daemon.Mount(container) 496 } 497 498 // conditionalUnmountOnCleanup is a platform specific helper function called 499 // during the cleanup of a container to unmount. 500 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error { 501 if daemon.runAsHyperVContainer(container.HostConfig) { 502 // We do not unmount if a Hyper-V container 503 return nil 504 } 505 return daemon.Unmount(container) 506 } 507 508 func driverOptions(_ *config.Config) nwconfig.Option { 509 return nil 510 } 511 512 // setDefaultIsolation determine the default isolation mode for the 513 // daemon to run in. This is only applicable on Windows 514 func (daemon *Daemon) setDefaultIsolation(config *config.Config) error { 515 // On client SKUs, default to Hyper-V. @engine maintainers. This 516 // should not be removed. Ping Microsoft folks is there are PRs to 517 // to change this. 518 if operatingsystem.IsWindowsClient() { 519 daemon.defaultIsolation = containertypes.IsolationHyperV 520 } else { 521 daemon.defaultIsolation = containertypes.IsolationProcess 522 } 523 for _, option := range config.ExecOptions { 524 key, val, err := parsers.ParseKeyValueOpt(option) 525 if err != nil { 526 return err 527 } 528 key = strings.ToLower(key) 529 switch key { 530 531 case "isolation": 532 if !containertypes.Isolation(val).IsValid() { 533 return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val) 534 } 535 if containertypes.Isolation(val).IsHyperV() { 536 daemon.defaultIsolation = containertypes.IsolationHyperV 537 } 538 if containertypes.Isolation(val).IsProcess() { 539 daemon.defaultIsolation = containertypes.IsolationProcess 540 } 541 default: 542 return fmt.Errorf("Unrecognised exec-opt '%s'\n", key) 543 } 544 } 545 546 log.G(context.TODO()).Infof("Windows default isolation mode: %s", daemon.defaultIsolation) 547 return nil 548 } 549 550 func setMayDetachMounts() error { 551 return nil 552 } 553 554 func (daemon *Daemon) setupSeccompProfile(*config.Config) error { 555 return nil 556 } 557 558 func setupResolvConf(config *config.Config) {} 559 560 func getSysInfo(*config.Config) *sysinfo.SysInfo { 561 return sysinfo.New() 562 } 563 564 func (daemon *Daemon) initLibcontainerd(ctx context.Context, cfg *config.Config) error { 565 var err error 566 567 rt := cfg.DefaultRuntime 568 if rt == "" { 569 if cfg.ContainerdAddr == "" { 570 rt = windowsV1RuntimeName 571 } else { 572 rt = windowsV2RuntimeName 573 } 574 } 575 576 switch rt { 577 case windowsV1RuntimeName: 578 daemon.containerd, err = local.NewClient( 579 ctx, 580 daemon.containerdClient, 581 filepath.Join(cfg.ExecRoot, "containerd"), 582 cfg.ContainerdNamespace, 583 daemon, 584 ) 585 case windowsV2RuntimeName: 586 if cfg.ContainerdAddr == "" { 587 return fmt.Errorf("cannot use the specified runtime %q without containerd", rt) 588 } 589 daemon.containerd, err = remote.NewClient( 590 ctx, 591 daemon.containerdClient, 592 filepath.Join(cfg.ExecRoot, "containerd"), 593 cfg.ContainerdNamespace, 594 daemon, 595 ) 596 default: 597 return fmt.Errorf("unknown windows runtime %s", rt) 598 } 599 600 return err 601 }