Browse code

Add support cpu cfs quota

Signed-off-by: Lei Jitang <leijitang@huawei.com>

Lei Jitang authored on 2015/04/21 00:16:47
Showing 15 changed files
... ...
@@ -132,6 +132,7 @@ type Info struct {
132 132
 	DriverStatus       [][2]string
133 133
 	MemoryLimit        bool
134 134
 	SwapLimit          bool
135
+	CpuCfsQuota        bool
135 136
 	IPv4Forwarding     bool
136 137
 	Debug              bool
137 138
 	NFd                int
... ...
@@ -770,6 +770,7 @@ _docker_run() {
770 770
 		--cidfile
771 771
 		--cpuset
772 772
 		--cpu-shares -c
773
+		--cpu-quota
773 774
 		--device
774 775
 		--dns
775 776
 		--dns-search
... ...
@@ -356,6 +356,7 @@ func populateCommand(c *Container, env []string) error {
356 356
 		CpuShares:  c.hostConfig.CpuShares,
357 357
 		CpusetCpus: c.hostConfig.CpusetCpus,
358 358
 		CpusetMems: c.hostConfig.CpusetMems,
359
+		CpuQuota:   c.hostConfig.CpuQuota,
359 360
 		Rlimits:    rlimits,
360 361
 	}
361 362
 
... ...
@@ -1246,6 +1246,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri
1246 1246
 	if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
1247 1247
 		return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n")
1248 1248
 	}
1249
+	if hostConfig.CpuQuota > 0 && !daemon.SystemConfig().CpuCfsQuota {
1250
+		warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.")
1251
+		hostConfig.CpuQuota = 0
1252
+	}
1249 1253
 
1250 1254
 	return warnings, nil
1251 1255
 }
... ...
@@ -111,6 +111,7 @@ type Resources struct {
111 111
 	CpuShares  int64            `json:"cpu_shares"`
112 112
 	CpusetCpus string           `json:"cpuset_cpus"`
113 113
 	CpusetMems string           `json:"cpuset_mems"`
114
+	CpuQuota   int64            `json:"cpu_quota"`
114 115
 	Rlimits    []*ulimit.Rlimit `json:"rlimits"`
115 116
 }
116 117
 
... ...
@@ -206,6 +207,7 @@ func SetupCgroups(container *configs.Config, c *Command) error {
206 206
 		container.Cgroups.MemorySwap = c.Resources.MemorySwap
207 207
 		container.Cgroups.CpusetCpus = c.Resources.CpusetCpus
208 208
 		container.Cgroups.CpusetMems = c.Resources.CpusetMems
209
+		container.Cgroups.CpuQuota = c.Resources.CpuQuota
209 210
 	}
210 211
 
211 212
 	return nil
... ...
@@ -113,6 +113,9 @@ lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}}
113 113
 {{if .Resources.CpusetMems}}
114 114
 lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}}
115 115
 {{end}}
116
+{{if .Resources.CpuQuota}}
117
+lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}}
118
+{{end}}
116 119
 {{end}}
117 120
 
118 121
 {{if .LxcConfig}}
... ...
@@ -60,6 +60,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
60 60
 		DriverStatus:       daemon.GraphDriver().Status(),
61 61
 		MemoryLimit:        daemon.SystemConfig().MemoryLimit,
62 62
 		SwapLimit:          daemon.SystemConfig().SwapLimit,
63
+		CpuCfsQuota:        daemon.SystemConfig().CpuCfsQuota,
63 64
 		IPv4Forwarding:     !daemon.SystemConfig().IPv4ForwardingDisabled,
64 65
 		Debug:              os.Getenv("DEBUG") != "",
65 66
 		NFd:                fileutils.GetTotalUsedFds(),
... ...
@@ -14,6 +14,7 @@ docker-create - Create a new container
14 14
 [**--cidfile**[=*CIDFILE*]]
15 15
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
16 16
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
17
+[**--cpu-quota**[=*0*]]
17 18
 [**--device**[=*[]*]]
18 19
 [**--dns-search**[=*[]*]]
19 20
 [**--dns**[=*[]*]]
... ...
@@ -82,6 +83,9 @@ IMAGE [COMMAND] [ARG...]
82 82
 then processes in your Docker container will only use memory from the first
83 83
 two memory nodes.
84 84
 
85
+**-cpu-quota**=0
86
+   Limit the CPU CFS (Completely Fair Scheduler) quota
87
+
85 88
 **--device**=[]
86 89
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
87 90
 
... ...
@@ -15,6 +15,7 @@ docker-run - Run a command in a new container
15 15
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
16 16
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
17 17
 [**-d**|**--detach**[=*false*]]
18
+[**--cpu-quota**[=*0*]]
18 19
 [**--device**[=*[]*]]
19 20
 [**--dns-search**[=*[]*]]
20 21
 [**--dns**[=*[]*]]
... ...
@@ -142,6 +143,13 @@ division of CPU shares:
142 142
 then processes in your Docker container will only use memory from the first
143 143
 two memory nodes.
144 144
 
145
+**--cpu-quota**=0
146
+   Limit the CPU CFS (Completely Fair Scheduler) quota
147
+
148
+   Limit the container's CPU usage. By default, containers run with the full
149
+CPU resource. This flag tell the kernel to restrict the container's CPU usage
150
+to the quota you specify.
151
+
145 152
 **-d**, **--detach**=*true*|*false*
146 153
    Detached mode: run the container in the background and print the new container ID. The default is *false*.
147 154
 
... ...
@@ -894,6 +894,7 @@ Creates a new container.
894 894
       --cidfile=""               Write the container ID to the file
