diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go
index dd327ad6c7..e283d76f5e 100644
--- a/daemon/daemon_windows.go
+++ b/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
diff --git a/daemon/oci_windows.go b/daemon/oci_windows.go
index 602b9bf85a..778bb41c95 100644
--- a/daemon/oci_windows.go
+++ b/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,
diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md
index a8e62d10f9..1e1dd1ee25 100644
--- a/docs/reference/api/docker_remote_api.md
+++ b/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-9 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
diff --git a/docs/reference/api/docker_remote_api_v1.25.md b/docs/reference/api/docker_remote_api_v1.25.md
index a174e1c068..a820b49986 100644
--- a/docs/reference/api/docker_remote_api_v1.25.md
+++ b/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-9 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,
diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md
index 23c8797686..034b9dd9d6 100644
--- a/docs/reference/commandline/create.md
+++ b/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
diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md
index 6ce6d29773..fc4a9e5a3a 100644
--- a/docs/reference/commandline/run.md
+++ b/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)
diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go
index a91919da7d..370347400b 100644
--- a/integration-cli/docker_cli_run_test.go
+++ b/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")
+}
diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go
index 56312c1682..abddb9fe4f 100644
--- a/integration-cli/requirements.go
+++ b/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
diff --git a/libcontainerd/client_windows.go b/libcontainerd/client_windows.go
index eca9b2d9fa..16abec3bca 100644
--- a/libcontainerd/client_windows.go
+++ b/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)
}
diff --git a/man/docker-create.1.md b/man/docker-create.1.md
index ff378397ed..767a7fc307 100644
--- a/man/docker-create.1.md
+++ b/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
diff --git a/man/docker-run.1.md b/man/docker-run.1.md
index 5af64627be..ebede9a64f 100644
--- a/man/docker-run.1.md
+++ b/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
diff --git a/runconfig/opts/parse.go b/runconfig/opts/parse.go
index 18e43cdfaf..b1771112cc 100644
--- a/runconfig/opts/parse.go
+++ b/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,