Преглед изворни кода

Merge pull request #10736 from coolljt0725/add_cpu_limit

Add support cpu cfs_quota
Jessie Frazelle пре 10 година
родитељ
комит
17d5450bc3

+ 1 - 0
api/types/types.go

@@ -132,6 +132,7 @@ type Info struct {
 	DriverStatus       [][2]string
 	DriverStatus       [][2]string
 	MemoryLimit        bool
 	MemoryLimit        bool
 	SwapLimit          bool
 	SwapLimit          bool
+	CpuCfsQuota        bool
 	IPv4Forwarding     bool
 	IPv4Forwarding     bool
 	Debug              bool
 	Debug              bool
 	NFd                int
 	NFd                int

+ 1 - 0
contrib/completion/bash/docker

@@ -770,6 +770,7 @@ _docker_run() {
 		--cidfile
 		--cidfile
 		--cpuset
 		--cpuset
 		--cpu-shares -c
 		--cpu-shares -c
+		--cpu-quota
 		--device
 		--device
 		--dns
 		--dns
 		--dns-search
 		--dns-search

+ 1 - 0
daemon/container.go

@@ -356,6 +356,7 @@ func populateCommand(c *Container, env []string) error {
 		CpuShares:  c.hostConfig.CpuShares,
 		CpuShares:  c.hostConfig.CpuShares,
 		CpusetCpus: c.hostConfig.CpusetCpus,
 		CpusetCpus: c.hostConfig.CpusetCpus,
 		CpusetMems: c.hostConfig.CpusetMems,
 		CpusetMems: c.hostConfig.CpusetMems,
+		CpuQuota:   c.hostConfig.CpuQuota,
 		Rlimits:    rlimits,
 		Rlimits:    rlimits,
 	}
 	}
 
 

+ 4 - 0
daemon/daemon.go

@@ -1250,6 +1250,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri
 	if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
 	if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
 		return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.")
 		return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.")
 	}
 	}
+	if hostConfig.CpuQuota > 0 && !daemon.SystemConfig().CpuCfsQuota {
+		warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.")
+		hostConfig.CpuQuota = 0
+	}
 
 
 	return warnings, nil
 	return warnings, nil
 }
 }

+ 2 - 0
daemon/execdriver/driver.go

@@ -111,6 +111,7 @@ type Resources struct {
 	CpuShares  int64            `json:"cpu_shares"`
 	CpuShares  int64            `json:"cpu_shares"`
 	CpusetCpus string           `json:"cpuset_cpus"`
 	CpusetCpus string           `json:"cpuset_cpus"`
 	CpusetMems string           `json:"cpuset_mems"`
 	CpusetMems string           `json:"cpuset_mems"`
+	CpuQuota   int64            `json:"cpu_quota"`
 	Rlimits    []*ulimit.Rlimit `json:"rlimits"`
 	Rlimits    []*ulimit.Rlimit `json:"rlimits"`
 }
 }
 
 
@@ -206,6 +207,7 @@ func SetupCgroups(container *configs.Config, c *Command) error {
 		container.Cgroups.MemorySwap = c.Resources.MemorySwap
 		container.Cgroups.MemorySwap = c.Resources.MemorySwap
 		container.Cgroups.CpusetCpus = c.Resources.CpusetCpus
 		container.Cgroups.CpusetCpus = c.Resources.CpusetCpus
 		container.Cgroups.CpusetMems = c.Resources.CpusetMems
 		container.Cgroups.CpusetMems = c.Resources.CpusetMems
+		container.Cgroups.CpuQuota = c.Resources.CpuQuota
 	}
 	}
 
 
 	return nil
 	return nil

+ 3 - 0
daemon/execdriver/lxc/lxc_template.go

@@ -113,6 +113,9 @@ lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}}
 {{if .Resources.CpusetMems}}
 {{if .Resources.CpusetMems}}
 lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}}
 lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}}
 {{end}}
 {{end}}
+{{if .Resources.CpuQuota}}
+lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}}
+{{end}}
 {{end}}
 {{end}}
 
 
 {{if .LxcConfig}}
 {{if .LxcConfig}}

+ 1 - 0
daemon/info.go

@@ -60,6 +60,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
 		DriverStatus:       daemon.GraphDriver().Status(),
 		DriverStatus:       daemon.GraphDriver().Status(),
 		MemoryLimit:        daemon.SystemConfig().MemoryLimit,
 		MemoryLimit:        daemon.SystemConfig().MemoryLimit,
 		SwapLimit:          daemon.SystemConfig().SwapLimit,
 		SwapLimit:          daemon.SystemConfig().SwapLimit,