895 895
       --cpuset-cpus=""           CPUs in which to allow execution (0-3, 0,1)
896 896
       --cpuset-mems=""           Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
897
+      --cpu-quota=0              Limit the CPU CFS (Completely Fair Scheduler) quota
897 898
       --device=[]                Add a host device to the container
898 899
       --dns=[]                   Set custom DNS servers
899 900
       --dns-search=[]            Set custom DNS search domains
... ...
@@ -1847,6 +1848,7 @@ To remove an image using its digest:
1847 1847
       --cidfile=""               Write the container ID to the file
1848 1848
       --cpuset-cpus=""           CPUs in which to allow execution (0-3, 0,1)
1849 1849
       --cpuset-mems=""           Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
1850
+      --cpu-quota=0              Limit the CPU CFS (Completely Fair Scheduler) quota
1850 1851
       -d, --detach=false         Run container in background and print container ID
1851 1852
       --device=[]                Add a host device to the container
1852 1853
       --dns=[]                   Set custom DNS servers
... ...
@@ -475,6 +475,7 @@ container:
475 475
     -c, --cpu-shares=0: CPU shares (relative weight)
476 476
     --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1)
477 477
     --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
478
+    --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota
478 479
 
479 480
 ### Memory constraints
480 481
 
... ...
@@ -615,6 +616,15 @@ memory nodes 1 and 3.
615 615
 This example restricts the processes in the container to only use memory from
616 616
 memory nodes 0, 1 and 2.
617 617
 
618
+### CPU quota constraint
619
+
620
+The `--cpu-quota` flag limits the container's CPU usage. The default 0 value
621
+allows the container to take 100% of a CPU resource (1 CPU). The CFS (Completely Fair
622
+Scheduler) handles resource allocation for executing processes and is default
623
+Linux Scheduler used by the kernel. Set this value to 50000 to limit the container
624
+to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary.
625
+For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt).
626
+
618 627
 ## Runtime privilege, Linux capabilities, and LXC configuration
619 628
 
620 629
     --cap-add: Add Linux capabilities
... ...
@@ -106,6 +106,36 @@ func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) {
106 106
 }
107 107
 
108 108
 // "test" should be printed
109
+func TestRunEchoStdoutWitCPUQuota(t *testing.T) {
110
+	defer deleteAllContainers()
111
+
112
+	runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test")
113
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
114
+	if err != nil {
115
+		t.Fatalf("failed to run container: %v, output: %q", err, out)
116
+	}
117
+	out = strings.TrimSpace(out)
118
+	if strings.Contains(out, "Your kernel does not support CPU cfs quota") {
119
+		t.Skip("Your kernel does not support CPU cfs quota, skip this test")
120
+	}
121
+	if out != "test" {
122
+		t.Errorf("container should've printed 'test'")
123
+	}
124
+
125
+	cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test")
126
+	out, _, err = runCommandWithOutput(cmd)
127
+	if err != nil {
128
+		t.Fatalf("failed to inspect container: %s, %v", out, err)
129
+	}
130
+	out = strings.TrimSpace(out)
131
+	if out != "8000" {
132
+		t.Errorf("setting the CPU CFS quota failed")
133
+	}
134
+
135
+	logDone("run - echo with CPU quota")
136
+}
137
+
138
+// "test" should be printed
109 139
 func TestRunEchoNamedContainer(t *testing.T) {
110 140
 	defer deleteAllContainers()
111 141
 
... ...
@@ -13,6 +13,7 @@ import (
13 13
 type SysInfo struct {
14 14
 	MemoryLimit            bool
15 15
 	SwapLimit              bool
16
+	CpuCfsQuota            bool
16 17
 	IPv4ForwardingDisabled bool
17 18
 	AppArmor               bool
18 19
 }
... ...
@@ -39,6 +40,19 @@ func New(quiet bool) *SysInfo {
39 39
 		}
40 40
 	}
41 41
 
42
+	if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil {
43
+		if !quiet {
44
+			logrus.Warnf("WARING: %s\n", err)
45
+		}
46
+	} else {
47
+		_, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us"))
48
+		logrus.Warnf("%s", cgroupCpuMountpoint)
49
+		sysInfo.CpuCfsQuota = err1 == nil
50
+		if !sysInfo.CpuCfsQuota && !quiet {
51
+			logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas")
52
+		}
53
+	}
54
+
42 55
 	// Check if AppArmor is supported.
43 56
 	if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
44 57
 		sysInfo.AppArmor = false
... ...
@@ -167,6 +167,7 @@ type HostConfig struct {
167 167
 	CpuShares       int64  // CPU shares (relative weight vs. other containers)
168 168
 	CpusetCpus      string // CpusetCpus 0-2, 0,1
169 169
 	CpusetMems      string // CpusetMems 0-2, 0,1
170
+	CpuQuota        int64
170 171
 	Privileged      bool
171 172
 	PortBindings    nat.PortMap
172 173
 	Links           []string
... ...
@@ -65,6 +65,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
65 65
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
66 66
 		flCpusetCpus      = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
67 67
 		flCpusetMems      = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
68
+		flCpuQuota        = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
68 69
 		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
69 70
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
70 71
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
... ...
@@ -312,6 +313,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
312 312
 		CpuShares:       *flCpuShares,
313 313
 		CpusetCpus:      *flCpusetCpus,
314 314
 		CpusetMems:      *flCpusetMems,
315
+		CpuQuota:        *flCpuQuota,
315 316
 		Privileged:      *flPrivileged,
316 317
 		PortBindings:    portBindings,
317 318
 		Links:           flLinks.GetAll(),