diff --git a/api/client/build.go b/api/client/build.go index 3139e451b8..1611f527f1 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -55,6 +55,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period") flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") @@ -275,6 +276,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("cpusetmems", *flCPUSetMems) v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10)) + v.Set("cpuperiod", strconv.FormatInt(*flCpuPeriod, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) v.Set("cgroupparent", *flCgroupParent) diff --git a/api/server/server.go b/api/server/server.go index 7b2328fe7c..a6aca07837 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1294,6 +1294,7 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") buildConfig.Memory = int64ValueOrZero(r, "memory") buildConfig.CpuShares = int64ValueOrZero(r, "cpushares") + buildConfig.CpuPeriod = int64ValueOrZero(r, "cpuperiod") buildConfig.CpuQuota = int64ValueOrZero(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") diff --git a/api/types/types.go b/api/types/types.go index 2f5e085eb6..3f33bcfd61 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -152,6 +152,7 @@ type Info struct { DriverStatus [][2]string MemoryLimit bool SwapLimit bool + CpuCfsPeriod bool CpuCfsQuota bool IPv4Forwarding bool Debug bool diff --git a/builder/evaluator.go b/builder/evaluator.go index 49e4a76481..62f86bb889 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -124,6 +124,7 @@ type Builder struct { cpuSetCpus string cpuSetMems string cpuShares int64 + cpuPeriod int64 cpuQuota int64 cgroupParent string memory int64 diff --git a/builder/internals.go b/builder/internals.go index 854bd54182..b1fad9c578 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -556,6 +556,7 @@ func (b *Builder) create() (*daemon.Container, error) { hostConfig := &runconfig.HostConfig{ CpuShares: b.cpuShares, + CpuPeriod: b.cpuPeriod, CpuQuota: b.cpuQuota, CpusetCpus: b.cpuSetCpus, CpusetMems: b.cpuSetMems, diff --git a/builder/job.go b/builder/job.go index a64375c96b..89b1426514 100644 --- a/builder/job.go +++ b/builder/job.go @@ -49,6 +49,7 @@ type Config struct { Memory int64 MemorySwap int64 CpuShares int64 + CpuPeriod int64 CpuQuota int64 CpuSetCpus string CpuSetMems string @@ -164,6 +165,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { ConfigFile: buildConfig.ConfigFile, dockerfileName: buildConfig.DockerfileName, cpuShares: buildConfig.CpuShares, + cpuPeriod: buildConfig.CpuPeriod, cpuQuota: buildConfig.CpuQuota, cpuSetCpus: buildConfig.CpuSetCpus, cpuSetMems: buildConfig.CpuSetMems, diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index bb2b4fe8a4..325e50c000 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -778,6 +778,7 @@ _docker_run() { --cidfile --cpuset --cpu-shares -c + --cpu-period --cpu-quota --device --dns diff --git a/daemon/container.go b/daemon/container.go index 1174a7071d..2a9f46282a 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -383,6 +383,7 @@ func populateCommand(c *Container, env []string) error { CpuShares: c.hostConfig.CpuShares, CpusetCpus: c.hostConfig.CpusetCpus, CpusetMems: c.hostConfig.CpusetMems, + CpuPeriod: c.hostConfig.CpuPeriod, CpuQuota: c.hostConfig.CpuQuota, BlkioWeight: c.hostConfig.BlkioWeight, Rlimits: rlimits, diff --git a/daemon/daemon.go b/daemon/daemon.go index 96364f9608..b82dd42a99 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1170,6 +1170,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") } + if hostConfig.CpuPeriod > 0 && !daemon.SystemConfig().CpuCfsPeriod { + warnings = append(warnings, "Your kernel does not support CPU cfs period. Period discarded.") + hostConfig.CpuPeriod = 0 + } if hostConfig.CpuQuota > 0 && !daemon.SystemConfig().CpuCfsQuota { warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.") hostConfig.CpuQuota = 0 diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 81a4f4d127..68ab60238b 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -106,6 +106,7 @@ type Resources struct { CpuShares int64 `json:"cpu_shares"` CpusetCpus string `json:"cpuset_cpus"` CpusetMems string `json:"cpuset_mems"` + CpuPeriod int64 `json:"cpu_period"` CpuQuota int64 `json:"cpu_quota"` BlkioWeight int64 `json:"blkio_weight"` Rlimits []*ulimit.Rlimit `json:"rlimits"` diff --git a/daemon/execdriver/driver_linux.go b/daemon/execdriver/driver_linux.go index f3bbca3e5c..63d043e851 100644 --- a/daemon/execdriver/driver_linux.go +++ b/daemon/execdriver/driver_linux.go @@ -53,6 +53,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.MemorySwap = c.Resources.MemorySwap container.Cgroups.CpusetCpus = c.Resources.CpusetCpus container.Cgroups.CpusetMems = c.Resources.CpusetMems + container.Cgroups.CpuPeriod = c.Resources.CpuPeriod container.Cgroups.CpuQuota = c.Resources.CpuQuota container.Cgroups.BlkioWeight = c.Resources.BlkioWeight container.Cgroups.OomKillDisable = c.Resources.OomKillDisable diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 55c05498c4..2f88808a08 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -109,6 +109,9 @@ lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} {{if .Resources.CpuShares}} lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} +{{if .Resources.CpuPeriod}} +lxc.cgroup.cpu.cfs_period_us = {{.Resources.CpuPeriod}} +{{end}} {{if .Resources.CpusetCpus}} lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}} {{end}} diff --git a/daemon/info.go b/daemon/info.go index e5ccae80aa..2cd1e5bc65 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -64,6 +64,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { DriverStatus: daemon.GraphDriver().Status(), MemoryLimit: daemon.SystemConfig().MemoryLimit, SwapLimit: daemon.SystemConfig().SwapLimit, + CpuCfsPeriod: daemon.SystemConfig().CpuCfsPeriod, CpuCfsQuota: daemon.SystemConfig().CpuCfsQuota, IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, Debug: os.Getenv("DEBUG") != "", diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 98d2359ec2..18a6f870dc 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -13,6 +13,7 @@ docker-create - Create a new container [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] +[**--cpu-period**[=*0*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] [**--cpu-quota**[=*0*]] @@ -78,6 +79,9 @@ IMAGE [COMMAND] [ARG...] **--cgroup-parent**="" Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. +**--cpu-peroid**=0 + Limit the CPU CFS (Completely Fair Scheduler) period + **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index c4f70fa389..6e533ec6f2 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -13,6 +13,7 @@ docker-run - Run a command in a new container [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] +[**--cpu-period**[=*0*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**[=*false*]] @@ -138,6 +139,11 @@ division of CPU shares: **--cidfile**="" Write the container ID to the file +**--cpu-period**=0 + Limit the CPU CFS (Completely Fair Scheduler) period + + Limit the container's CPU usage. This flag tell the kernel to restrict the container's CPU usage to the period you specify. + **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index c2db79be4c..0512757fd5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -147,6 +147,7 @@ Create a container "Memory": 0, "MemorySwap": 0, "CpuShares": 512, + "CpuPeriod": 100000, "CpusetCpus": "0,1", "CpusetMems": "0,1", "BlkioWeight": 300, @@ -193,6 +194,7 @@ Json Parameters: always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container (ie. the relative weight vs other containers). +- **CpuPeriod** - The length of a CPU period (in microseconds). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. - **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. @@ -350,6 +352,7 @@ Return low-level information on the container `id` "CpusetCpus": "", "CpusetMems": "", "CpuShares": 0, + "CpuPeriod": 100000, "Devices": [], "Dns": null, "DnsSearch": null, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 1efc528b09..30e2f80d0b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -950,6 +950,7 @@ Creates a new container. --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-period=0 Limit the CPU CFS (Completely Fair Scheduler) period --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota --device=[] Add a host device to the container --dns=[] Set custom DNS servers @@ -1907,6 +1908,7 @@ To remove an image using its digest: --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-period=0 Limit the CPU CFS (Completely Fair Scheduler) period --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota -d, --detach=false Run container in background and print container ID --device=[] Add a host device to the container diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index ff6cfa80ba..93ca0d07f5 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -483,6 +483,7 @@ container: -m, --memory="": Memory limit (format: , where unit = b, k, m or g) -memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g) -c, --cpu-shares=0: CPU shares (relative weight) + --cpu-period=0: Limit the CPU CFS (Completely Fair Scheduler) period --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota @@ -620,6 +621,20 @@ division of CPU shares: 101 {C1} 1 100% of CPU1 102 {C1} 2 100% of CPU2 +### CPU period constraint + +The default CPU CFS (Completely Fair Scheduler) period is 100ms. We can use +`--cpu-period` to set the period of CPUs to limit the container's CPU usage. +And usually `--cpu-period` should work with `--cpu-quota`. + +Examples: + + $ docker run -ti --cpu-period=50000 --cpu-quota=25000 ubuntu:14.04 /bin/bash + +If there is 1 CPU, this means the container can get 50% CPU worth of run-time every 50ms. + +For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt). + ### Cpuset constraint We can set cpus in which to allow execution for containers. diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index a6bc54d314..7de38918d0 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1164,6 +1164,28 @@ func (s *DockerSuite) TestRunProcWritableInPrivilegedContainers(c *check.C) { } } +func (s *DockerSuite) TestRunWithCpuPeriod(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "--cpu-period", "50000", "--name", "test", "busybox", "true") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + out = strings.TrimSpace(out) + if strings.Contains(out, "Your kernel does not support CPU cfs period") { + c.Skip("Your kernel does not support CPU cfs period, skip this test") + } + + cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuPeriod}}", "test") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("failed to inspect container: %s, %v", out, err) + } + out = strings.TrimSpace(out) + if out != "50000" { + c.Errorf("setting the CPU CFS period failed") + } +} + func (s *DockerSuite) TestRunWithCpuset(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index e679aabd2a..e6d5d84546 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -15,6 +15,7 @@ import ( type SysInfo struct { MemoryLimit bool SwapLimit bool + CpuCfsPeriod bool CpuCfsQuota bool IPv4ForwardingDisabled bool AppArmor bool @@ -50,8 +51,15 @@ func New(quiet bool) *SysInfo { logrus.Warnf("%v", err) } } else { - _, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) - sysInfo.CpuCfsQuota = err1 == nil + _, err := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_period_us")) + logrus.Warnf("%s", cgroupCpuMountpoint) + sysInfo.CpuCfsPeriod = err == nil + if !sysInfo.CpuCfsPeriod && !quiet { + logrus.Warnf("WARING: Your kernel does not support cgroup cfs period") + } + _, err = ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) + logrus.Warnf("%s", cgroupCpuMountpoint) + sysInfo.CpuCfsQuota = err == nil if !sysInfo.CpuCfsQuota && !quiet { logrus.Warn("Your kernel does not support cgroup cfs quotas") } diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 695a5d1994..719bd3a87c 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -166,9 +166,10 @@ type HostConfig struct { Binds []string ContainerIDFile string LxcConf *LxcConfig - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + CpuPeriod int64 CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 CpuQuota int64 diff --git a/runconfig/parse.go b/runconfig/parse.go index e944a644e3..8ddfd4983f 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -64,6 +64,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota") @@ -319,6 +320,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe Memory: flMemory, MemorySwap: MemorySwap, CpuShares: *flCpuShares, + CpuPeriod: *flCpuPeriod, CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, CpuQuota: *flCpuQuota,