+		CpuCfsQuota:        daemon.SystemConfig().CpuCfsQuota,
 		IPv4Forwarding:     !daemon.SystemConfig().IPv4ForwardingDisabled,
 		IPv4Forwarding:     !daemon.SystemConfig().IPv4ForwardingDisabled,
 		Debug:              os.Getenv("DEBUG") != "",
 		Debug:              os.Getenv("DEBUG") != "",
 		NFd:                fileutils.GetTotalUsedFds(),
 		NFd:                fileutils.GetTotalUsedFds(),

+ 4 - 0
docs/man/docker-create.1.md

@@ -14,6 +14,7 @@ docker-create - Create a new container
 [**--cidfile**[=*CIDFILE*]]
 [**--cidfile**[=*CIDFILE*]]
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
+[**--cpu-quota**[=*0*]]
 [**--device**[=*[]*]]
 [**--device**[=*[]*]]
 [**--dns-search**[=*[]*]]
 [**--dns-search**[=*[]*]]
 [**--dns**[=*[]*]]
 [**--dns**[=*[]*]]
@@ -82,6 +83,9 @@ IMAGE [COMMAND] [ARG...]
 then processes in your Docker container will only use memory from the first
 then processes in your Docker container will only use memory from the first
 two memory nodes.
 two memory nodes.
 
 
+**-cpu-quota**=0
+   Limit the CPU CFS (Completely Fair Scheduler) quota
+
 **--device**=[]
 **--device**=[]
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
 
 

+ 8 - 0
docs/man/docker-run.1.md

@@ -15,6 +15,7 @@ docker-run - Run a command in a new container
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**-d**|**--detach**[=*false*]]
 [**-d**|**--detach**[=*false*]]
+[**--cpu-quota**[=*0*]]
 [**--device**[=*[]*]]
 [**--device**[=*[]*]]
 [**--dns-search**[=*[]*]]
 [**--dns-search**[=*[]*]]
 [**--dns**[=*[]*]]
 [**--dns**[=*[]*]]
@@ -142,6 +143,13 @@ division of CPU shares:
 then processes in your Docker container will only use memory from the first
 then processes in your Docker container will only use memory from the first
 two memory nodes.
 two memory nodes.
 
 
+**--cpu-quota**=0
+   Limit the CPU CFS (Completely Fair Scheduler) quota
+
+   Limit the container's CPU usage. By default, containers run with the full
+CPU resource. This flag tell the kernel to restrict the container's CPU usage
+to the quota you specify.
+
 **-d**, **--detach**=*true*|*false*
 **-d**, **--detach**=*true*|*false*
    Detached mode: run the container in the background and print the new container ID. The default is *false*.
    Detached mode: run the container in the background and print the new container ID. The default is *false*.
 
 

+ 2 - 0
docs/sources/reference/commandline/cli.md

@@ -925,6 +925,7 @@ Creates a new container.
       --cidfile=""               Write the container ID to the file
       --cidfile=""               Write the container ID to the file
       --cpuset-cpus=""           CPUs in which to allow execution (0-3, 0,1)
       --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)
       --cpuset-mems=""           Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
+      --cpu-quota=0              Limit the CPU CFS (Completely Fair Scheduler) quota
       --device=[]                Add a host device to the container
       --device=[]                Add a host device to the container
       --dns=[]                   Set custom DNS servers
       --dns=[]                   Set custom DNS servers
       --dns-search=[]            Set custom DNS search domains
       --dns-search=[]            Set custom DNS search domains
@@ -1879,6 +1880,7 @@ To remove an image using its digest:
       --cidfile=""               Write the container ID to the file
       --cidfile=""               Write the container ID to the file
       --cpuset-cpus=""           CPUs in which to allow execution (0-3, 0,1)
       --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)
       --cpuset-mems=""           Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
+      --cpu-quota=0              Limit the CPU CFS (Completely Fair Scheduler) quota
       -d, --detach=false         Run container in background and print container ID
       -d, --detach=false         Run container in background and print container ID
       --device=[]                Add a host device to the container
       --device=[]                Add a host device to the container
       --dns=[]                   Set custom DNS servers
       --dns=[]                   Set custom DNS servers

+ 10 - 0
docs/sources/reference/run.md

@@ -475,6 +475,7 @@ container:
     -c, --cpu-shares=0: CPU shares (relative weight)
     -c, --cpu-shares=0: CPU shares (relative weight)
     --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1)
     --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.
     --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
 
 
 ### Memory constraints
 ### Memory constraints
 
 
@@ -615,6 +616,15 @@ memory nodes 1 and 3.
 This example restricts the processes in the container to only use memory from
 This example restricts the processes in the container to only use memory from
 memory nodes 0, 1 and 2.
 memory nodes 0, 1 and 2.
 
 
