Explorar el Código

Merge pull request #24194 from Microsoft/WindowsCpuCount

Windows: Added processor count option
Sebastiaan van Stijn hace 8 años
padre
commit
e8c9063353

+ 53 - 18
daemon/daemon_windows.go

@@ -27,10 +27,13 @@ import (
 )
 
 const (
-	defaultNetworkSpace = "172.16.0.0/12"
-	platformSupported   = true
-	windowsMinCPUShares = 1
-	windowsMaxCPUShares = 10000
+	defaultNetworkSpace  = "172.16.0.0/12"
+	platformSupported    = true
+	windowsMinCPUShares  = 1
+	windowsMaxCPUShares  = 10000
+	windowsMinCPUPercent = 1
+	windowsMaxCPUPercent = 100
+	windowsMinCPUCount   = 1
 )
 
 func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) {
@@ -80,6 +83,15 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
 		return nil
 	}
 
+	numCPU := int64(sysinfo.NumCPU())
+	if hostConfig.CPUCount < 0 {
+		logrus.Warnf("Changing requested CPUCount of %d to minimum allowed of %d", hostConfig.CPUCount, windowsMinCPUCount)
+		hostConfig.CPUCount = windowsMinCPUCount
+	} else if hostConfig.CPUCount > numCPU {
+		logrus.Warnf("Changing requested CPUCount of %d to current number of processors, %d", hostConfig.CPUCount, numCPU)
+		hostConfig.CPUCount = numCPU
+	}
+
 	if hostConfig.CPUShares < 0 {
 		logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, windowsMinCPUShares)
 		hostConfig.CPUShares = windowsMinCPUShares
@@ -88,19 +100,42 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
 		hostConfig.CPUShares = windowsMaxCPUShares
 	}
 
+	if hostConfig.CPUPercent < 0 {
+		logrus.Warnf("Changing requested CPUPercent of %d to minimum allowed of %d", hostConfig.CPUPercent, windowsMinCPUPercent)
+		hostConfig.CPUPercent = windowsMinCPUPercent
+	} else if hostConfig.CPUPercent > windowsMaxCPUPercent {
+		logrus.Warnf("Changing requested CPUPercent of %d to maximum allowed of %d", hostConfig.CPUPercent, windowsMaxCPUPercent)
+		hostConfig.CPUPercent = windowsMaxCPUPercent
+	}
+
 	return nil
 }
 
-func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo) ([]string, error) {
+func verifyContainerResources(resources *containertypes.Resources, isHyperv bool) ([]string, error) {
 	warnings := []string{}
 
-	// cpu subsystem checks and adjustments
-	if resources.CPUPercent < 0 || resources.CPUPercent > 100 {
-		return warnings, fmt.Errorf("Range of CPU percent is from 1 to 100")
-	}
-
-	if resources.CPUPercent > 0 && resources.CPUShares > 0 {
-		return warnings, fmt.Errorf("Conflicting options: CPU Shares and CPU Percent cannot both be set")
+	if !isHyperv {
+		// The processor resource controls are mutually exclusive on
+		// Windows Server Containers, the order of precedence is
+		// CPUCount first, then CPUShares, and CPUPercent last.
+		if resources.CPUCount > 0 {
+			if resources.CPUShares > 0 {
+				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
+				logrus.Warn("Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
+				resources.CPUShares = 0
+			}
+			if resources.CPUPercent > 0 {
+				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
+				logrus.Warn("Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
+				resources.CPUPercent = 0
+			}
+		} else if resources.CPUShares > 0 {
+			if resources.CPUPercent > 0 {
+				warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
+				logrus.Warn("Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
+				resources.CPUPercent = 0
+			}
+		}
 	}
 
 	if resources.NanoCPUs > 0 && resources.CPUPercent > 0 {
@@ -154,7 +189,7 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi
 func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) {
 	warnings := []string{}
 
-	w, err := verifyContainerResources(&hostConfig.Resources, nil)
+	w, err := verifyContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig))
 	warnings = append(warnings, w...)
 	if err != nil {
 		return warnings, err
@@ -388,14 +423,14 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
 }
 
 // runasHyperVContainer returns true if we are going to run as a Hyper-V container
-func (daemon *Daemon) runAsHyperVContainer(container *container.Container) bool {
-	if container.HostConfig.Isolation.IsDefault() {
+func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool {
+	if hostConfig.Isolation.IsDefault() {
 		// Container is set to use the default, so take the default from the daemon configuration
 		return daemon.defaultIsolation.IsHyperV()
 	}
 
 	// Container is requesting an isolation mode. Honour it.
-	return container.HostConfig.Isolation.IsHyperV()
+	return hostConfig.Isolation.IsHyperV()
 
 }
 
@@ -403,7 +438,7 @@ func (daemon *Daemon) runAsHyperVContainer(container *container.Container) bool
 // container start to call mount.
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
 	// We do not mount if a Hyper-V container
-	if !daemon.runAsHyperVContainer(container) {
+	if !daemon.runAsHyperVContainer(container.HostConfig) {
 		return daemon.Mount(container)
 	}
 	return nil
@@ -413,7 +448,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
 // during the cleanup of a container to unmount.
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
 	// We do not unmount if a Hyper-V container
-	if !daemon.runAsHyperVContainer(container) {
+	if !daemon.runAsHyperVContainer(container.HostConfig) {
 		return daemon.Unmount(container)
 	}
 	return nil

+ 2 - 0
daemon/oci_windows.go

@@ -86,11 +86,13 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	if c.HostConfig.NanoCPUs > 0 {
 		cpuPercent = uint8(c.HostConfig.NanoCPUs * 100 / int64(sysinfo.NumCPU()) / 1e9)
 	}
+	cpuCount := uint64(c.HostConfig.CPUCount)
 	memoryLimit := uint64(c.HostConfig.Memory)
 	s.Windows.Resources = &specs.WindowsResources{
 		CPU: &specs.WindowsCPUResources{
 			Percent: &cpuPercent,
 			Shares:  &cpuShares,
+			Count:   &cpuCount,
 		},
 		Memory: &specs.WindowsMemoryResources{
 			Limit: &memoryLimit,

+ 1 - 0
docs/reference/api/docker_remote_api.md

@@ -166,6 +166,7 @@ This section lists each version from latest to oldest.  Each listing includes a
 * `GET /nodes` and `GET /node/(id or name)` now return `Addr` as part of a node's `Status`, which is the address that that node connects to the manager from.
 * The `HostConfig` field now includes `NanoCPUs` that represents CPU quota in units of 10<sup>-9</sup> CPUs.
 * `GET /info` now returns more structured information about security options.
+* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only.
 
 ### v1.24 API changes
 

+ 10 - 1
docs/reference/api/docker_remote_api_v1.25.md

@@ -303,6 +303,7 @@ Create a container
              "MemoryReservation": 0,
              "KernelMemory": 0,
              "NanoCPUs": 500000,
+             "CpuCount": 4,
              "CpuPercent": 80,
              "CpuShares": 512,
              "CpuPeriod": 100000,
@@ -427,7 +428,14 @@ Create a container
     -   **MemoryReservation** - Memory soft limit in bytes.
     -   **KernelMemory** - Kernel memory limit in bytes.
     -   **NanoCPUs** - CPU quota in units of 10<sup>-9</sup> CPUs.
-    -   **CpuPercent** - An integer value containing the usable percentage of the available CPUs. (Windows daemon only)
+    -   **CpuCount** - An integer value containing the number of usable CPUs.
+          Windows daemon only. On Windows Server containers,
+          the processor resource controls are mutually exclusive, the order of precedence
+          is CPUCount first, then CPUShares, and CPUPercent last.
+    -   **CpuPercent** - An integer value containing the usable percentage of
+          the available CPUs. Windows daemon only. On Windows Server containers,
+          the processor resource controls are mutually exclusive, the order of precedence
+          is CPUCount first, then CPUShares, and CPUPercent last.
     -   **CpuShares** - An integer value containing the container's CPU Shares
           (ie. the relative weight vs other containers).
     -   **CpuPeriod** - The length of a CPU period in microseconds.
@@ -623,6 +631,7 @@ Return low-level information on the container `id`
 			"ContainerIDFile": "",
 			"CpusetCpus": "",
 			"CpusetMems": "",
+			"CpuCount": 4,
 			"CpuPercent": 80,
 			"CpuShares": 0,
 			"CpuPeriod": 100000,

+ 3 - 0
docs/reference/commandline/create.md

@@ -31,6 +31,9 @@ Options:
       --cap-drop value              Drop Linux capabilities (default [])
       --cgroup-parent string        Optional parent cgroup for the container
       --cidfile string              Write the container ID to the file
+      --cpu-count int               The number of CPUs available for execution by the container.
+                                    Windows daemon only. On Windows Server containers, this is
+                                    approximated as a percentage of total CPU usage.
       --cpu-percent int             CPU percent (Windows only)
       --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
       --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota

+ 8 - 1
docs/reference/commandline/run.md

@@ -29,7 +29,14 @@ Options:
       --cap-drop value              Drop Linux capabilities (default [])
       --cgroup-parent string        Optional parent cgroup for the container
       --cidfile string              Write the container ID to the file
-      --cpu-percent int             CPU percent (Windows only)
+      --cpu-count int               The number of CPUs available for execution by the container.
+                                    Windows daemon only. On Windows Server containers, this is
+                                    approximated as a percentage of total CPU usage.
+      --cpu-percent int             Limit percentage of CPU available for execution
+                                    by the container. Windows daemon only.
+                                    The processor resource controls are mutually
+                                    exclusive, the order of precedence is CPUCount
+                                    first, then CPUShares, and CPUPercent last.
       --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
       --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
   -c, --cpu-shares int              CPU shares (relative weight)

+ 64 - 0
integration-cli/docker_cli_run_test.go

@@ -4766,3 +4766,67 @@ func (s *DockerSuite) TestRunMount(c *check.C) {
 		}
 	}
 }
+
+func (s *DockerSuite) TestRunWindowsWithCPUCount(c *check.C) {
+	testRequires(c, DaemonIsWindows)
+
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--name", "test", "busybox", "echo", "testing")
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
+
+	out = inspectField(c, "test", "HostConfig.CPUCount")
+	c.Assert(out, check.Equals, "1")
+}
+
+func (s *DockerSuite) TestRunWindowsWithCPUShares(c *check.C) {
+	testRequires(c, DaemonIsWindows)
+
+	out, _ := dockerCmd(c, "run", "--cpu-shares=1000", "--name", "test", "busybox", "echo", "testing")
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
+
+	out = inspectField(c, "test", "HostConfig.CPUShares")
+	c.Assert(out, check.Equals, "1000")
+}
+
+func (s *DockerSuite) TestRunWindowsWithCPUPercent(c *check.C) {
+	testRequires(c, DaemonIsWindows)
+
+	out, _ := dockerCmd(c, "run", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
+
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
+	c.Assert(out, check.Equals, "80")
+}
+
+func (s *DockerSuite) TestRunProcessIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
+	testRequires(c, DaemonIsWindows, IsolationIsProcess)
+
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
+	c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
+	c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
+	c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
+
+	out = inspectField(c, "test", "HostConfig.CPUCount")
+	c.Assert(out, check.Equals, "1")
+
+	out = inspectField(c, "test", "HostConfig.CPUShares")
+	c.Assert(out, check.Equals, "0")
+
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
+	c.Assert(out, check.Equals, "0")
+}
+
+func (s *DockerSuite) TestRunHypervIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
+	testRequires(c, DaemonIsWindows, IsolationIsHyperv)
+
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
+	c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
+
+	out = inspectField(c, "test", "HostConfig.CPUCount")
+	c.Assert(out, check.Equals, "1")
+
+	out = inspectField(c, "test", "HostConfig.CPUShares")
+	c.Assert(out, check.Equals, "1000")
+
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
+	c.Assert(out, check.Equals, "80")
+}

+ 12 - 0
integration-cli/requirements.go

@@ -218,6 +218,18 @@ var (
 		},
 		"Test requires containers are not pausable.",
 	}
+	IsolationIsHyperv = testRequirement{
+		func() bool {
+			return daemonPlatform == "windows" && isolation == "hyperv"
+		},
+		"Test requires a Windows daemon running default isolation mode of hyperv.",
+	}
+	IsolationIsProcess = testRequirement{
+		func() bool {
+			return daemonPlatform == "windows" && isolation == "process"
+		},
+		"Test requires a Windows daemon running default isolation mode of process.",
+	}
 )
 
 // testRequires checks if the environment satisfies the requirements

+ 3 - 0
libcontainerd/client_windows.go

@@ -110,6 +110,9 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
 
 	if spec.Windows.Resources != nil {
 		if spec.Windows.Resources.CPU != nil {
+			if spec.Windows.Resources.CPU.Count != nil {
+				configuration.ProcessorCount = uint32(*spec.Windows.Resources.CPU.Count)
+			}
 			if spec.Windows.Resources.CPU.Shares != nil {
 				configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
 			}

+ 14 - 0
man/docker-create.1.md

@@ -15,6 +15,8 @@ docker-create - Create a new container
 [**--cap-drop**[=*[]*]]
 [**--cgroup-parent**[=*CGROUP-PATH*]]
 [**--cidfile**[=*CIDFILE*]]
+[**--cpu-count**[=*0*]]
+[**--cpu-percent**[=*0*]]
 [**--cpu-period**[=*0*]]
 [**--cpu-quota**[=*0*]]
 [**--cpu-rt-period**[=*0*]]
@@ -124,6 +126,18 @@ The initial status of the container created with **docker create** is 'created'.
 **--cidfile**=""
    Write the container ID to the file
 
+**--cpu-count**=*0*
+    Limit the number of CPUs available for execution by the container.
+    
+    On Windows Server containers, this is approximated as a percentage of total CPU usage.
+
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
+
+**--cpu-percent**=*0*
+    Limit the percentage of CPU available for execution by a container running on a Windows daemon.
+
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
+
 **--cpu-period**=*0*
     Limit the CPU CFS (Completely Fair Scheduler) period
 

+ 14 - 0
man/docker-run.1.md

@@ -15,6 +15,8 @@ docker-run - Run a command in a new container
 [**--cap-drop**[=*[]*]]
 [**--cgroup-parent**[=*CGROUP-PATH*]]
 [**--cidfile**[=*CIDFILE*]]
+[**--cpu-count**[=*0*]]
+[**--cpu-percent**[=*0*]]
 [**--cpu-period**[=*0*]]
 [**--cpu-quota**[=*0*]]
 [**--cpu-rt-period**[=*0*]]
@@ -174,6 +176,18 @@ division of CPU shares:
 **--cidfile**=""
    Write the container ID to the file
 
+**--cpu-count**=*0*
+    Limit the number of CPUs available for execution by the container.
+    
+    On Windows Server containers, this is approximated as a percentage of total CPU usage.
+
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
+
+**--cpu-percent**=*0*
+    Limit the percentage of CPU available for execution by a container running on a Windows daemon.
+
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
+
 **--cpu-period**=*0*
    Limit the CPU CFS (Completely Fair Scheduler) period
 

+ 3 - 0
runconfig/opts/parse.go

@@ -73,6 +73,7 @@ type ContainerOptions struct {
 	kernelMemory       string
 	user               string
 	workingDir         string
+	cpuCount           int64
 	cpuShares          int64
 	cpuPercent         int64
 	cpuPeriod          int64
@@ -227,6 +228,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
 	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
 	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
 	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
+	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
 	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
 	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
 	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
@@ -529,6 +531,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
 		KernelMemory:         kernelMemory,
 		OomKillDisable:       &copts.oomKillDisable,
 		NanoCPUs:             copts.cpus.Value(),
+		CPUCount:             copts.cpuCount,
 		CPUPercent:           copts.cpuPercent,
 		CPUShares:            copts.cpuShares,
 		CPUPeriod:            copts.cpuPeriod,