+### CPU quota constraint
+
+The `--cpu-quota` flag limits the container's CPU usage. The default 0 value
+allows the container to take 100% of a CPU resource (1 CPU). The CFS (Completely Fair
+Scheduler) handles resource allocation for executing processes and is default
+Linux Scheduler used by the kernel. Set this value to 50000 to limit the container
+to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary.
+For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt).
+
 ## Runtime privilege, Linux capabilities, and LXC configuration
 ## Runtime privilege, Linux capabilities, and LXC configuration
 
 
     --cap-add: Add Linux capabilities
     --cap-add: Add Linux capabilities

+ 30 - 0
integration-cli/docker_cli_run_test.go

@@ -105,6 +105,36 @@ func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) {
 	logDone("run - echo with CPU and memory limit")
 	logDone("run - echo with CPU and memory limit")
 }
 }
 
 
+// "test" should be printed
+func TestRunEchoStdoutWitCPUQuota(t *testing.T) {
+	defer deleteAllContainers()
+
+	runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		t.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 quota") {
+		t.Skip("Your kernel does not support CPU cfs quota, skip this test")
+	}
+	if out != "test" {
+		t.Errorf("container should've printed 'test'")
+	}
+
+	cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test")
+	out, _, err = runCommandWithOutput(cmd)
+	if err != nil {
+		t.Fatalf("failed to inspect container: %s, %v", out, err)
+	}
+	out = strings.TrimSpace(out)
+	if out != "8000" {
+		t.Errorf("setting the CPU CFS quota failed")
+	}
+
+	logDone("run - echo with CPU quota")
+}
+
 // "test" should be printed
 // "test" should be printed
 func TestRunEchoNamedContainer(t *testing.T) {
 func TestRunEchoNamedContainer(t *testing.T) {
 	defer deleteAllContainers()
 	defer deleteAllContainers()

+ 14 - 0
pkg/sysinfo/sysinfo.go

@@ -13,6 +13,7 @@ import (
 type SysInfo struct {
 type SysInfo struct {
 	MemoryLimit            bool
 	MemoryLimit            bool
 	SwapLimit              bool
 	SwapLimit              bool
+	CpuCfsQuota            bool
 	IPv4ForwardingDisabled bool
 	IPv4ForwardingDisabled bool
 	AppArmor               bool
 	AppArmor               bool
 }
 }
@@ -39,6 +40,19 @@ func New(quiet bool) *SysInfo {
 		}
 		}
 	}
 	}
 
 
+	if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil {
+		if !quiet {
+			logrus.Warnf("WARING: %s\n", err)
+		}
+	} else {
+		_, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us"))
+		logrus.Warnf("%s", cgroupCpuMountpoint)
+		sysInfo.CpuCfsQuota = err1 == nil
+		if !sysInfo.CpuCfsQuota && !quiet {
+			logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas")
+		}
+	}
+
 	// Check if AppArmor is supported.
 	// Check if AppArmor is supported.
 	if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
 	if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
 		sysInfo.AppArmor = false
 		sysInfo.AppArmor = false

+ 1 - 0
runconfig/hostconfig.go

@@ -167,6 +167,7 @@ type HostConfig struct {
 	CpuShares       int64  // CPU shares (relative weight vs. other containers)
 	CpuShares       int64  // CPU shares (relative weight vs. other containers)
 	CpusetCpus      string // CpusetCpus 0-2, 0,1
 	CpusetCpus      string // CpusetCpus 0-2, 0,1
 	CpusetMems      string // CpusetMems 0-2, 0,1
 	CpusetMems      string // CpusetMems 0-2, 0,1
+	CpuQuota        int64
 	Privileged      bool
 	Privileged      bool
 	PortBindings    nat.PortMap
 	PortBindings    nat.PortMap
 	Links           []string
 	Links           []string

+ 2 - 0
runconfig/parse.go

@@ -65,6 +65,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
 		flCpusetCpus      = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
 		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)")
 		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 (Completely Fair Scheduler) quota")
 		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
 		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
@@ -312,6 +313,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		CpuShares:       *flCpuShares,
 		CpuShares:       *flCpuShares,
 		CpusetCpus:      *flCpusetCpus,
 		CpusetCpus:      *flCpusetCpus,
 		CpusetMems:      *flCpusetMems,
 		CpusetMems:      *flCpusetMems,
+		CpuQuota:        *flCpuQuota,
 		Privileged:      *flPrivileged,
 		Privileged:      *flPrivileged,
 		PortBindings:    portBindings,
 		PortBindings:    portBindings,
 		Links:           flLinks.GetAll(),
 		Links:           flLinks.GetAll(),